为什么0.1累加100次结果不是10?浮点数精度问题解析
你是否好奇过,为什么在编程中将0.1累加100次,得到的结果竟然不是10?这背后的根源与计算机处理小数的机制密切相关。接下来,我们一起来深入剖析这一现象。
一个经典的运算错误示例
我们来看一个典型的计算机运算错误示例。
function sum(){
let sum = 0;
for(let i=1;i<=100;i++){
sum +=0.1;
}
console.log(sum)
}
在浏览器的控制台中运行sum(),得到的结果是9.99999999999998。这显然与我们九年义务教育所学的知识 「背道而驰」。
有句话说得好: 「雪崩的时候,没有一片雪花是无辜的」。在这段代码中,程序没有错,计算机也没有故障,当然也和所用语言没有直接关系(选用其他高级语言可能会得到不同的运算结果)。如果非要找出一个“背锅侠”,那就是 「计算机处理小数的机制」。
小提示: 这个结果并非Ja vaScript特有的bug,几乎所有采用二进制浮点数的编程语言(如Ja va、C++、Python等)都会遇到类似问题。只是不同语言对小数精度的处理方式略有差异,因此显示结果可能略有不同。
用二进制数表示小数
在计算机底层知识之二进制中我们讲过,由于计算机内部所有信息都以二进制数形式处理,因此 「整数和小数并无差别」。
在说明计算机如何用二进制数表示小数的具体方法之前,我们先尝试将1011.0011这个带小数点的二进制数转换为十进制数。
小数点 「前面」 部分的转换方法在计算机底层知识之二进制中已经介绍过:只需将各 「数位」 数值与 「位权」 相乘,再将相乘的结果相加即可。其实,对于小数点后面的部分,也是 「照猫画虎」,同样是将各 「数位」 数值与 「位权」 相乘的结果相加。

二进制数小数点前面部分的「位权」
- 第一位是
2的0次幂 - 第二位是
2的1次幂 - 第三位是
2的2次幂 - 以此类推
而小数点后面部分的「位权」
- 第一位是
2的-1次幂 - 第二位是
2的-2次幂 - 第三位是
2的-3次幂 - 以此类推
❝0次幂前面的位的位权按照
1次幂、2次幂····的方式「递增」0次幂后面的位的位权按照
-1次幂、-2次幂····的方式「递减」❞
小提示: 二进制小数位的位权是2的负整数次幂,因此只有分母为2的幂次(如1/2、1/4、1/8等)的小数才能被有限位二进制精确表示。像0.1(即1/10)这样分母不是2的幂次的数,在二进制中会变成无限循环小数。
计算机运算出错的原因
❝计算机运算出错的原因:「有一些十进制数的小数无法被精确转换为二进制」
❞
小数点后4位用二进制数表示时的数值范围为0.0000~0.1111。这里只能表示由0.5、0.24、0.125、0.0625这四个二进制数小数点后面的位权组合而成(相加总和)的小数。

❝可以看出:「二进制数是连续的,十进制数是非连续的」
❞
在前面讲二进制的时候,我们提到:根据IC引脚个数不同,我们可以表示不同位数的二进制数。通过增加引脚数(即增加二进制小数点后面的位数),与之对应的十进制数的个数也会增加, 「但是无论增加多少位,2的-〇〇次幂相加都无法精确得到0.1这个结果」。
实际上,十进制数0.1转换成二进制后,会变成0.00011001100···(1100循环)这样的 「循环小数」。这和十进制数无法精确表示1/3是同样的道理。
❝计算机作为一个 「功能有限」 的机器设备,是无法处理 「无限循环」 的小数的
❞
因此,当遇到 「循环小数」 时,计算机就会根据 「变量数据类型」 所对应的长度,将数值从 「中间截断」 或者 「四舍五入」。
然后,我们再结合上面的例子:一个 「循环小数」 在存储时已经被 「掐头去尾」,而偏偏这个近似值又被反复进行了N次累加。不怕你不努力,就怕你持之以恒地向偏离既定轨道的方向上移动——那么结果可想而知,永远无法达到最终想要的结果。
常见问题:
- 问: 为什么有些编程语言(如Python)中
0.1+0.2的结果是0.30000000000000004而不是0.3?
答: 这和累加0.1的问题同源。0.1和0.2在二进制中都是无限循环小数,存储时被截断,相加后误差累积导致结果不精确。 - 问: 有没有办法避免这种误差?
答: 对于金融等要求高精度的场景,可以使用十进制浮点数库(如Python的decimal模块)或采用整数运算(例如将金额以分为单位存储)。
浮点数
像1011.0011这样带小数点的表现形式,在计算机内部是无法直接使用的。
很多编程语言都提供了两种表示小数的数据类型,分别是 「双精度浮点数」 和 「单精度浮点数」。
- 「双精度浮点数」 用
64位表示小数 - 「单精度浮点数」 用
32位表示小数
「浮点数」 是指用 「符号」、 「尾数」、 「基数」 和 「指数」 这四部分表示的小数。

❝计算机内部使用的是二进制数,所以 「基数是2」,因此实际的数据中往往不考虑基数。只用 「符号」、 「尾数」、 「指数」 这三部分就可以表示 「浮点数」。
❞
浮点数表现形式
浮点数的表示方式有很多种,我们以IEEE标准为例进行说明。

双精度浮点数和单精度浮点数在表示同一个数值时,「使用的位数」 不同。
「符号部分」 是指使用一个 「数据位」 来表示符号。「数据位为1时表示负,为0时表示正或者0」
❝数值的大小用 「尾数部分」 和 「指数部分」 来表示。即用 「尾数部分 × 2的指数部分次幂」 的形式来表示。
❞
- 「尾数部分」 采用的是「将小数点前面的值固定为1的正则表达式」
- 「指数部分」 采用的是「EXCESS系统表示」
总结来说,0.1累加100次无法得到10的根本原因在于:十进制小数0.1无法用有限的二进制位精确表示,计算机只能近似存储,多次累加后误差不断积累,最终导致结果偏离预期。理解这一点,有助于我们在编程中正确处理小数运算,避免踩坑。
