第2期 JS浮点数误差问题

175 阅读3分钟

Javascript跟Java等不同,对于数字没有区分类型,只有一种Number类型,即64位固定长度表示,也就是双精度浮点数。

用科学计数法来表示。

符号位S

第 1 位是正负数符号位(sign),0代表正数,1代表负数

指数位E

中间的 11 位存储指数(exponent),用来表示次方数,E是一个无符号整数,因为长度是11位,取值范围是0~2047。但是科学计数法中的指数是可以为负数的,所以再减去一个中间数 1023,[0,1022]表示为负,[1024,2047] 表示为正。此处没有使用符号位来区分指数的正负而是用范围来表示,等同于原本是可以存储11位,现在只能存储10位

尾数位M

最后的 52 位是尾数(mantissa),超出的部分自动进一舍零。但是其实尾数最大的表示数是2^53 因为尾数在二进制中第一位不为0,所以省略这一位,可以多展示一位,但是指数位最大也就是52位,即尾数位右移52位,所以当超过最大整数9007199254740992的时候,+1 还是等于9007199254740992,+2才等于9007199254740994。

最大安全整数

能够安全使用,进行算数运算的范围

2^53+1的存储和2^53一样。这样就不再“一一对应”,所以就不是安全整数。所以最大安全整数是2^53。

2^53:

符号位:0 指数:53 尾数:1.000000...000 (小数点后一共52个0)

2^53+1:

符号位:0 指数:53 尾数:1.000000...000(小数点后一共52个0),原本加1是1.000000...0001 (小数点后一共52个0,1个1,但是只能存52位,所以1没有存储,就会出现误差)。

所以能够精准表示的最大整数是 2^53 = 9007199254740992 指数位是53 尾数位是52个0 即尾数位右移53位得到9007199254740992。

但是9007199254740992+1 就会得到不准确的值。

浮点数

在十进制中会存在例如圆周率、无理数之类的小数,无法用有限的位数表示,就会进行四舍五入,也会造成不够精准,在二进制中也存在类似的数,在进行二进制表达的时候无法精准表达。

例如:

0.1 >> 0.0001 1001 1001 1001…(1001无限循环)

0.2 >> 0.0011 0011 0011 0011…(0011无限循环)

那么对于 (2^53, 2^63) 之间的数会出现什么情况呢?

(2^53, 2^54) 之间的数会两个选一个,只能精确表示偶数 (2^54, 2^55) 之间的数会四个选一个,只能精确表示4个倍数 ... 依次跳过更多2的倍数

toPrecision vs toFixed

toPrecision 是处理精度,精度是从左至右第一个不为0的数开始数起。

toFixed 是小数点后指定位数取整,从小数点开始数起。

相关插件

github.com/nefe/number…

参考

github.com/camsong/blo…

www.zhihu.com/question/29…