摘要:
由于计算机是用二进制来存储和处理数字,不能精确表示浮点数,而JavaScript中没有相应的封装类来处理浮点数运算,直接计算会导致运算精度丢失。
为了避免产生精度差异,把需要计算的数字升级(乘以10的n次幂)成计算机能够精确识别的整数,等计算完毕再降级(除以10的n次幂),这是大部分编程语言处理精度差异的通用方法。
关键词:
计算精度 四舍五入 四则运算 精度丢失
1. 疑惑
我们知道,几乎每种编程语言都提供了适合货币计算的类。例如C#提供了decimal,Java提供了BigDecimal,JavaScript提供了Number……
由于之前用decimal和BigDecimal用得很好,没有产生过精度问题,所以一直没有怀疑过JavaScript的Number类型,以为可以直接使用Number类型进行计算。但是直接使用是有问题的。
我们先看看四舍五入的如下代码:

  1. alert(Number(0.009).toFixed(2));
  2. alert(Number(162.295).toFixed(2));
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,第二个数截取不正确
我们再来看看四则运算的代码: 

  1. alert(1/3);//弹出: 0.3333333333333333
  2. alert(0.1 + 0.2);//弹出: 0.30000000000000004
  3. alert(-0.09 - 0.01);//弹出: -0.09999999999999999
  4. alert(0.012345 * 0.000001);//弹出: 1.2344999999999999e-8
  5. alert(0.000001 / 0.0001);//弹出: 0.009999999999999998
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后再计算看看:

  1. alert(Number(1)/Number(3));//弹出: 0.3333333333333333
  2. alert(Number(0.1) + Number(0.2));//弹出: 0.30000000000000004
  3. alert(Number(-0.09) – Number(0.01));//弹出: -0.09999999999999999
  4. alert(Number(0.012345) * Number(0.000001));//弹出: 1.2344999999999999e-8
  5. alert(Number(0.000001) / Number(0.0001));//弹出: 0.009999999999999998
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弹出类型看看:

  1. alert(typeof(1));//弹出: number
  2. alert(typeof(1/3));//弹出: number
  3. alert(typeof(-0.09999999));//弹出: number
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位(31) 8位(30-23) 23位(22-00) 127
双精度浮点数 1位(63) 11位(62-52) 52位(51-00) 1023

我们以单精度浮点数来说明:
指数是8位,可表达的范围是0到255
而对应的实际的指数是-127到+128
这里特殊说明,-127和+128这两个数据在IEEE当中是保留的用作多种用途的
-127表示的数字是0
128和其他位数组合表示多种意义,最典型的就是NAN状态。
知道了这些,我们来模拟计算机的进制转换的计算,就找一个简单的0.1+0.2来推演吧(引用自http://blog.csdn.net/xujiaxuliang/archive/2010/10/13/5939573.aspx):

  1. 十进制0.1
  2. => 二进制0.00011001100110011…(循环0011)
  3. =>尾数为1.1001100110011001100…1100(共52位,除了小数点左边的1),指数为-4(二进制移码为00000000010),符号位为0
  4. => 计算机存储为:0 00000000100 10011001100110011…11001
  5. => 因为尾数最多52位,所以实际存储的值为0.00011001100110011001100110011001100110011001100110011001
  6. 而十进制0.2
  7. => 二进制0.0011001100110011…(循环0011)
  8. =>尾数为1.1001100110011001100…1100(共52位,除了小数点左边的1),指数为-3(二进制移码为00000000011),符号位为0
  9. => 存储为:0 00000000011 10011001100110011…11001
  10. 因为尾数最多52位,所以实际存储的值为0.00110011001100110011001100110011001100110011001100110011
  11.  那么两者相加得:
  12.  0.00011001100110011001100110011001100110011001100110011001
  13. +  0.00110011001100110011001100110011001100110011001100110011
  14. =  0.01001100110011001100110011001100110011001100110011001100
  15. 转换成10进制之后得到:0.30000000000000004
十进制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)就可以了吗?我最初就是这么想的,但后来却发现一些数字运算后实际结果与我们的猜想并不一致。我们来看看这个简单的运算:

  1. alert(512.06*100);
alert(512.06*100);

