Open huihuiha opened 3 years ago
“二、问题分析”的上面 : “0+10000000011+011 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000” 是否应该为: “0+10000000011+1011 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000”, 第二个“+”后面少了一个“1”。
在 JavaScript 中,由于采用的是双精度浮点数表示数字,会导致在进行浮点数运算时出现精度丢失的问题。这是由于浮点数的二进制表示方式无法精确地表示所有的十进制小数。
为了解决 JavaScript 数字精度丢失的问题,可以采取以下几种方法:
使用整数进行计算:将需要进行精确计算的数字转换为整数进行计算,最后再将结果转换为需要的小数形式。
使用第三方库:可以使用第三方库如 BigNumber.js 或 decimal.js 等来处理精确计算,这些库提供了更高精度的数字计算功能。
四舍五入:在进行浮点数运算后,可以使用 JavaScript 中的 toFixed() 方法对结果进行四舍五入,保留指定位数的小数。
使用 Math 库中的函数:Math 库中提供了一些函数如 Math.round()、Math.floor()、Math.ceil() 等可以帮助处理精度问题。
不只是js,很多语言也是用了iee754的规范,都会存在这个问题
一、场景复现
一个经典的面试题
为什么是
false
呢?先看下面这个比喻
比如一个数 1÷3=0.33333333......
3会一直无限循环,数学可以表示,但是计算机要存储,方便下次取出来再使用,但0.333333...... 这个数无限循环,再大的内存它也存不下,所以不能存储一个相对于数学来说的值,只能存储一个近似值,当计算机存储后再取出时就会出现精度丢失问题
二、浮点数
“浮点数”是一种表示数字的标准,整数也可以用浮点数的格式来存储
我们也可以理解成,浮点数就是小数
在
JavaScript
中,现在主流的数值类型是Number
,而Number
采用的是IEEE754
规范中64位双精度浮点数编码这样的存储结构优点是可以归一化处理整数和小数,节省存储空间
对于一个整数,可以很轻易转化成十进制或者二进制。但是对于一个浮点数来说,因为小数点的存在,小数点的位置不是固定的。解决思路就是使用科学计数法,这样小数点位置就固定了
而计算机只能用二进制(0或1)表示,二进制转换为科学记数法的公式如下:
其中,
a
的值为0或者1,e为小数点移动的位置举个例子:
27.0转化成二进制为11011.0 ,科学计数法表示为:
前面讲到,
javaScript
存储方式是双精度浮点数,其长度为8个字节,即64位比特64位比特又可分为三个部分:
如下图所示:
举个例子:
27.5 转换为二进制11011.1
11011.1转换为科学记数法
符号位为1(正数),指数位为4+,1023+4,即1027
因为它是十进制的需要转换为二进制,即
10000000011
,小数部分为10111
,补够52位即: 1011 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000`所以27.5存储为计算机的二进制标准形式(符号位+指数位+小数部分 (阶数)),既下面所示
0+10000000011+011 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000`
二、问题分析
再回到问题上
通过上面的学习,我们知道,在
javascript
语言中,0.1 和 0.2 都转化成二进制后再进行运算所以输出
false
再来一个问题,那么为什么
x=0.1
得到0.1
?主要是存储二进制时小数点的偏移量最大为52位,最多可以表达的位数是
2^53=9007199254740992
,对应科学计数尾数是9.007199254740992
,这也是 JS 最多能表示的精度它的长度是 16,所以可以使用
toPrecision(16)
来做精度运算,超过的精度会自动做凑整处理但看到的
0.1
实际上并不是0.1
。不信你可用更高的精度试试:如果整数大于
9007199254740992
会出现什么情况呢?由于指数位最大值是1023,所以最大可以表示的整数是
2^1024 - 1
,这就是能表示的最大整数。但你并不能这样计算这个数字,因为从2^1024
开始就变成了Infinity
那么对于
(2^53, 2^63)
之间的数会出现什么情况呢?(2^53, 2^54)
之间的数会两个选一个,只能精确表示偶数(2^54, 2^55)
之间的数会四个选一个,只能精确表示4个倍数要想解决大数的问题你可以引用第三方库
bignumber.js
,原理是把所有数字当作字符串,重新实现了计算逻辑,缺点是性能比原生差很多小结
计算机存储双精度浮点数需要先把十进制数转换为二进制的科学记数法的形式,然后计算机以自己的规则{符号位+(指数位+指数偏移量的二进制)+小数部分}存储二进制的科学记数法
因为存储时有位数限制(64位),并且某些十进制的浮点数在转换为二进制数时会出现无限循环,会造成二进制的舍入操作(0舍1入),当再转换为十进制时就造成了计算误差
三、解决方案
理论上用有限的空间来存储无限的小数是不可能保证精确的,但我们可以处理一下得到我们期望的结果
当你拿到
1.4000000000000001
这样的数据要展示时,建议使用toPrecision
凑整并parseFloat
转成数字后再显示,如下:封装成方法就是:
对于运算类操作,如
+-*/
,就不能使用toPrecision
了。正确的做法是把小数转成整数后再运算。以加法为例:最后还可以使用第三方库,如
Math.js
、BigDecimal.js
参考文献