知识点

浮点数与IEEE 754

定义

二进制浮点数沿用科学计数法:先把数写成整数部分为一位的二进制小数,再乘以2的幂,即 ±(1.f)2×2e\pm(1.f)_2\times2^{e}。IEEE 754把符号、指数、尾数三段装进定长字段,其中指数存成偏移后的 E=e+127E=e+127(单精度),数值由下式还原:

V=(−1)S×(1.f)2×2E−127.V=(-1)^S\times(1.f)_2\times 2^{E-127}.

32位单精度的字段划分如下。

字段位数位置含义
符号 SS1第31位0为正,1为负
阶码 EE8第30~23位偏移后的指数,E=e+127E=e+127
尾数 ff23第22~0位小数点后的部分,隐含前导1

写成位串时依次是S EEEEEEEE FFF…F,阶码在前、尾数在后。记尾数位为 f1f2⋯f23f_1f_2\cdots f_{23}(f1f_1 紧接小数点),则

(1.f)2=1+∑i=123fi2−i,V=(−1)S(1+∑i=123fi2−i)2E−127.(1.f)_2=1+\sum_{i=1}^{23}f_i2^{-i},\qquad V=(-1)^S\left(1+\sum_{i=1}^{23}f_i2^{-i}\right)2^{E-127}.

这两个式子是换算的依据:先拆出三段,把阶码减127得到实际指数 ee,再把尾数按 2−1,2−2,…2^{-1},2^{-2},\dots 展开,最后加上隐含的1。

性质

阶码用偏移表示

指数可能是负的,例如 2−32^{-3} 表示很小的数。如果阶码直接存带符号数,比较两个浮点数的大小时就得先看阶码的符号位,硬件无法把位串当作无符号整数直接比较。IEEE 754给指数统一加一个偏移:单精度 E=e+127E=e+127,双精度 E=e+1023E=e+1023。这样阶码字段本身就是无符号整数,指数的大小顺序与位串的字典序一致。

单精度阶码8位,规格化数对应 E=1∼254E=1\sim254,即 e=−126∼127e=-126\sim127;双精度阶码11位,对应 e=−1022∼1023e=-1022\sim1023。

隐藏的前导1

规格化之后整数部分总是1:整数部分若为0,左移一位、阶码加一即可;若大于1,则还没有规格化完。既然总是1,就不必存进尾数,省下一位。因此23位尾数实际提供24位有效数字,双精度的52位尾数提供53位。

精度与范围

格式总位数阶码位尾数位偏移十进制有效数字
单精度32823127约7位
双精度6411521023约15~16位

范围由阶码决定,精度由尾数决定。单精度能表示的最大规格化数约为 3.4×10383.4\times10^{38},双精度约为 1.8×103081.8\times10^{308}。C中的float、double分别对应这两种格式。

误差来源

十进制小数转二进制时,若最简分数的分母含有2以外的质因子,展开就无限循环。0.10.1 的二进制是 0.0001100110011…20.0001100110011\ldots_2,规格化后为 1.100110011…2×2−41.100110011\ldots_2\times2^{-4},尾数需要无限多位。23位尾数只能截断或就近舍入,存进去的值与 110\frac{1}{10} 相差约 1.5×10−91.5\times10^{-9}。

运算中还要对齐阶码、对结果舍入,误差会逐步累积。例如:

浮点误差
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False

第二行输出False,说明两个结果不相等。比较浮点数应写成 ∣a−b∣<ε|a-b|<\varepsilon 的形式,而不是直接用等号。

例子

解码0 10000010 101000…0

  1. 拆字段:S=0S=0;E=(10000010)2=130E=(10000010)_2=130;尾数前三位是 101101,其余为0。

  2. 定符号:S=0S=0,是正数。

  3. 算指数:e=130−127=3e=130-127=3。

  4. 算尾数:0.1012=2−1+2−3=0.5+0.125=0.6250.101_2=2^{-1}+2^{-3}=0.5+0.125=0.625,加上隐含的1得 1.6251.625。

  5. 合成:V=1.625×23=1.625×8=13.0V=1.625\times2^3=1.625\times8=13.0。

为什么 0.1+0.20.1+0.2 不等于 0.30.3

0.10.1、0.20.2、0.30.3 在二进制下都是无限循环小数,存入时各自被舍入成最接近的可表示值。三个值都不等于原来的十进制小数,相加的结果自然与 0.30.3 的存储值不同,差额在双精度下约为 10−1710^{-17} 量级。

这不是浮点数算错了,而是舍入的必然结果。把判断改成 ∣(0.1+0.2)−0.3∣<ε|(0.1+0.2)-0.3|<\varepsilon 后,比较就能通过。

题集

练习

  1. 位串0 10000001 100000…0表示的十进制值是 。

    参考解答

    E=129E=129,实际指数 129−127=2129-127=2;尾数 0.12=0.50.1_2=0.5,加上隐含的1得 1.51.5。1.5×22=61.5\times2^2=6。

  2. 填出IEEE 754各字段的位数。

    1. 单精度的阶码占 位。

      参考解答

      单精度共32位:符号1位、阶码8位、尾数23位。

    2. 单精度的尾数占 位。

      参考解答

      尾数不存储隐含的前导1,所以只占23位。

    3. 双精度的尾数占 位。

      参考解答

      双精度共64位:符号1位、阶码11位、尾数52位。

  3. 8.08.0 的单精度表示中,阶码字段的8位二进制是 (只填0和1)。

    参考解答

    8.0=1.02×238.0=1.0_2\times2^3,实际指数 e=3e=3,阶码 E=3+127=130=(10000010)2E=3+127=130=(10000010)_2。尾数位全为0。

  4. 在浮点数中 0.1+0.2≠0.30.1+0.2\ne0.3 的原因是:

    选择答案
    参考解答

    0.10.1、0.20.2、0.30.3 的二进制展开都无限循环,存入时已经各自舍入;加法还要再舍入一次,结果与 0.30.3 的存储值不相等。这与溢出无关,也不是符号位造成的。

交互探索

0.1在32位里是什么样?

输入一个十进制数,拆开它的IEEE 754单精度符号、阶码和尾数。

正在准备演示…