事情的起源: 项目中 填写的赔付金额是小数 传给后端需要 *100 9.87 *100 传给后端后是986.9999999999999 后端直接取整 就变成了9.86了
0.1 + 0.2 != 0.3
console.log(0.1 + 0.2) //0.30000000000000004 console.log(0.1 + 0.2 == 0.3) //false
1. 数字的存储
浮点数是用二进制的科学计算法来表示的,在计算机上是以二进制来进行存储的,单精度浮点数占用32位,双精度浮点数占用64位。
最高位是符号位(sign) , 0 表示正数, 1表示负数。接下来的11存储的是指数(exponent) , 最后是52位存储的是小数(mantissa)。浮点数的值可以用下面这个式子算出,类似于十进制的科学计数法。
注意以上的公式遵循科学计数法的规范,在十进制中 0<M<10,到二进制就是 0<M<2。也就是说整数部分只能是1,所以可以被舍去,只保留后面的小数部分。如 4.5 转成二进制就是 100.1,科学计数法表示是 1.001*2^2,舍去1后 M = 001。E是一个无符号整数,因为长度是11位,取值范围是 0~2047。但是科学计数法中的指数是可以为负数的,所以约定减去一个中间数 1023,[0,1022] 表示为负,[1024,2047] 表示为正。如 4.5 的指数 E = 1025,尾数 M = 001。
以 0.1
为例解释浮点误差的原因,0.1
转成二进制表示为 0.0001100110011001100
(1100循环),1.100110011001100x2^-4
,所以 E=-4+1023=1019
;M 舍去首位的1,得到 100110011...
在计算机中的存储为:
2. 0.1+0.2=0.30000000000000004?
转换成二进制计算: 0.00011001100110011001100110011001100110011001100110011010 + 0.0011001100110011001100110011001100110011001100110011010 = 0.0100110011001100110011001100110011001100110011001100111 // 十进制小数转二进制 小数部分*2 取整数 // 二进制小数转换成十进制 1*2^(-小数点后第几位)+1*2^(-小数点后第几位)....
9.87*100= 986.9999999999999
9.87 = 1001.110111101011100001010001111010111000010100011111 = 1.001110111101011100001010001111010111000010100011111 * 2^3
S = 0 E = 1026 M = 0011 1011 1101 0111 0000 1010 0011 1101 0111 0000 1010 0011 111
为什么x=0.1能得到0.1
二进制转换十进制的时候 小数的精度为2^(-52) ,即2.220446049e-16
所以数字转换成十进制的时候,JavaScript能表示的精度最多能精确到小数点后第16位,会把小数点后第17位进行凑整处理
0.1~0.9 21位有效数字处理结果
0.1.toPrecision(21) // 0.100000000000000005551 0.2.toPrecision(21) // 0.200000000000000011102 0.3.toPrecision(21) // 0.299999999999999988898 0.4.toPrecision(21) // 0.400000000000000022204 0.5.toPrecision(21) // 0.500000000000000000000 0.6.toPrecision(21) // 0.599999999999999977796 0.7.toPrecision(21) // 0.699999999999999955591 0.8.toPrecision(21) // 0.800000000000000044409 0.9.toPrecision(21) // 0.900000000000000022204
小数位16位处理后
0.1.toPrecision(16) // 0.1000000000000000 0.2.toPrecision(16) // 0.2000000000000000 0.3.toPrecision(16) // 0.3000000000000000 0.4.toPrecision(16) // 0.4000000000000000 0.5.toPrecision(16) // 0.5000000000000000 0.6.toPrecision(16) // 0.6000000000000000 0.7.toPrecision(16) // 0.7000000000000000 0.8.toPrecision(16) // 0.8000000000000000 0.9.toPrecision(16) // 0.9000000000000000
解决方案
- 自己手撸
- 现成: decimal.js number-precision long.js .....