0.1 + 0.2 为什么不等于 0.3?答不上来的都挂了
这个问题你可能在面试、线上 Bug、甚至随手写 Demo 的时候都见过:
javascript
console.log(0.1 + 0.2 === 0.3); // false
很多人第一反应是"浮点数精度问题",但如果继续追问:
- 为什么偏偏是
0.1、0.2这种小数出问题? - "精度"到底精在哪一位、丢在哪一步?
- 实际开发里应该怎么比较、怎么计算才稳?
这篇文章按"现象 → 原因 → 解决 → 面试回答"的顺序,把它讲透。
先看现象
javascript
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.7); // 0.7999999999999999
console.log(0.3 - 0.2); // 0.09999999999999998
console.log(0.1 * 3); // 0.30000000000000004
但有些计算又是对的:
javascript
console.log(0.5 + 0.5); // 1
console.log(0.25 + 0.25); // 0.5
console.log(0.125 * 8); // 1
为什么?
根本原因:二进制无法精确表示某些十进制小数
计算机用二进制存储数字。十进制的 0.5 在二进制里是 0.1,能精确表示。但十进制的 0.1 在二进制里是:
scss
0.0001100110011001100110011001100110011... (无限循环)
就像十进制里 1/3 = 0.333... 无限循环一样,0.1 在二进制里也是无限循环的。
但计算机内存有限,不能存无限长的数字,必须在某个位置截断。JavaScript 用的是 IEEE 754 双精度浮点数,只有 64 位,其中 52 位用来存小数部分。
截断就意味着误差。
哪些数能精确表示?
能被 2 的幂次整除的小数,在二进制里都能精确表示:
javascript
// 这些都是精确的
0.5 = 1/2 = 0.1 (二进制)
0.25 = 1/4 = 0.01 (二进制)
0.125 = 1/8 = 0.001 (二进制)
0.0625 = 1/16 = 0.0001 (二进制)
而 0.1 = 1/10,10 = 2 × 5,有因子 5,所以在二进制里是无限循环。
规律:分母只包含因子 2 的分数,在二进制里能精确表示。
怎么解决?
方案一:容差比较(推荐)
既然有误差,那就别用 === 比较,改用"误差小于某个值":
javascript
function isEqual(a, b) {
return Math.abs(a - b) < Number.EPSILON;
}
console.log(isEqual(0.1 + 0.2, 0.3)); // true
Number.EPSILON 是 JavaScript 里最小的可表示精度差,约等于 2.22e-16。
方案二:转成整数算
小数不精确,整数是精确的。把小数转成整数,算完再转回来:
javascript
// 0.1 + 0.2
const result = (0.1 * 10 + 0.2 * 10) / 10; // 0.3
// 封装一下
function add(a, b) {
const precision = Math.max(
(a.toString().split('.')[1] || '').length,
(b.toString().split('.')[1] || '').length
);
const factor = Math.pow(10, precision);
return (Math.round(a * factor) + Math.round(b * factor)) / factor;
}
console.log(add(0.1, 0.2)); // 0.3
方案三:toFixed 四舍五入
简单粗暴,直接四舍五入:
javascript
const result = parseFloat((0.1 + 0.2).toFixed(10));
console.log(result); // 0.3
注意 toFixed 返回的是字符串,要用 parseFloat 转回数字。
方案四:用专门的库
金融计算这种对精度要求高的场景,用专门的库:
javascript
// decimal.js
import Decimal from 'decimal.js';
const a = new Decimal(0.1);
const b = new Decimal(0.2);
console.log(a.plus(b).toString()); // "0.3"
实际开发中怎么选?
| 场景 | 推荐方案 |
|---|---|
| 普通比较 | 容差比较 |
| 价格计算 | 转成整数(分)或用 decimal.js |
| 显示用途 | toFixed 四舍五入 |
| 科学计算 | 用专门的数值计算库 |
面试怎么答?
JavaScript 用 IEEE 754 双精度浮点数存储数字。十进制的 0.1 在二进制里是无限循环小数,但只有 52 位存储空间,必须截断,所以有精度损失。0.1 和 0.2 存储时都有微小误差,加起来误差累积,结果就不等于 0.3 了。
解决方案有几种:比较时用容差比较、计算时转成整数、或者用 decimal.js 这样的高精度库。
如果面试官继续问"IEEE 754 是什么",可以补充:
IEEE 754 是浮点数的存储标准,用 64 位存一个数字:1 位符号位、11 位指数位、52 位尾数位。这个标准是为了让不同计算机上的浮点运算结果一致。
一道相关的坑题
javascript
console.log(0.1 + 0.2 - 0.3); // ?
答案不是 0,而是 5.551115123125783e-17。
再来一道:
javascript
console.log(9999999999999999); // ?
答案是 10000000000000000。因为超出了安全整数范围(2^53 - 1),精度丢失了。
如果你觉得这篇文章有帮助,欢迎关注我的 GitHub,下面是我的一些开源项目:
Claude Code Skills (按需加载,意图自动识别,不浪费 token,介绍文章):
- code-review-skill - 代码审查技能,覆盖 React 19、Vue 3、TypeScript、Rust 等约 9000 行规则(详细介绍)
- 5-whys-skill - 5 Whys 根因分析,说"找根因"自动激活
- first-principles-skill - 第一性原理思考,适合架构设计和技术选型
全栈项目(适合学习现代技术栈):
- prompt-vault - Prompt 管理器,用的都是最新的技术栈,适合用来学习了解最新的前端全栈开发范式:Next.js 15 + React 19 + tRPC 11 + Supabase 全栈示例,clone 下来配个免费 Supabase 就能跑
- chat_edit - 双模式 AI 应用(聊天+富文本编辑),Vue 3.5 + TypeScript + Vite 5 + Quill 2.0 + IndexedDB