0.1 + 0.2 不等于 0.3?
date
Apr 1, 2022
slug
not-equal-1-1-3
status
Published
tags
float
summary
type
Post
现象
学过小数加减法的同学都知道 0.1 + 0.2 是等于 0.3,那我何出此言呢。人可以很快并准确的计算出来,但是计算机就不定算得对了。我们现在用 Javascript 试试来计算看看是不是等于 0.3 。
- 我们在 Chrome 的 Devtools 的 Console 中输入 0.1 + 0.2,可以看到确实不等于 0.3。

- 再看一个例子,假如你想买 1.2 元一斤的土豆 3 斤,算出来不是3.6,而是 3.5900000000000006,可以少给0.0000000000000004 元钱,哈哈哈哈,不过一般不会显示这么多位小数,一般保留两位小数显示为 3.59,这样就可以少给一分钱了。

其实不仅 Javascript, 比如其他 Python,Java,Golang 等语言都会出现这个浮点数计算不准确的现象,这个网站 可以查看。下面我就讲讲为什么出现这个问题。
为什么出现
出现这个现象的本质原因就是现在计算机无法准确地表示浮点数。
什么是浮点数
在计算机科学中,浮点(英语:floating point,缩写为FP)是一种对于实数的近似值数值表现法,由一个有效数字(即尾数)加上幂数来表示,通常是乘以某个基数的整数次指数得到。以这种表示法表示的数值,称为浮点数(floating-point number)。利用浮点进行运算,称为浮点计算。
浮点数一般分为单精度浮点数和双精度浮点数。
我们直接来看一个双精度浮点数内部是怎么表示的,可以打开这个 网站,输入 1.2 可以得到下图的结果 。

我来解释一下是怎么转化的,首先双精度浮点数一共占用8个字节,即 64 位。
- 第 1 位为符号位,0 表示是正数,1表示是负数
- 这个数是正数所以是 0
- 第 2 位到 12 位,共 11 位是指数位
- 因为 1.2 的非小数位的二进制为 1,所以指数位为 0(1 == 2^0)。
- 但我们看到上面指数位的二进制并不是:000 0000 0001,而是 011 1111 1111,这是为什么呢?
- 双精度浮点数本来这 11 位可以表示 0 —> 2^11 指数,即: 0 —> 2048
- 但是指数也可以能是负数,除去全0(0)和全1 (2048),所以一半作为负指数,一半为正指数
- 1 —> 1023 就表示 -1023 —> 0
- 1024 —> 2047 就表示 1 —> 1023
- 所以指数为 0 ,在双精度指数位就表示为 1023, 对应的二进制为:011 1111 1111
- 第 13 位到 64 位,共 52位是尾数位
- 我们知道整数转为二进制采用的是除 2 取余法,那小数是怎么转化的呢,我可以告诉大家小数采用的是乘 2 取整。
- 最终我们到的小数位 0.2 的二进为: .0011001100110011001100110011001100110011001100110011
我们可以看到数是不断循环的,这就是前面计算不正确的原因。
浮点数区别
上面举例的是双精度浮点数,除了它一般还有单精度浮点数,两者不同点就在于尾数和幂数所占的二进制位数不一样。
名称/数量 | 单精度 | 双精度 |
符号位 | 1 | 1 |
幂数位(指数位) | 8 | 11 |
尾数位(小数位) | 23 | 52 |
总长度 | 32 | 64 |
浮点数储计算
我们现在来看看两个浮点数是怎么计算的,比如 0.1 + 0.2 。
看到了吧,就是这样算出来不精确的 0.3。
怎么避免
想要解决浮点数计算和表示不准确的问题,不需要自己去重复造轮子,推荐的方式就是使用对应语言别人实现好的 decimal 库,例如 Javascript 的 decimal.js (https://mikemcl.github.io/decimal.js),Python 自带的库 decimal (https://docs.python.org/3/library/decimal.html) 等等。