⚙️ 第三章 计算机在计算小数时会出错的原因
📄 一句话概括

把浮点数的四个字节取出来,按 1+8+23 切三段,就能亲眼看到 0.1 的尾巴被砍了一刀。

📝 本节和前面几节的关系

3.5 讲完了规矩,这一节动手把规矩验证一遍——把位拆开看。

一、工具:把内存里的字节取出来

Python 里用 struct 就能把 4 个字节取出来:pack 成 4 字节,再展开成 32 个位。 然后按 1 位符号、8 位指数、23 位尾数切开。 这三步对应的正是 3.4 讲的结构,没有黑魔法,就是切字符串。

二、拿三个数验算

0.5:指数位 01111110 = 126,减 127 得 -1,即 1.0 × 2⁻¹ = 0.5 ✓ 1.0:指数 01111111 = 127,真实指数 0,即 1.0 × 2⁰ = 1.0 ✓ 10.0:指数 10000010 = 130,真实指数 3,即 1.25 × 2³ = 10 ✓ 三行都对上了,说明我们对格式的理解没错。

三、再看 0.1 那一行

0.1 的尾数是 10011001100110011001101——数一数,正好 23 位。 而真值该是 1001100110011001100110011001100…(无限循环)。 末尾被砍了一刀——这就是误差的位置看得见的样子。 它的指数真实值是 -4,所以这个数实际是 1.10011001100110011001101 × 2⁻⁴。

四、这件事有什么实际用处

第一,调试时不再发懵。 看到 9.99999999999998,你知道该往哪儿找原因。 第二,判断精度够不够。 尾数只有 23/52 位,超出的位数本来就不存在。 第三,理解为什么比较要带容差。 两个【应该相等】的浮点数,尾数可能就差最后一位。 拆一次位,比读十页文档管用——这是理解浮点最快的一条路。

五、动手 Demo:把浮点数的位拆开看

用 struct 把内存里的 4 个字节取出来,按 1+8+23 切成三段。 指数真实值 = 存下的指数值 - 127。

import struct
for v in (0.5, 1.0, 10.0, 0.1, -2.5):
    bits = "".join(format(x, "08b") for x in struct.pack(">f", v))
    print("%6s  %s  %s  %s  (真实指数 %d)"
          % (v, bits[0], bits[1:9], bits[9:], int(bits[1:9], 2) - 127))

实跑输出:

--------------------------------------------------------------------
把浮点数的位拆开看
--------------------------------------------------------------------

用 struct 把内存里的 4 个字节取出来,按 1+8+23 切成三段。
  指数真实值 = 存下的指数值 - 127。

  值       符号     指数         尾数(23 位)               指数真实值
  ----------------------------------------------------------------------
  0.5      0        01111110     00000000000000000000000     -1
  1.0      0        01111111     00000000000000000000000     0
  10.0     0        10000010     01000000000000000000000     3
  0.1      0        01111011     10011001100110011001101     -4
  -2.5     1        10000000     01000000000000000000000     1

  对着前三行验算一下:
    0.5  指数真实 -1  ->  1.0 × 2^-1 = 0.5
    1.0  指数真实  0  ->  1.0 × 2^0  = 1.0
    10.0 指数真实  3  ->  1.25 × 2^3 = 10

  再看 0.1 那一行——**尾数是 10011001100110011001101,末尾被砍了一刀。**
  那就是它的误差:真值该是 1001100110011…(无限循环),这里只能存 23 位。

0.5/1.0/10.0 三行都能按【1.xxx × 2ⁿ】验算回去,格式理解无误。 0.1 的尾数 10011001100110011001101 末尾被砍——误差就写在这 23 位里。

30 秒实验

  1. 把 3.14 的 32 位拆开,看它的尾数最后几位是什么。
  2. 把同一个数按单精度和双精度各拆一次,对比尾数长度。

常见误区

  1. 以为【位模式看不懂就等于学不会】。就是切字符串,谁都能做。
  2. 忘记指数要减偏移量。不减的话 126 会被当成指数 126,差着十万八千里。
自测三问(点开对答案) 1. 怎么看一个浮点数的三段? —— 把 32 位按 1(符号)+8(指数)+23(尾数)切开。 2. 指数真实值怎么算? —— 用指数位的值减去偏移量,单精度减 127、双精度减 1023。 3. 0.1 的位模式说明了什么? —— 尾数只存了 23 位,后面的循环位被砍掉,这就是它的误差来源。

相关笔记