JavaScript精度丢失

短信控制台-时延分析表格

试一下

精度丢失的典型问题

震惊~!

震惊 X 2~!

---大整数运算

Java和Python

     不仅在 JavaScript 中存在这个问题,所有的支持二进制浮点数运算(绝大部分都是 IEEE 754的实现)的系统都存在这个现象。

精度丢失的原因

  • Javascript采用了IEEE-754浮点数表示法
  • 二进制表示法,可以精确地表示分数,比如1/2,1/8,1/1024
  • 我们常用的分数都是十进制分数1/10,1/100等。
  • 二进制浮点数不能精确的表示类似0.1这样的简单的数字

在有限的存储空间下,绝大部分的十进制小数都不能用二进制浮点数来精确表示

十进制中,科学计数法的形式是:

 

 

二进制中,科学计数法就是:

 

 

在有限的存储空间下,十进制小数 0.1 无论如何也不能用这种形式来表示

 

二进制的「科学计数法」

双精度存储(double precision)

  • 1位用来表示符号位(sign)
  • 11位用来表示指数(exponent)
  • 52位表示尾数(fraction)

比如对于0.5,就可以表示成sign = 0, exponent = -1, fraction = 1

IEEE 754对表示方法做了优化

 

  • fraction必须是1-2之间的一个小数,这样fraction就可以表示53位而不是52位数:

      比如原数为0.5, 那么实际上会表示成

 

  • exponent的实际值是exponent-offset,单精度的offset为127

       比如-1会表示成126,因为126-offest(127) = -1


 

IEEE 754对表示方法做了优化

比如0.5的单精度表示为:
0        01111110        00000000000000000000000
其中:

  • 0为符号位
  • 01111110为指数位,十进制为126, 所以实际的exponent为126 - 127 = -1
  • 00000000000000000000000 为fraction,十进制为0

 

所以0.5f =

 

这种方法导致很多浮点数不能精确表示

 

0.1的浮点数表示为:
0       01111011       10011001100110011001101


实际上值为:


0.1 => 0.0001 1001 1001 1001…(无限循环)
0.2 => 0.0011 0011 0011 0011…(无限循环)


双精度浮点数的小数部分最多支持 52 位

 

0.0100110011001100110011001100110011001100110011001100

         (十进制)

0.30000000000000004

0.1 + 0.2 的问题

大整数的精度丢失

尾数位最大是52位,因此 JS 中能精准表示的最大整数是 Math.pow(2, 53),十进制即 9007199254740992。

 

大于 9007199254740992 的可能会丢失精度:

解决方案

toFixed()的问题

IE6-10

chrome/firefox

toFixed()的问题

四舍五入  or  四舍六入五成双:

各版本浏览器的测试结果

https://www.bbsmax.com/A/obzbvK21zE/

当5后有数时,舍5入1;

当5后无有效数字时,需要分两种情况来讲:

①5前为奇数,舍5入1;

②5前为偶数,舍5不进。

9.655.toFixed(2)的过程

1.  f = 2
3.  x = 9.655
5.  s = ''
8.  a.   n = 965
    原因:
         当 n = 965 时 n / 10^f – x = -0.004999999999999005
         当 n = 966 时 n / 10^f – x = 0.005000000000000782

         965更靠近0
     b.   m = '965'
     c.    i. k = 3
            iii. a = '9', b = '65'
            iv. m = a + '.' + b = '9.65'
9.   Return s + m = '9.65'

9.955.toFixed(2)的过程

1.   f = 2
3.   x = 9.955
5.   s = ''
8.  a.     n = 996
     原因:
          当 n = 995 时 n / 10^f – x = -0.005000000000000782
          当 n = 996 时 n / 10^f – x = 0.005000000000000782
          同样靠近0, 选择大的那个
     b.     m = '996'
     c.      i. k = 3
             iii. a = '9', b = '96'
             iv. m = a + '.' + b = '9.96'
9.  Return s + m = '9.96'

 我就是想要0.1 + 0.2 === 0.3 啊!

Math.formatFloat = function(f, digit) { 
    var m = Math.pow(10, digit); 
    return parseInt(f * m, 10) / m; 
} 

var numA = 0.1; 
var numB = 0.2;
alert(Math.formatFloat(numA + numB, 1) === 0.3);    //true

把需要计算的数字乘以 10 的 n 次幂,换算成计算机能够精确识别的整数,然后再除以 10 的 n 次幂

相关库

小学奥数相关

无限循环小数 => 分数 ?

纯循环小数

非纯循环小数

无限循环小数 => 分数 ?

重要!

  • 不要用JavaScript来计算重要的高精度数据

  • 不要用浮点数运算来进行条件判断

  • toFixed() 仅仅用来显示

参考内容

js-precision-loss

By christyma

js-precision-loss

  • 695