写点什么

腾讯面试:都知道 0.1+0.2≠0.3,为啥 0.1+0.1 却等于 0.2?

作者:Fox爱分享
  • 2025-10-30
    湖南
  • 本文字数:2609 字

    阅读完需:约 9 分钟

腾讯面试:都知道0.1+0.2≠0.3,为啥 0.1+0.1 却等于 0.2?

“你知道 0.1+0.2 不等于 0.3 吧?那我再问一个:为什么 0.1+0.1 又等于 0.2?”

面试现场,当面试官抛出这个问题时,小林心里咯噔一下 —— 他背过 “0.1 二进制无法精确存储” 的结论,却从没琢磨过 “两个不精确的 0.1 相加,为啥能得到精确的 0.2”。支支吾吾半天没说清,这场面试最终以 “再联系” 收尾。

Java 中测试效果如下:



其实这道题的核心,不是 “记结论”,而是搞懂 IEEE 754 双精度浮点数的存储规则—— 所有 “看似矛盾” 的加法结果,都藏在 “符号位、指数位、尾数位” 这三部分里。

一、先搞懂底层:计算机怎么存 0.1?(IEEE 754 双精度规则)

我们平时用的 “十进制小数”,计算机要先转成 “二进制小数”,再按 IEEE 754 双精度(Java 的 double、Python 的 float 默认类型)规则存储。双精度共 64 位,分三部分:

  • 符号位(1 位):0 正 1 负,0.1 是正数,所以符号位为 0;

  • 指数位(11 位):用 “偏移值 1023” 表示指数,比如指数是 - 4,实际存储的是 1023-4=1019;

  • 尾数位(52 位):存储二进制小数的 “有效数字”,但有个关键规则 —— 二进制小数会先转成 “1.xxxxxx×2^e” 的科学计数法(类似十进制的 1.23×10^2),而 “1.” 是默认不存的,只存后面的 “xxxxxx”(52 位,不够补 0,超了舍入)。

那 0.1 转二进制是多少?答案是无限循环小数

0.1₁₀ = 0.00011001100110011...₂(“0011” 无限循环)

按科学计数法转成 “1.xxxx×2^e”:

0.000110011...₂ = 1.100110011...₂ × 2^(-4)

这时候对应到 IEEE 754 存储:

  • 指数位:-4 + 1023 = 1019(二进制是 01111111011);

  • 尾数位:取 “1.xxxx” 后面的 52 位(因为 “1.” 不存),但 “0011” 无限循环,所以第 52 位会触发 “舍入”(类似十进制四舍五入),最终尾数位是 “100110011...001101”(中间省略部分是循环的 0011,最后一位因舍入变 1)。

重点来了:0.1 的二进制是无限循环的,所以计算机存储的 0.1,其实是 “舍入后的近似值”,不是数学上精确的 0.1

二、拆解两个加法:为啥结果天差地别?

知道了 “0.1 是近似值”,再看两个加法的区别 —— 核心是 “两个近似值相加后,结果是否能被 IEEE 754 精确存储”。

案例 1:0.1 + 0.1 = 0.2(结果精确)

先看 0.2 的二进制和存储:

0.2₁₀ = 0.001100110011...₂(同样 “0011” 无限循环)

转科学计数法:0.00110011...₂ = 1.100110011...₂ × 2^(-3)

再看两个 “近似 0.1” 相加的过程(二进制):

两个 0.1 的二进制近似值相加 → 0.000110011...₂ + 0.000110011...₂ = 0.00110011...₂

而这个 “相加结果”,刚好就是 0.2 的二进制(无限循环的 0.00110011...₂)。更关键的是:

当这个结果按 IEEE 754 存储时,它的 “1.xxxx×2^e” 形式中,“xxxx” 部分的循环长度,刚好能被 52 位尾数位 “完整容纳 + 舍入后无误差”—— 简单说,两个近似的 0.1 相加后,误差刚好抵消,结果刚好是 0.2 的精确存储值

我们可以用代码验证:

