腾讯面试:都知道 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 的精确存储值。
我们可以用代码验证:
看到了吗?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≠0.3”。
三、面试标准答案模板:3 步说清,不慌不忙
遇到这道题,别只说 “二进制无法精确存储”,要按 “原理→案例→总结” 的逻辑说,体现你的深度:
核心原因:IEEE 754 双精度存储的 “近似性”
计算机用 IEEE 754 双精度存储小数时,会先把十进制转二进制。但像 0.1、0.2、0.3 这类小数,二进制是无限循环的,而尾数位只有 52 位,必须舍入,所以存储的是 “近似值”,不是数学上的精确值。加法结果是否相等,本质是 “两个近似值相加后,是否等于目标值的近似存储值”。
分案例拆解:为什么结果不同?
0.1+0.1=0.2:两个 0.1 的近似值相加后,得到的二进制值,刚好和 0.2 的近似存储值完全一致(舍入误差抵消),所以计算机判定相等;
0.1+0.2≠0.3:两个近似值相加后的二进制值,和 0.3 的近似存储值有微小差异(舍入误差叠加),所以判定不相等。可以用 BigDecimal 打印实际值来验证这个差异。
延伸总结:工程中怎么避免这个问题?
实际开发中,如果需要精确计算(比如金额),不能用 double/float,要改用:
Java 用 BigDecimal(注意用 String 构造,别用 double 构造);
或者把小数转成整数计算(比如金额乘 100 转成分,计算完再转回来)。
其实这道题考的不是 “记结论”,而是 “是否理解浮点数的存储本质”。只要把 “无限循环→尾数位舍入→近似值相加” 的逻辑说透,再结合代码验证,面试官就会觉得你不仅 “知其然”,更 “知其所以然”。
你之前面试遇到过类似的 “反常识” 问题吗?评论区聊聊~
觉得有用的兄弟,点个赞,收藏起来,万一下次面试就用上了呢!
想了解更多高频面试题,欢迎关注公众号【Fox 爱分享】,领取大厂高频面试真题。
版权声明: 本文为 InfoQ 作者【Fox爱分享】的原创文章。
原文链接:【http://xie.infoq.cn/article/f2b7003dd01aea439215e224f】。文章转载请联系作者。







评论