按常理应该返回51206,但实际结果却是51205.99999999999。奇怪吧?其实也不奇怪,这是因为浮点数不能精确参与乘法运算,即使这个运算很特殊(只是乘以10的scale次方进行升级)。如此我们就不能直接乘以10的scale次方进行升级,那就让我们自己来挪动小数点吧。
怎么挪动小数点肯定大家是各有妙招,此处附上我写的几个方法:

  1. /**
  2. * 左补齐字符串
  3. *
  4. * @param nSize
  5. *            要补齐的长度
  6. * @param ch
  7. *            要补齐的字符
  8. * @return
  9. */
  10. String.prototype.padLeft = function(nSize, ch)
  11. {
  12. var len = 0;
  13. var s = this ? this : "";
  14. ch = ch ? ch : '0';// 默认补0
  15. len = s.length;
  16. while (len < nSize)
  17. {
  18. s = ch + s;
  19. len++;
  20. }
  21. return s;
  22. }
  23. /**
  24. * 右补齐字符串
  25. *
  26. * @param nSize
  27. *            要补齐的长度
  28. * @param ch
  29. *            要补齐的字符
  30. * @return
  31. */
  32. String.prototype.padRight = function(nSize, ch)
  33. {
  34. var len = 0;
  35. var s = this ? this : "";
  36. ch = ch ? ch : '0';// 默认补0
  37. len = s.length;
  38. while (len < nSize)
  39. {
  40. s = s + ch;
  41. len++;
  42. }
  43. return s;
  44. }
  45. /**
  46. * 左移小数点位置(用于数学计算,相当于除以Math.pow(10,scale))
  47. *
  48. * @param scale
  49. *            要移位的刻度
  50. * @return
  51. */
  52. String.prototype.movePointLeft = function(scale)
  53. {
  54. var s, s1, s2, ch, ps, sign;
  55. ch = '.';
  56. sign = '';
  57. s = this ? this : "";
  58. if (scale <= 0) return s;
  59. ps = s.split('.');
  60. s1 = ps[0] ? ps[0] : "";
  61. s2 = ps[1] ? ps[1] : "";
  62. if (s1.slice(0, 1) == '-')
  63. {
  64. s1 = s1.slice(1);
  65. sign = '-';
  66. }
  67. if (s1.length <= scale)
  68. {
  69. ch = "0.";
  70. s1 = s1.padLeft(scale);
  71. }
  72. return sign + s1.slice(0, -scale) + ch + s1.slice(-scale) + s2;
  73. }
  74. /**
  75. * 右移小数点位置(用于数学计算,相当于乘以Math.pow(10,scale))
  76. *
  77. * @param scale
  78. *            要移位的刻度
  79. * @return
  80. */
  81. String.prototype.movePointRight = function(scale)
  82. {
  83. var s, s1, s2, ch, ps;
  84. ch = '.';
  85. s = this ? this : "";
  86. if (scale <= 0) return s;
  87. ps = s.split('.');
  88. s1 = ps[0] ? ps[0] : "";
  89. s2 = ps[1] ? ps[1] : "";
  90. if (s2.length <= scale)
  91. {
  92. ch = '';
  93. s2 = s2.padRight(scale);
  94. }
  95. return s1 + s2.slice(0, scale) + ch + s2.slice(scale, s2.length);
  96. }
  97. /**
  98. * 移动小数点位置(用于数学计算,相当于(乘以/除以)Math.pow(10,scale))
  99. *
  100. * @param scale
  101. *            要移位的刻度(正数表示向右移;负数表示向左移动;0返回原值)
  102. * @return
  103. */
  104. String.prototype.movePoint = function(scale)
  105. {
  106. if (scale >= 0)
  107. return this.movePointRight(scale);
  108. else
  109. return this.movePointLeft(-scale);
  110. }
