seo网站推广怎么收费,wordpress 后台 主题,网站按钮确定后图片怎么做,校园网站建设与应用来源#xff1a;http://rockyee.iteye.com/blog/891538
摘要#xff1a; 由于计算机是用二进制来存储和处理数字#xff0c;不能精确表示浮点数#xff0c;而JavaScript中没有相应的封装类来处理浮点数运算#xff0c;直接计算会导致运算精度丢失。 为了避免产生精度差…来源http://rockyee.iteye.com/blog/891538
摘要 由于计算机是用二进制来存储和处理数字不能精确表示浮点数而JavaScript中没有相应的封装类来处理浮点数运算直接计算会导致运算精度丢失。 为了避免产生精度差异把需要计算的数字升级乘以10的n次幂成计算机能够精确识别的整数等计算完毕再降级除以10的n次幂这是大部分编程语言处理精度差异的通用方法。 关键词 计算精度 四舍五入 四则运算 精度丢失 1. 疑惑 我们知道几乎每种编程语言都提供了适合货币计算的类。例如C#提供了decimalJava提供了BigDecimalJavaScript提供了Number…… 由于之前用decimal和BigDecimal用得很好没有产生过精度问题所以一直没有怀疑过JavaScript的Number类型以为可以直接使用Number类型进行计算。但是直接使用是有问题的。 我们先看看四舍五入的如下代码
alert(Number(0.009).toFixed(2));
alert(Number(162.295).toFixed(2));
按正常结果应该分别弹出0.01和162.30。但实际测试结果却是在不同浏览器中得到的是不同的结果 在ie6、7、8下得到0.00和162.30第一个数截取不正确 在firefox中得到0.01和162.29第二个数截取不正确 在opera下得到0.01和162.29第二个数截取不正确 我们再来看看四则运算的代码
alert(1/3);//弹出: 0.3333333333333333
alert(0.1 0.2);//弹出: 0.30000000000000004
alert(-0.09 - 0.01);//弹出: -0.09999999999999999
alert(0.012345 * 0.000001);//弹出: 1.2344999999999999e-8
alert(0.000001 / 0.0001);//弹出: 0.009999999999999998
按正常结果除第一行外(因为其本身就不能除尽)其他都应该要得到精确的结果从弹出的结果我们却发现不是我们想要的正确结果。是因为没有转换成Number类型吗我们转换成Number后再计算看看
alert(Number(1)/Number(3));//弹出: 0.3333333333333333
alert(Number(0.1) Number(0.2));//弹出: 0.30000000000000004
alert(Number(-0.09) – Number(0.01));//弹出: -0.09999999999999999
alert(Number(0.012345) * Number(0.000001));//弹出: 1.2344999999999999e-8
alert(Number(0.000001) / Number(0.0001));//弹出: 0.009999999999999998
还是一样的结果看来javascript默认把数字识别为number类型。为了验证这一点我们用typeof弹出类型看看
alert(typeof(1));//弹出: number
alert(typeof(1/3));//弹出: number
alert(typeof(-0.09999999));//弹出: number
2. 原因 为什么会产生这种精度丢失的问题呢是javascript语言的bug吗 我们回忆一下大学时学过的计算机原理计算机执行的是二进制算术当十进制数不能准确转换为二进制数时这种精度误差就在所难免。 再查查javascript的相关资料我们知道javascript中的数字都是用浮点数表示的并规定使用IEEE 754 标准的双精度浮点数表示 IEEE 754 规定了两种基本浮点格式单精度和双精度。 IEEE单精度格式具有24 位有效数字精度(包含符号号)并总共占用32 位。 IEEE双精度格式具有53 位有效数字精度(包含符号号)并总共占用64 位。 这种结构是一种科学表示法用符号正或负、指数和尾数来表示底数被确定为2也就是说是把一个浮点数表示为尾数乘以2的指数次方再加上符号。下面来看一下具体的规格: 符号位 指数位 小数部分指数偏移量单精度浮点数1位318位30-2323位22-00127双精度浮点数1位6311位62-5252位51-001023 我们以单精度浮点数来说明 指数是8位可表达的范围是0到255 而对应的实际的指数是127到128 这里特殊说明127和128这两个数据在IEEE当中是保留的用作多种用途的 127表示的数字是0 128和其他位数组合表示多种意义最典型的就是NAN状态。 知道了这些我们来模拟计算机的进制转换的计算就找一个简单的0.10.2来推演吧(引用自
http://blog.csdn.net/xujiaxuliang/archive/2010/10/13/5939573.aspx)十进制0.1
二进制0.00011001100110011…(循环0011)
尾数为1.1001100110011001100…1100共52位除了小数点左边的1指数为-4二进制移码为00000000010,符号位为0
计算机存储为0 00000000100 10011001100110011…11001
因为尾数最多52位所以实际存储的值为0.00011001100110011001100110011001100110011001100110011001
而十进制0.2
二进制0.0011001100110011…(循环0011)
尾数为1.1001100110011001100…1100共52位除了小数点左边的1指数为-3二进制移码为00000000011,符号位为0
存储为0 00000000011 10011001100110011…11001
因为尾数最多52位所以实际存储的值为0.00110011001100110011001100110011001100110011001100110011
那么两者相加得
0.00011001100110011001100110011001100110011001100110011001
0.00110011001100110011001100110011001100110011001100110011
0.01001100110011001100110011001100110011001100110011001100
转换成10进制之后得到:0.30000000000000004
从上述的推演过程我们知道这种误差是难免的c#的decimal和Java的BigDecimal之所以没有出现精度差异只是因为在其内部作了相应处理把这种精度差异给屏蔽掉了而javascript是一种弱类型的脚本语言本身并没有对计算精度做相应的处理这就需要我们另外想办法处理了。
3. 解决办法 3.1 升级降级 从上文我们已经知道javascript中产生精度差异的原因是计算机无法精确表示浮点数连自身都不能精确运算起来就更加得不到精确的结果了。那么怎么让计算机精确认识要计算的数呢 我们知道十进制的整数和二进制是可以互相进行精确转换的那么我们把浮点数升级乘以10的n次幂成计算机能够精确识别的整数来计算计算完毕之后再降级除以10的n次幂不就得到精确的结果了吗好就这么办 我们知道Math.pow(10,scale)可以得到10的scale次方那么就把浮点数直接乘以Math.pow(10,scale)就可以了吗我最初就是这么想的但后来却发现一些数字运算后实际结果与我们的猜想并不一致。我们来看看这个简单的运算
alert(512.06*100);
按常理应该返回51206但实际结果却是51205.99999999999。奇怪吧其实也不奇怪这是因为浮点数不能精确参与乘法运算即使这个运算很特殊只是乘以10的scale次方进行升级。如此我们就不能直接乘以10的scale次方进行升级那就让我们自己来挪动小数点吧。 怎么挪动小数点肯定大家是各有妙招此处附上我写的几个方法
/**
* 左补齐字符串
*
* param nSize
* 要补齐的长度
* param ch
* 要补齐的字符
* return
*/
String.prototype.padLeft function(nSize, ch)
{
var len 0;
var s this ? this : ;
ch ch ? ch : 0;// 默认补0
len s.length;
while (len nSize)
{
s ch s;
len;
}
return s;
}
/**
* 右补齐字符串
*
* param nSize
* 要补齐的长度
* param ch
* 要补齐的字符
* return
*/
String.prototype.padRight function(nSize, ch)
{
var len 0;
var s this ? this : ;
ch ch ? ch : 0;// 默认补0
len s.length;
while (len nSize)
{
s s ch;
len;
}
return s;
}
/**
* 左移小数点位置用于数学计算相当于除以Math.pow(10,scale)
*
* param scale
* 要移位的刻度
* return
*/
String.prototype.movePointLeft function(scale)
{
var s, s1, s2, ch, ps, sign;
ch .;
sign ;
s this ? this : ;
if (scale 0) return s;
ps s.split(.);
s1 ps[0] ? ps[0] : ;
s2 ps[1] ? ps[1] : ;
if (s1.slice(0, 1) -)
{
s1 s1.slice(1);
sign -;
}
if (s1.length scale)
{
ch 0.;
s1 s1.padLeft(scale);
}
return sign s1.slice(0, -scale) ch s1.slice(-scale) s2;
}
/**
* 右移小数点位置用于数学计算相当于乘以Math.pow(10,scale)
*
* param scale
* 要移位的刻度
* return
*/
String.prototype.movePointRight function(scale)
{
var s, s1, s2, ch, ps;
ch .;
s this ? this : ;
if (scale 0) return s;
ps s.split(.);
s1 ps[0] ? ps[0] : ;
s2 ps[1] ? ps[1] : ;
if (s2.length scale)
{
ch ;
s2 s2.padRight(scale);
}
return s1 s2.slice(0, scale) ch s2.slice(scale, s2.length);
}
/**
* 移动小数点位置用于数学计算相当于乘以/除以Math.pow(10,scale)
*
* param scale
* 要移位的刻度正数表示向右移负数表示向左移动0返回原值
* return
*/
String.prototype.movePoint function(scale)
{
if (scale 0)
return this.movePointRight(scale);
else
return this.movePointLeft(-scale);
}
这样我们升级降级都可以转换成字符串后调用String对象的自定义方法movePoint了乘以10的scale次方我们传正整数scale除以10的scale次方我们传负整数-scale。 再来看看我们之前升级512.06的代码采用自定义方法的调用代码变成这样
alert(512.06.toString().movePoint(2)); //弹出: 51206
这样直接挪动小数点就不怕它不听话出现一长串数字了(*^__^*)。 当然movePoint方法得到的结果是字符串如果要转成Number类型也很方便怎么转就不再废话了。 3.2 四舍五入 好有了升级降级的基础我们来看看四舍五入的方法由于不同浏览器对Number的toFixed方法有不同的支持我们需要用自己的方法去覆盖浏览器的默认实现。 有一个简单的办法是我们自己来判断要截取数据的后一位是否大于等于5然后进行舍或者入。我们知道Math.ceil方法是取大于等于指定数的最小整数Math.floor方法是取小于等于指定数的最大整数于是我们可以利用这两个方法来进行舍入处理先将要进行舍入的数升级要舍入的位数scale乘以10的scale次方进行ceil或floor取整后再降级要舍入的位数scale除以10的scale次方。 代码如下
Number.prototype.toFixed function(scale)
{
var s, s1, s2, start;
s1 this ;
start s1.indexOf(.);
s s1.movePoint(scale);
if (start 0)
{
s2 Number(s1.substr(start scale 1, 1));
if (s2 5 this 0 || s2 5 this 0)
{
s Math.ceil(s);
}
else
{
s Math.floor(s);
}
}
return s.toString().movePoint(-scale);
}
覆盖Number类型的toFixed方法后我们再来执行以下方法
alert(Number(0.009).toFixed(2));//弹出0.01
alert(Number(162.295).toFixed(2));//弹出162.30
在ie6、7、8、firefox、Opera下分别进行验证都能得到相应的正确的结果。 另一种方式是在网上找到的采用正则表达式来进行四舍五入代码如下
Number.prototype.toFixed function(scale)
{
var s this ;
if (!scale) scale 0;
if (s.indexOf(.) -1) s .;
s new Array(scale 1).join(0);
if (new RegExp(^(-|\\)?(\\d(\\.\\d{0, (scale 1) })?)\\d*$).test(s))
{
var s 0 RegExp.$2, pm RegExp.$1, a RegExp.$3.length, b true;
if (a scale 2)
{
a s.match(/\d/g);
if (parseInt(a[a.length - 1]) 4)
{
for (var i a.length - 2; i 0; i--)
{
a[i] parseInt(a[i]) 1;
if (a[i] 10)
{
a[i] 0;
b i ! 1;
}
else
break;
}
}
s a.join().replace(new RegExp((\\d)(\\d{ scale })\\d$), $1.$2);
}
if (b) s s.substr(1);
return (pm s).replace(/\.$/, );
}
return this ;
}
经验证这两个方法都能够进行准确的四舍五入那么采用哪个方法好呢实践出真知我们写一个简单的方法来验证一下两种方式的性能 function testRound()
{
var dt, dtBegin, dtEnd, i;
dtBegin new Date();
for (i0; i100000; i)
{
dt new Date();
Number(0. dt.getMilliseconds()).toFixed(2);
}
dtEnd new Date();
alert(dtEnd.getTime()-dtBegin.getTime());
}
为了避免对同一个数字进行四舍五入运算有缓存问题我们取当前毫秒数进行四舍五入。经验证在同一台机器上运算10万次的情况下用movePoint方法平均耗时2500毫秒用正则表达式方法平均耗时4000毫秒。