js 数字精确度

事情的起源: 项目中 填写的金额是小数 传给后端需要 *100 9.87 *100 传给后端后是986.9999999999999 后端直接取整 就变成了9.86了

0.1 + 0.2 != 0.3

arduino 复制代码
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?

scss 复制代码
转换成二进制计算:
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位有效数字处理结果

scss 复制代码
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位处理后

scss 复制代码
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

解决方案

  1. 自己手撸
  2. 现成: decimal.js number-precision long.js .....
相关推荐
To_OC9 小时前
从一头雾水到跑通全流程:我用一个周末啃透了JWT登录鉴权
前端·后端·http
excel10 小时前
研究 Vue 3 源码的收获
前端·vue.js
可乐鸡翅yeah_12 小时前
业务中 M3U8 水印相关坑,硬水印和动态水印区别
前端·网络·数据库·ffmpeg·m3u8在线
lerhxx12 小时前
AI 应用如何高效优雅地恢复中断?—— "连接解耦 + 状态持久化"
前端·javascript
计算机魔术师12 小时前
METR 演示 AI 智能体如何篡改 Inspect 评估记录以掩盖不当行为
前端
前端snow13 小时前
ai agent --- 异步处理之 Rabbit MQ
前端
念何架构之路13 小时前
zap扩展生态与总结
java·前端·数据库
独孤九剑打醒他13 小时前
【原创开源】【概念设计】源 - 栅 - 漏 - 栅 - 源 横向双栅 MOS,低压交流多值逻辑芯片探索
前端·其他·架构·开源·硬件工程
mantou13214 小时前
我给 AI Agent 做了个「油猴」:让 Claude Code / Codex 直接用你已登录的浏览器
前端·javascript·后端
默_笙14 小时前
🍕 一个主编、三个工种、两本手册:搭一支 AI 调研队
前端·javascript