js 精度丢失问题

74 阅读2分钟

为什么0.1 +0.2===0.30000000000000004 ?

我们来推导一下0.1

0.1>>>二进制>>>科学记数法>>实际存储时的形式[64位] 符号位+(指数位+指数偏移量)+小数部分) 即:

0.1>>>0.0001100110011001100110011001100110011001100110011001101>>> 1.100110011001100110011001100110011001100110011001101 * 2^(-4)>>>

0011111110111001100110011001100110011001100110011001100110011010

同理,0.2

0.2>>二进制>>>科学记数法>>实际存储时的形式64位

0.2>>>0.001100110011001100110011001100110011001100110011001101>>>1.100110011001100110011001100110011001100110011001101 * 2^(-3)>>>0011111111001001100110011001100110011001100110011001100110011010

可以看出来在转换为二进制时

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

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

就像一些无理数不能有限表示,如 圆周率 3.1415926...,1.3333... 等,在转换为二进制的科学记数法的形式时只保留64位有效的数字,此时只能模仿十进制进行四舍五入了,但是二进制只有 0 和 1 两个,于是变为 0 舍 1 入。在这一步出现了错误,那么一步错步步错,那么在计算机存储小数时也就理所应当的出现了误差。这即是计算机中部分浮点数运算时出现误差,这就是丢失精度的根本原因

将0.1和0.2的二进制形式按实际展开,末尾补零相加,结果如下

0.00011001100110011001100110011001100110011001100110011010

+0.00110011001100110011001100110011001100110011001100110100

=0.01001100110011001100110011001100110011001100110011001110

0.1+0.2 >> 0.0100 1100 1100 1100...(1100无限循环)

则0.1 + 0.2的结果的二进制数科学记数法表示为为1.001100110011001100110011001100110011001100110011010 * 2^(-2), 省略尾数最后的0,即 1.00110011001100110011001100110011001100110011001101 * 2^(-2), 因此(0.1+0.2)实际存储时的形式是 0011111111010011001100110011001100110011001100110011001100110100
因计算机存储位数的限制而截断的二进制数字,再转换为十进制,就成了0.30000000000000004

推导完成

总结:计算机存储双精度浮点数需要先把十进制数转换为二进制的科学记数法的形式,然后计算机以自己的规则{符号位+(指数位+指数偏移量的二进制)+小数部分}存储二进制的科学记数法,因为存储时有位数限制(64位),并且某些十进制的浮点数在转换为二进制数时会出现无限循环,会造成二进制的舍入操作(0舍1入),当再转换为十进制时就造成了计算误差。