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。
notion image
  • 再看一个例子,假如你想买 1.2 元一斤的土豆 3 斤,算出来不是3.6,而是 3.5900000000000006,可以少给0.0000000000000004 元钱,哈哈哈哈,不过一般不会显示这么多位小数,一般保留两位小数显示为 3.59,这样就可以少给一分钱了。
notion image
其实不仅 Javascript, 比如其他 Python,Java,Golang 等语言都会出现这个浮点数计算不准确的现象,这个网站 可以查看。下面我就讲讲为什么出现这个问题。

为什么出现

出现这个现象的本质原因就是现在计算机无法准确地表示浮点数。

什么是浮点数

在计算机科学中,浮点(英语:floating point,缩写为FP)是一种对于实数的近似值数值表现法,由一个有效数字(即尾数)加上幂数来表示,通常是乘以某个基数的整数次指数得到。以这种表示法表示的数值,称为浮点数(floating-point number)。利用浮点进行运算,称为浮点计算。
浮点数一般分为单精度浮点数和双精度浮点数。
我们直接来看一个双精度浮点数内部是怎么表示的,可以打开这个 网站,输入 1.2 可以得到下图的结果 。
notion image
我来解释一下是怎么转化的,首先双精度浮点数一共占用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) 等等。
     

    Copyright © 2022 - 2025 Levi