IEEE 754 与 9999999999999999.0 - 9999999999999998.0 == 2.0

1. 计算机算错了吗?

今天在 Twitter 上看到这样一条 tweet:

It’s a simple question:

9999999999999999.0 - 9999999999999998.0

Does your favorite language give the right answer? http://geocar.sdf1.org/numbers.html

当然是等于 1 嘛~

然而大多数编程语言都没有得出正确答案——它们得出的结果是 2.0

language output
C: main(){printf("%lf\n",(double)9999999999999999.0-9999999999999998.0);}
2.000000
Java: public class Foo{public static void main(String args[]){System.out.println(9999999999999999.0-9999999999999998.0);}}
2.0
Python: >>> 9999999999999999.0 - 9999999999999998.0
2.0
Javascript: 9999999999999999.0 - 9999999999999998.0
2.0
GoLang: var a = 9999999999999999.0; var b = 9999999999999998.0; fmt.Printf("%f\n", a-b)
2.000000
Ruby: irb(main):001:0> 9999999999999999.0 - 9999999999999998.0
2.0
R: > 9999999999999999.0-9999999999999998.0 [1]
2

WHY?

因为 IEEE 754

2. IEEE 754

The IEEE Standard for Floating-Point Arithmetic (IEEE 754) is a technical standard for floating-point arithmetic established in 1985 by the Institute of Electrical and Electronics Engineers (IEEE). via en.wikipedia.org/wiki/IEEE_754)

1985 年,电气电子工程师协会(IEEE)制定了浮点数算数标准——IEEE 754,为许多CPU与浮点运算器所采用。IEEE 754 规定了四种表示浮点数值的方式:单精度(32位)、双精度(64位)、延伸单精度(43位以上,很少使用)与延伸双精度(79位以上,通常以80位实现)。现代的编程语言通常采用双精度(64位)进行浮点数算数运算。

2.1 浮点数实现

浮点数在计算机内部以二进制数表示,其结构如下:

Value = sign × exponent × fraction值 = 符号位 * 指数偏移值 * 分数值

其中:

  • 符号位:正为 0,负为 1。
  • 指数偏移值:即浮点数表示法中指数域的编码值,等于指数的实际值加上某个固定的值,IEEE 754 标准规定该固定值为 2^(e - 1) - 1​,其中 e 为指数偏移值部分的长度。
  • 编码范围为 0 <= exponent <= 2^e - 1,实际取值范围为 -2^(e - 1) - 2 <= exponent <= 2^(e - 1) - 1​。​ -2^(e - 1) - 1 ​和 2^(e - 1) 被用作特殊处理,见下方「非规约形式的浮点数」和「特殊值」。
  • 分数值:

  • 规约形式的浮点数:如果浮点数中指数部分的编码值在 0 < exponent <= 2^{e}-2 之间,且在科学表示法的表示方式下,分数 (fraction) 部分最高有效位(即整数位)是 1,那么这个浮点数将被称为规约形式的浮点数。

    • 根据定义,规约形式的浮点数中,分数部分第一位必为整数 1,故此 1 可以省去,只留小数部分,由此可以多存一位有效数字。
  • 非规约形式的浮点数:如果浮点数的指数部分的编码值是 0,分数部分非零,那么这个浮点数将被称为非规约形式的浮点数。一般是某个数字相当接近零时才会使用非规约型式来表示。 此时,0 表示 -2^(e - 1) - 2 而不是 -2^(e - 1) - 1,以此来解决突然式下溢出(abrupt underflow)问题。

在使用 32 位存储的浮点数中,符号位占 1 位,指数偏移值占 8 位,分数值占 23 位。

在使用 64 位存储的浮点数中,符号位占 1 位,指数偏移值占 11 位,分数值占 52 位。(如图所示:)

IEEE 754 双精度浮点数

2.1.1 IEEE 754 规定了三个特殊值:

  • 如果指数是 0 并且尾数的小数部分是 0,这个数是 ±0(和符号位相关)
  • 如果指数 = 2^(e - 1) 并且尾数的小数部分是 0,这个数是 ±(同样和符号位相关)
  • 如果指数 = 2^(e - 1) 并且尾数的小数部分非 0,这个数表示为不是一个数(NaN)

2.2 双精度有多精确?

双精度的分数部分共有 52 位,加上规约形式前面省略的 1 位,因此共有 53 位二进制精度。

能表示的最大数值(在不考虑指数部分的情况下)为:2^53 - 1 = 9007199254740991 (十进制,共 16 位有效数字)。

超过精度的数值会被舍入,IEEE 754 默认的舍入规则为就近舍入,即舍入到最近的偶数——二进制下末位为 0 的数值。

2.3 双精度浮点数表示 9999999999999999.09999999999999998.0

考察 99999999999999999999999999999998 的二进制形式如下:

>>> bin(9999999999999999), bin(9999999999999998)
<<< ('0b100011100001101111001001101111110000001111111111111111', # 二进制共 54 位
     '0b100011100001101111001001101111110000001111111111111110') # 同上

二进制共 54 位,规约后省略首位,仍有 53 位

2.3.1 9999999999999999.0:

1. 9999999999999999 用二进制表示为:
              100011100001101111001001101111110000001111111111111111
              └─────────────────────────┬──────────────────────────┘
                                     54 bit
2. 9999999999999999.0 科学表示法表示为:
      2^53 × 1.00011100001101111001001101111110000001111111111111111
               └───────────────────────┬──────────────────────────┘│
                                    52 bit             超出 1 bit,舍去最后一位 1 并进一位
3. 9999999999999999.0 用双精度浮点数表示为:
   0100001101000001110000110111100100110111111000001000000000000000
   │└────┬────┘└────────────────────────┬─────────────────────────┘
   sign exp                          52 bit
   等于 10000000000000000,即 1e+16

2.3.2 9999999999999998.0:

1. 9999999999999998 用二进制表示为:
              100011100001101111001001101111110000001111111111111110
              └─────────────────────────┬──────────────────────────┘
                                     54 bit
2. 9999999999999998.0 科学表示法表示为:
      2^53 × 1.00011100001101111001001101111110000001111111111111110
               └───────────────────────┬──────────────────────────┘│
                                    52 bit              超出 1 bit 舍入:舍去最后一位 0
3. 9999999999999998.0 用双精度浮点数表示为:
   0100001101000001110000110111100100110111111000000111111111111111
   │└────┬────┘└────────────────────────┬─────────────────────────┘
   sign exp                          52 bit
   等于 9999999999999998,即 9.999999999999998e+15

2.4 双精度浮点数表示 9999999999999999.0 - 9999999999999998.0

因此,在双精度浮点数表示下,

   9999999999999999.0 - 9999999999999998.0
= 10000000000000000.0 - 9999999999999998.0
= 2.0

也就可以理解了。

最后

二进制可以精确地表示绝大部分常见的 10 进制整数,但无法精确地表示大部分常见的 10 进制小数(仅能精确表示二进分数 m/2^n)。

比如 0.1 + 0.2 == 0.30000000000000004 也是困扰无数计算机新手的经典问题。

双精度浮点数表示 实际值
0.1 0x3FB999999999999A 0.100000000000000005551115123126
0.2 0x3FC999999999999A 0.200000000000000011102230246252
0.3 0x3FD3333333333333 0.299999999999999988897769753748

Well, nobody's perfect. - Some Like It Hot (1959)

By @Andy in
Tags: #computer
Share: