跳过正文
Background Image

面试复盘:0.1 + 0.2 === 0.3 结果是 ?附精准解决办法

·391 字·2 分钟
Hypoy
作者
Hypoy
写万行码,行万里路
目录

面试复盘:0.1 + 0.2 === 0.3 结果是 ?附精准解决办法
#

最近面试被问到一个超经典的 JS 问题:0.1 + 0.2 === 0.3 对不对?我马上说肯定不对,但面试官接着追问为啥返回 false,我只答得出 “浮点数精度问题” 这个表面原因,再往下说就卡壳了。今天就把这个问题掰开揉碎复盘,从根上的原理到实际能用的解决办法,一次性讲明白。

一、先看现象:反直觉的结果
#

先执行一段简单的代码,验证这个反直觉的现象:

1
2
console.log(0.1 + 0.2); // 输出 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // 输出 false

明明 0.1 加 0.2 数学上等于 0.3,为什么在 JS 里却不是?核心原因藏在 JavaScript 对数字的存储方式里。

二、底层原因:二进制浮点数的 “先天缺陷”
#

JavaScript 遵循 IEEE 754 标准,所有数字(整数、小数)都以 64 位双精度浮点数(Double-precision floating-point format)存储。这个标准的设计本身,导致了部分十进制小数无法被二进制精确表示。

1. 十进制转二进制的 “无限循环”
#

我们先回忆十进制转二进制小数的规则:乘 2 取整,直到小数部分为 0。以 0.1 为例:

1
2
3
4
5
6
0.1 × 2 = 0.2 → 取整 0
0.2 × 2 = 0.4 → 取整 0
0.4 × 2 = 0.8 → 取整 0
0.8 × 2 = 1.6 → 取整 1,剩余 0.6
0.6 × 2 = 1.2 → 取整 1,剩余 0.2
... 循环往复,永远无法得到 0

最终,0.1 的二进制是 无限循环小数0.0001100110011...(0011 循环)

同理,0.2 的二进制也是无限循环小数。而 64 位双精度浮点数的存储空间有限,只能截取前 52 位有效数字(尾数部分)进行存储,这就导致了 精度丢失

2. 精度丢失后的计算
#

0.1 和 0.2 存储时都被 “近似处理” 了,两者相加后,结果自然不是精确的 0.3,而是一个接近 0.3 的数(0.30000000000000004),因此 === 严格相等判断会返回 false。

简单总结:不是 JS 计算错了,而是十进制的 0.1 和 0.2 无法被二进制浮点数精确表示,存储和计算时的近似值导致了结果偏差。

三、解决方案:如何正确比较 0.1 + 0.2 和 0.3?
#

知道了原因,我们需要对应的解决方案,核心思路是 “规避浮点数精度问题”,常见方案有 3 种:

方案 1:设置误差阈值(最常用)
#

既然结果是近似值,我们可以允许一个极小的误差范围(通常用 Number.EPSILON,表示 JS 中能表示的最小精度差),只要两个数的差值小于这个阈值,就认为相等。

1
2
3
4
5
6
// 封装通用的浮点数比较函数
function isEqual(a, b, epsilon = Number.EPSILON) {
  return Math.abs(a - b) < epsilon;
}
// 测试
console.log(isEqual(0.1 + 0.2, 0.3)); // true

Number.EPSILON 的值约为 2.220446049250313e-16,是 JS 中两个可表示的浮点数之间的最小差值,完全满足日常精度需求。

方案 2:转整数计算(适合金融场景)
#

浮点数精度问题在金额计算中尤为致命(比如 0.1 元 + 0.2 元),此时可以将小数转为整数(乘以 10 的 n 次方),计算后再转回来,从根源避免浮点数运算。

1
2
3
4
5
6
7
8
// 0.1 + 0.2 转整数计算
const a = 0.1, b = 0.2;
const sum = (a * 10 + b * 10) / 10; // (1 + 2)/10 = 0.3
console.log(sum === 0.3); // true

// 金额计算示例:1.23 元 + 4.56 元
const price1 = 1.23, price2 = 4.56;
const total = (price1 * 100 + price2 * 100) / 100; // 579 / 100 = 5.79

方案 3:使用成熟的数学库(推荐复杂场景)
#

如果是企业级金融、科学计算等高精度场景,手动处理容易出错,推荐使用成熟的库:

  • decimal.js:功能全面的高精度十进制运算库
  • big.js:轻量、专注于高精度小数运算
  • bignumber.js:兼容 IEEE 754 标准,适合金融场景

big.js 为例:

1
2
3
4
5
6
7
// 先安装:npm install big.js
const Big = require('big.js');

const a = new Big(0.1);
const b = new Big(0.2);
const sum = a.plus(b); // 0.3
console.log(sum.eq(0.3)); // true

四、面试延伸:常见误区与补充
#

  1. 误区:“只有 0.1 + 0.2 有问题”—— 其实所有无法被二进制精确表示的十进制小数运算都可能有精度问题,比如 0.7 + 0.1 = 0.7999999999999999。
  2. 补充:整数运算为什么没问题?因为十进制整数可以被二进制精确表示(只要不超过 2^53,超过后也会丢失精度)。

五、总结
#

回到面试题,完整的回答应该包含 3 个核心点:

  1. 底层原因:JS 遵循 IEEE 754 标准,用 64 位双精度浮点数存储数字,0.1 和 0.2 的二进制是无限循环小数,存储时精度丢失;
  2. 现象本质:精度丢失后的 0.1 + 0.2 结果是 0.30000000000000004,而非精确的 0.3,因此严格相等判断为 false;
  3. 解决方案:日常场景用误差阈值(Number.EPSILON),金融场景转整数计算或使用 decimal.js/big.js 等库。

这个问题看似简单,实则考察对 JS 数字存储底层的理解,记住 “现象 - 原因 - 解决方案” 的逻辑链,面试时就能从容应对了。