JS中数字(精度缺失)存储问题

1,644 阅读4分钟

在 js 中的小数运算中,一直存在着一个问题,
比如:0.1+0.2=0.30000000000000004 、0.4-0.3=0.10000000000000003。
那么为什么会出现这种情况呢?  这种情况又如何解决呢?  为什么?
这就要讲到 js 小数的存储。

一、计算机存储数字的方法 

其他语言(如 C 和 Java):整数法浮点法

JavaScript: 不区分整数法和浮点法,所有数字均用浮点法,在js中的数字都是以双精度64位的浮点数存储;

二、什么是浮点数

浮点法存放的数字,叫做浮点数(float),浮点数不是精确。涉及到小数的计算需要特别注意,浮点数分位单精度(float)双精度(double),在JS中,使用双精度存放浮点数;

三、单精度(float)和双精度(double)区别

单精度和双精度在计算机上的存储都遵循IEEE规范,使用二进制科学计数法,都包含三个部分:符号位,指数位和尾数部分。

单精度32位的浮点数存储形式:


双精度64位的浮点数存储形式:


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

指数位 E:中间的是存储指数(exponent),用来表示次方数;

尾数位 F:最后的是有效数字(fraction),超出的部分自动进一舍零;

区别:

(1)在内存中占有的字节数不同

单精度浮点数在计算机中存储占4个字节 (32位),包括符号位1位,指数位8位,尾数位23位。

双精度浮点数在计算机中存储占8个字节(64位),包括符号位1位,指数位11位,尾数位52位

(2)有效数字位数不同

•单精度浮点数有效数字8位

•双精度浮点数有效数字16位

(3)所能表示数的范围不同(E是指数的意思,E表示10的多少次方,如3.4E38指的是3.4乘以10的38次方。)

•单精度浮点的表示范围:-2^128~2^128,即-3.40E+38~ .40E+38。

•双精度浮点的表示范围:-2^1024~2^1024,即-1.79E^308~1.79E^308。


(4)在程序中处理速度不同

一般来说,CPU处理单精度浮点数的速度比处理双精度浮点数快

最本质的区别:存储位不同,表示的数值的范围不同,能准确表示的数的位数就不同,所以单精度和双精度精确的范围就不一样。

四、js在计算机上是如何存储数字的

首先我们需要了解整数和小数是如何转成二进制的

现实世界中: 十进制,10个数字,缝十进一

计算机世界中:二进制,2个数字,缝二进一

整数转成二进制

算法:"除2取余,逆序排列"。

例如:10转成二进制:
10 / 2 = 5 --> 余数0
5 / 2 = 2 --> 余数1
2 / 2 = 1 --> 余数0
1 / 2 = 0 --> 余数1  
然后逆序排列就是:1010,所以对于10如果用一个字节也就是8位来存储就是:0000 1010

小数转成二进制

算法:"乘2取整,顺序排列"。

例如:0.1转成二进制:
0.1 * 2 = 0.2 --> 取整0
0.2 * 2 = 0.4 --> 取整0
0.4 * 2 = 0.8 --> 取整0
0.8 * 2 = 1.6 --> 取整1
0.6 * 2 = 1.2 --> 取整1
0.2 * 2 = 0.4 --> 取整0
0.4 * 2 = 0.8 --> 取整0
0.8 * 2 = 1.6 --> 取整1
...........  
然后顺序排列就是:0.00011001100110011001100110011001100110011001100110011001100110011001.....(这里就是说明小数如何转化成的二进制,计算机存储浮点数时候不是这样存储的)

十进制的小数,转换位二进制后,可能是无限小数,但是计算机对数字存储能力有限,因此会丢失一些数据,所以导致了js小数运算不精确。

五、js安全问题

JS 的最大和最小安全整数值

我们知道js是使用双精度(64位)来存储数字的,在浮点数64位中存储数字的位数是52位,那么可以表示最大的数范围就是(-)1111111111 1111111111 1111111111 1111111111 1111111111 11 ~ (+)1111111111 1111111111 1111111111 1111111111 1111111111 11 = -2^53 ~ 2^53 = (-)Math.pow(2, 53) ~ (+)Math.pow(2, 53) = -9007199254740992 ~ +9007199254740992;

最大值:Number.MAX_SAFE_INTEGER // 9007199254740991 ;

最小值:Number.MIN_SAFE_INTEGER) //-9007199254740991

这里就存在安全性问题,如果浮点数超出这个值范围之后,那么数字值将不再准确,所表达的数就是一个。举个例子:

Math.pow(2, 53) === Math.pow(2, 53) + 1.0 // true

六、解决方案

1.手写方案通常通过转化整数进行计算后还原。

2.第三方库有math.jsdecimal.js、bignumbigint等。

3.ES规范中新提出的BigInt。