浮点数精度问题
1. 核心原因
JavaScript 中的数字类型(Number)遵循 IEEE 754 标准,使用 双精度 64 位浮点数 存储。
计算机内部使用 二进制 表示数字:
- 0.1 (十进制) -> 0.0001100110011001100... (二进制)
- 0.2 (十进制) -> 0.0011001100110011001100... (二进制)
这两个数字在二进制中都是 无限循环小数。由于存储位数是有限的(52 位尾数),必须进行截断,导致精度丢失。
相加后再转换回十进制,结果是一个极为接近 0.3 但不等于 0.3 的数:
0.30000000000000004
可借助该工具加深理解: https://devtool.tech/double-type
2. 如何解决?
(1) 使用 Number.EPSILON (比较)
Number.EPSILON 是 ES6 引入的一个极小常量,表示 1 与大于 1 的最小浮点数之间的差值。可以用来判断两个浮点数是否足够接近。
(2) 转换为整数计算
先将小数放大为整数(乘以 10^n),运算后再缩小回去。
(3) 使用第三方库
如果涉及金额计算,强烈建议不要手动计算,而是使用经过严格测试的库:
- decimal.js
- bignumber.js
原理:
这类库并不是让 JavaScript 的 Number 变得更精确,而是绕开二进制浮点数,自己实现一套十进制高精度计算。
通常会把一个十进制数拆成几部分保存:
内部可以抽象成:
也就是把 123.45 存成 12345 * 10^-2。计算时不直接用二进制小数,而是围绕整数有效数字和十进制指数做运算。
以 0.1 + 0.2 为例:
如果小数位不同,先对齐指数:
乘法则更直接:有效数字相乘,指数相加。
除法无法总是得到有限小数,所以库会提供精度配置和舍入模式,比如保留多少位有效数字、四舍五入、向上取整、向下取整等。
推荐传入字符串而不是 Number。因为 0.1 在进入库之前已经是一个带误差的二进制浮点数了;传字符串可以让库从准确的十进制文本开始解析。
(4) 使用 toFixed
如果只是为了页面展示,可以直接使用 toFixed 保留小数位。注意它返回的是字符串。
3. 大数危机 (BigInt)
除了小数精度,还有一个大数精度问题。也就是超过 Number.MAX_SAFE_INTEGER (2^53 - 1) 的整数会丢失精度。
解决方案:使用 BigInt (ES2020)。
4. 面试总结
- 根本原因:IEEE 754 双精度浮点数存储导致的无限循环小数截断误差。
- 解决方案:如果是比较,用
EPSILON;如果是计算,转整数处理或用库;如果是展示,用toFixed。