// Java中打印0.1的实际存储值(不是数学上的0.1)System.out.println(new BigDecimal(0.1)); // 输出:0.1000000000000000055511151231257827021181583404541015625
// 打印0.1+0.1的实际值System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.1))); // 输出:0.200000000000000011102230246251565404236316680908203125
// 打印0.2的实际存储值System.out.println(new BigDecimal(0.2)); // 输出:0.200000000000000011102230246251565404236316680908203125
复制代码



看到了吗?0.1+0.1 的结果,和 0.2 的存储值完全一样 —— 所以计算机判定 “0.1+0.1=0.2”。


案例 2:0.1 + 0.2 ≠ 0.3(结果不精确)

同样先看 0.3 的二进制:

0.3₁₀ = 0.01001100110011...₂(还是 “0011” 无限循环)

转科学计数法:0.010011...₂ = 1.00110011...₂ × 2^(-2)

再看 0.1+0.2 的二进制相加过程:

0.1 的近似二进制(0.000110011...₂) + 0.2 的近似二进制(0.00110011...₂) = 0.0100110011...₂

这个相加结果,虽然数学上等于 0.3,但按 IEEE 754 存储时出了问题:

它的 “1.xxxx×2^e” 形式中,“xxxx” 部分的循环长度,在 52 位尾数位中 “无法完整容纳”,舍入后的结果,和 “数学上 0.3 的二进制” 舍入后的存储值,差了一点点 —— 简单说,这次相加的误差没有抵消,反而产生了新的误差,导致结果不等于 0.3 的存储值

再用代码验证:

// 打印0.1+0.2的实际值System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2))); // 输出:0.3000000000000000444089209850062616169452667236328125
// 打印0.3的实际存储值System.out.println(new BigDecimal(0.3)); // 输出:0.299999999999999988897769753748434595763683319091796875
复制代码



很明显,两个值不一样 —— 所以计算机判定 “0.1+0.2≠0.3”。

三、面试标准答案模板:3 步说清,不慌不忙

遇到这道题,别只说 “二进制无法精确存储”,要按 “原理→案例→总结” 的逻辑说,体现你的深度:

  1. 核心原因:IEEE 754 双精度存储的 “近似性”

计算机用 IEEE 754 双精度存储小数时,会先把十进制转二进制。但像 0.1、0.2、0.3 这类小数,二进制是无限循环的,而尾数位只有 52 位,必须舍入,所以存储的是 “近似值”,不是数学上的精确值。加法结果是否相等,本质是 “两个近似值相加后,是否等于目标值的近似存储值”。

  1. 分案例拆解:为什么结果不同?

  • 0.1+0.1=0.2:两个 0.1 的近似值相加后,得到的二进制值,刚好和 0.2 的近似存储值完全一致(舍入误差抵消),所以计算机判定相等;

  • 0.1+0.2≠0.3:两个近似值相加后的二进制值,和 0.3 的近似存储值有微小差异(舍入误差叠加),所以判定不相等。可以用 BigDecimal 打印实际值来验证这个差异。

  1. 延伸总结:工程中怎么避免这个问题?

实际开发中,如果需要精确计算(比如金额),不能用 double/float,要改用:

  • Java 用 BigDecimal(注意用 String 构造,别用 double 构造);

  • 或者把小数转成整数计算(比如金额乘 100 转成分,计算完再转回来)。



其实这道题考的不是 “记结论”,而是 “是否理解浮点数的存储本质”。只要把 “无限循环→尾数位舍入→近似值相加” 的逻辑说透,再结合代码验证,面试官就会觉得你不仅 “知其然”,更 “知其所以然”。

你之前面试遇到过类似的 “反常识” 问题吗?评论区聊聊~

觉得有用的兄弟,点个赞,收藏起来,万一下次面试就用上了呢!

想了解更多高频面试题,欢迎关注公众号【Fox 爱分享】,领取大厂高频面试真题。

发布于: 刚刚阅读数: 3
用户头像

Fox爱分享

关注

更多技术干货,欢迎关注公众号:Fox爱分享 2019-03-12 加入

多年中间件,高并发经验,擅长高并发,中间件,微服务架构,喜欢分享技术干货

评论

发布
暂无评论
腾讯面试:都知道0.1+0.2≠0.3,为啥 0.1+0.1 却等于 0.2?_程序员_Fox爱分享_InfoQ写作社区