/**
* 左补齐字符串
*
* @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的代码,采用自定义方法的调用代码变成这样:

  1. alert(512.06.toString().movePoint(2)); //弹出: 51206
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次方)。
代码如下:

  1. Number.prototype.toFixed = function(scale)
  2. {
  3. var s, s1, s2, start;
  4. s1 = this + "";
  5. start = s1.indexOf(".");
  6. s = s1.movePoint(scale);
  7. if (start >= 0)
  8. {
  9. s2 = Number(s1.substr(start + scale + 1, 1));
  10. if (s2 >= 5 && this >= 0 || s2 < 5 && this < 0)
  11. {
  12. s = Math.ceil(s);
  13. }
  14. else
  15. {
  16. s = Math.floor(s);
  17. }
  18. }
  19. return s.toString().movePoint(-scale);
  20. }
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方法后,我们再来执行以下方法

  1. alert(Number(0.009).toFixed(2));//弹出0.01
  2. alert(Number(162.295).toFixed(2));//弹出162.30
alert(Number(0.009).toFixed(2));//弹出0.01
alert(Number(162.295).toFixed(2));//弹出162.30

在ie6、7、8、firefox、Opera下分别进行验证,都能得到相应的正确的结果。
另一种方式是在网上找到的采用正则表达式来进行四舍五入,代码如下:

  1. Number.prototype.toFixed = function(scale)
  2. {
  3. var s = this + "";
  4. if (!scale) scale = 0;
  5. if (s.indexOf(".") == -1) s += ".";
  6. s += new Array(scale + 1).join("0");
  7. if (new RegExp("^(-|\\+)?(\\d+(\\.\\d{0," + (scale + 1) + "})?)\\d*$").test(s))
  8. {
  9. var s = "0" + RegExp.$2, pm = RegExp.$1, a = RegExp.$3.length, b = true;
  10. if (a == scale + 2)
  11. {
  12. a = s.match(/\d/g);
  13. if (parseInt(a[a.length - 1]) > 4)
  14. {
  15. for (var i = a.length - 2; i >= 0; i--)
  16. {
  17. a[i] = parseInt(a[i]) + 1;
  18. if (a[i] == 10)
  19. {
  20. a[i] = 0;
  21. b = i != 1;
  22. }
  23. else
  24. break;
  25. }
  26. }
  27. s = a.join("").replace(new RegExp("(\\d+)(\\d{" + scale + "})\\d$"), "$1.$2");
  28. }
  29. if (b) s = s.substr(1);
  30. return (pm + s).replace(/\.$/, "");
  31. }
  32. return this + "";
  33. }
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 + "";
}

经验证,这两个方法都能够进行准确的四舍五入,那么采用哪个方法好呢?实践出真知,我们写一个简单的方法来验证一下两种方式的性能:

  1. function testRound()
  2. {
  3. var dt, dtBegin, dtEnd, i;
  4. dtBegin = new Date();
  5. for (i=0; i<100000; i++)
  6. {
  7. dt = new Date();
  8. Number("0." + dt.getMilliseconds()).toFixed(2);
  9. }
  10. dtEnd = new Date();
  11. alert(dtEnd.getTime()-dtBegin.getTime());
  12. }
function testRound()
{
var dt, dtBegin, dtEnd, i;
dtBegin = new Date();
for (i=0; i<100000; i++)
{
dt = new Date();
Number("0." + dt.getMilliseconds()).toFixed(2);
}
dtEnd = new Date();
alert(dtEnd.getTime()-dtBegin.getTime());
}

为了避免对同一个数字进行四舍五入运算有缓存问题,我们取当前毫秒数进行四舍五入。经验证,在同一台机器上运算10万次的情况下,用movePoint方法,平均耗时2500毫秒;用正则表达式方法,平均耗时4000毫秒。

3.3 加减乘除
对指定数字进行四舍五入可以通过floor/ceil或者正则表达式达到舍入的目的,那么四则运算是不是也可以升级成计算机能够精确识别的整数来计算,计算完毕再降级呢?这个答案是肯定的,我们先来看看加法:

  1. Number.prototype.add = function(arg)
  2. {
  3. var n, n1, n2, s, s1, s2, ps;
  4. s1 = this.toString();
  5. ps = s1.split('.');
  6. n1 = ps[1] ? ps[1].length : 0;
  7. s2 = arg.toString();
  8. ps = s2.split('.');
  9. n2 = ps[1] ? ps[1].length : 0;
  10. n = n1 > n2 ? n1 : n2;
  11. s = Number(s1.movePoint(n)) + Number(s2.movePoint(n));
  12. s = s.toString().movePoint(-n);
  13. return Number(s);
  14. }
Number.prototype.add = function(arg)
{
var n, n1, n2, s, s1, s2, ps; s1 = this.toString();
ps = s1.split('.');
n1 = ps[1] ? ps[1].length : 0; s2 = arg.toString();
ps = s2.split('.');
n2 = ps[1] ? ps[1].length : 0; n = n1 > n2 ? n1 : n2;
s = Number(s1.movePoint(n)) + Number(s2.movePoint(n));
s = s.toString().movePoint(-n);
return Number(s);
}

这时候再执行之前的加法
alert(Number(0.1).add(0.2));//弹出0.3
这时候就可以计算出精确的结果了。
类似可以写出减法:

  1. Number.prototype.sub = function(arg)
  2. {
  3. var n, n1, n2, s, s1, s2, ps;
  4. s1 = this.toString();
  5. ps = s1.split('.');
  6. n1 = ps[1] ? ps[1].length : 0;
  7. s2 = arg.toString();
  8. ps = s2.split('.');
  9. n2 = ps[1] ? ps[1].length : 0;
  10. n = n1 > n2 ? n1 : n2;
  11. s = Number(s1.movePoint(n)) - Number(s2.movePoint(n));
  12. s = s.toString().movePoint(-n);
  13. return Number(s);
  14. }
Number.prototype.sub = function(arg)
{
var n, n1, n2, s, s1, s2, ps; s1 = this.toString();
ps = s1.split('.');
n1 = ps[1] ? ps[1].length : 0; s2 = arg.toString();
ps = s2.split('.');
n2 = ps[1] ? ps[1].length : 0; n = n1 > n2 ? n1 : n2;
s = Number(s1.movePoint(n)) - Number(s2.movePoint(n));
s = s.toString().movePoint(-n);
return Number(s);
}

类似可以写出乘法:

  1. Number.prototype.mul = function(arg)
  2. {
  3. var n, n1, n2, s, s1, s2, ps;
  4. s1 = this.toString();
  5. ps = s1.split('.');
  6. n1 = ps[1] ? ps[1].length : 0;
  7. s2 = arg.toString();
  8. ps = s2.split('.');
  9. n2 = ps[1] ? ps[1].length : 0;
  10. n = n1 + n2;
  11. s = Number(s1.replace('.', '')) * Number(s2.replace('.', ''));
  12. s = s.toString().movePoint(-n);
  13. return Number(s);
  14. }
Number.prototype.mul = function(arg)
{
var n, n1, n2, s, s1, s2, ps; s1 = this.toString();
ps = s1.split('.');
n1 = ps[1] ? ps[1].length : 0; s2 = arg.toString();
ps = s2.split('.');
n2 = ps[1] ? ps[1].length : 0; n = n1 + n2;
s = Number(s1.replace('.', '')) * Number(s2.replace('.', ''));
s = s.toString().movePoint(-n);
return Number(s);
}

类似可以写出除法:

  1. Number.prototype.div = function(arg)
  2. {
  3. var n, n1, n2, s, s1, s2, ps;
  4. s1 = this.toString();
  5. ps = s1.split('.');
  6. n1 = ps[1] ? ps[1].length : 0;
  7. s2 = arg.toString();
  8. ps = s2.split('.');
  9. n2 = ps[1] ? ps[1].length : 0;
  10. n = n1 - n2;
  11. s = Number(s1.replace('.', '')) / Number(s2.replace('.', ''));
  12. s = s.toString().movePoint(-n);
  13. return Number(s);
  14. }
Number.prototype.div = function(arg)
{
var n, n1, n2, s, s1, s2, ps; s1 = this.toString();
ps = s1.split('.');
n1 = ps[1] ? ps[1].length : 0; s2 = arg.toString();
ps = s2.split('.');
n2 = ps[1] ? ps[1].length : 0; n = n1 - n2;
s = Number(s1.replace('.', '')) / Number(s2.replace('.', ''));
s = s.toString().movePoint(-n);
return Number(s);
}

重要提示:由于除法不能精确到几位小数,在计算完成的时候应根据需要进行适当的四舍五入,以避免产生精度差异。

4. 结论
由于计算机是用二进制来存储和处理数字,不能精确表示浮点数,因此这种精度差异几乎出现在所有的编程语言中(例如C/C++/C#,Java),准确的说:“使用了IEEE 754浮点数格式”来存储浮点类型(float 32,double 64)的任何编程语言都有这个问题!而C#、Java是因为提供了封装类decimal、BigDecimal来进行相应的处理才避开了这个精度差异。
为了避免产生精度差异,把需要计算的数字全升级(乘以10的n次幂)成计算机能够精确识别的整数,等计算完毕再降级(除以10的n次幂),这是大部分编程语言处理精度差异的通用方法。

关于JavaScript中计算精度丢失的问题的更多相关文章

  1. JavaScript数字计算精度丢失的问题和解决方案

    一.JS数字精度丢失的一些典型问题 1. 两个简单的浮点数相加:0.1 + 0.2 != 0.3 // true,下图是firebug的控制台截图: 看看java的计算结果:是不是让你很不能接受 再来 ...

  2. 计算价格, java中浮点数精度丢失的解决方案

    计算价格, java中浮点数精度丢失的解决方案

  3. 0.1+0.2不等于0.3,微信小程序云开发如何解决JavaScript小数计算精度失准的问题

    先看图 这个是JavaScript语言自身存在的一个问题.说道这里不得不提一下网上流传的JavaScript搞笑图 我们在使用云开发来开发微信小程序的时候,会经常遇到JavaScript小数计算精度失 ...

  4. 如何避开JavaScript浮点数计算精度问题(如0.1+0.2!==0.3)

    不知道大家在使用JS的过程中有没有发现某些浮点数运算的时候,得到的结果存在精度问题:比如0.1 + 0.2 = 0.30000000000000004以及7 * 0.8 = 5.60000000000 ...

  5. float类型进行计算精度丢失的问题

    今天一个案子,用户反映数量差异明明是 2.0-1.8,显示的结果却为0.20000005,就自己写了段方法测试了一下:package test1;public class Test2 {/*** @p ...

  6. [ JAVA编程 ] double类型计算精度丢失问题及解决方法

    前言 如果你在测试金融相关产品,请务必覆盖交易金额为小数的场景.特别是使用Java语言的初级开发. Java基本实例 先来看Java中double类型数值加.减.乘.除计算式实例: public cl ...

  7. JavaScript中解决计算精度丢失的问题

    在做项目之前老师就给我们封装好了一个js文件,解决计算中丢失精度的一些函数,直接引用js文件就可以使用. eg: var numA = 0.1; var numB = 0.2; alert( numA ...

  8. javaScript中计算字符串MD5

    进行HTTP网络通信的时候,调用API向服务器请求数据,有时为了防止API调用过程中被黑客恶意篡改,所请求参数需要进行MD5算法计算,得到摘要签名.服务端会根据请求参数,对签名进行验证,签名不合法的请 ...

  9. javascript(js)小数精度丢失的解决方案

    原因:js按照2进制来处理小数的加减乘除,在arg1的基础上 将arg2的精度进行扩展或逆扩展匹配,所以会出现如下情况. javascript(js)的小数点加减乘除问题,是一个js的bug如0.3* ...

随机推荐

  1. [LeetCode145]Binary Tree Postorder Traversal

    题目: Given a list, rotate the list to the right by k places, where k is non-negative. For example:Giv ...

  2. Chrome console(转)

    阅读目录 写在前面 谷歌控制台Elements面板 查看元素上绑定的事情 样式操作 总况 console.log console.info console.error console.warn con ...

  3. datagrid直接编辑保存“设计缺陷”

    当今使用easyUI的datagrid组件的时候,碰到了一些问题,记录下来以便下次高速解决. 需求是在一张表单里会关联有一个列表,能够增删查改 曾经没用easyUI的时候,这个增和改的页面我通常是用一 ...

  4. Jafka来源分析——Processor

    Jafka Acceptor接受client而建立后的连接请求,Acceptor会将Socket连接交给Processor进行处理.Processor通过下面的处理步骤进行client请求的处理: 1 ...

  5. Memcahce(MC)系列(三)Memcached它PHP转让

    由PHP转让Memcahce,首先,需要在server安装Memcache,如何安装Memcache这不是本文的重点, 大约memcache安装,谁的朋友有兴趣,请参阅这里:http://blog.c ...

  6. Action、Category、Data、Extras知识具体解释

    开头 Intent作为联系各Activity之间的纽带,其作用并不仅仅仅仅限于简单的数据传递.通过其自带的属性,事实上能够方便的完毕非常多较为复杂的操作.比如直接调用拨号功能.直接自己主动调用合适的程 ...

  7. POJ 3126 Prime Path(BFS 数字处理)

    意甲冠军  给你两个4位质数a, b  每次你可以改变a个位数,但仍然需要素数的变化  乞讨a有多少次的能力,至少修改成b 基础的bfs  注意数的处理即可了  出队一个数  然后入队全部能够由这个素 ...

  8. jQuery -&gt; 获取各种滤芯(filter)

    按顺序选择 依次选择过滤器(filter)有着 :first 第一元件 :last 最后一个元素 :even 序号为偶数的元素 :odd 序号为奇数的元素 :eq(n) 序号等于n的元素 :lt(n) ...

  9. Maven+Spring

    Maven+Spring 关于Maven Maven是一个用于项目构建的工具,通过它便捷的管理项目的生命周期.即项目的jar包依赖,开发,测试,发布打包. 做过.NET的人应该会联想到Nuget,是的 ...

  10. 5次Shift会触发粘滞键的妙用(转)

    1.前提 你可以在平时亲身接触状态电脑,哪怕是在电脑主人不在的时候(虽然主人不在,或者关机了,进入电脑是要密码的). 2.原理 利用电脑连续按5次Shift会触发粘滞键,它会运行c:\winows\s ...