软考备考笔记(三):浮点数表示

4 阅读6分钟

一、浮点数是什么

1.1 从科学计数法说起

十进制里我们这样写很小的数:

0.0034 = 3.4 × 10⁻³
         ↑       ↑
       尾数     指数

浮点数就是二进制的科学计数法

二进制:1.011 × 2¹⁰
          ↑      ↑
        尾数    指数

计算机存一个浮点数,就是存尾数指数这两个部分。

1.2 存储结构

一个浮点数在内存中分为四个字段:

┌──────────┬──────────┬──────────┬──────────┐
│   阶符    │   阶码    │   数符    │   尾数    │
│ (指数正负) │ (指数大小) │ (数值正负) │ (有效数字) │
└──────────┴──────────┴──────────┴──────────┘
字段含义举例
阶符指数是正还是负+
阶码指数的值3
数符整个数是正还是负+
尾数有效数字部分1.011

二、精度与范围:考试核心考点

记住两条规则:

① 尾数越长 → 精度越高(能表示的有效数字越多)
② 阶码越长 → 范围越大(能表示的数越大或越小)

打个比方:

尾数好比尺子的刻度 → 刻度越细,量得越准(精度)
阶码好比尺子的长度 → 尺子越长,量得越远(范围)

典型考题

浮点数 A:阶码 4 位,尾数 8 位(共 12 位) 浮点数 B:阶码 6 位,尾数 6 位(共 12 位)

总位数相同,分配不同:

阶码尾数特点
A4 位(短)8 位(长)范围小,精度高
B6 位(长)6 位(短)范围大,精度低

阶码和尾数此消彼长,总位数固定的条件下,精度和范围不可兼得。


三、规格化

3.1 什么是规格化

规则:尾数的小数点后第一位必须是 1,不能是 0。

规格化:  1.0110 × 2³    ✓ 小数点后第一位是 1
未规格化:0.0110 × 2⁴    ✗ 小数点后第一位是 0

3.2 如何规格化

未规格化的数可以调整指数来修正:

0.0110 × 2⁴ = 0.110 × 2³ = 1.10 × 2²
                                   ↑
                             已规格化

小数点右移一位,指数就减 1,值不变。

3.3 为什么要规格化

如果不规格化,0.0110 的前两位 00 是浪费的——占了尾数位却没有贡献有效数字。规格化确保每一位尾数都承载有效信息,不浪费精度。


四、IEEE 754 标准——浮点数的真实存储格式

你在 Java 里用的 floatdouble,底层遵循的就是 IEEE 754 标准

4.1 两种精度

float(单精度):32 位
┌──────┬──────────┬───────────────────────────┐
│ 符号 │  指数     │          尾数              │
│ 1 位 │  8 位     │         23 位              │
└──────┴──────────┴───────────────────────────┘

double(双精度):64 位
┌──────┬──────────┬───────────────────────────┐
│ 符号 │  指数     │          尾数              │
│ 1 位 │  11 位    │         52 位              │
└──────┴──────────┴───────────────────────────┘
floatdouble
总位数3264
符号位1 位1 位
指数位8 位11 位
尾数位23 位52 位
精度约 7 位有效数字约 15~16 位有效数字
Java 声明float f = 1.0f;double d = 1.0;(默认)

尾数越多,精度越高。 double 的尾数是 float 的两倍多,精度自然高得多。

4.2 指数的偏移量

IEEE 754 中,指数不用补码表示,而是用偏移量(Bias)

类型指数位数偏移量
float8 位127
double11 位1023
存储值 = 实际指数 + 偏移量

例如 float 中,实际指数为 3:

存储值 = 3 + 127 = 130 = 10000010

读取时减去偏移量即可还原。


五、完整手算示例:float 存储 9.625

第一步:整数和小数分别转二进制

整数部分:9 ÷ 2 ... 余 1
                  ÷ 2 ... 余 0
                  ÷ 2 ... 余 0
                  ÷ 2 ... 余 1
          9 = 1001(二进制)

小数部分:0.625 × 2 = 1.25 → 取 1
          0.25  × 2 = 0.5  → 取 0
          0.5   × 2 = 1.0  → 取 1
          0.625 = .101(二进制)

合起来:9.625 = 1001.101(二进制)

第二步:规格化

1001.101 = 1.001101 × 2³
             ↑          ↑
           尾数       指数 = 3(小数点左移了 3 位)

第三步:填入 32 位

符号:0(正数)
指数:3 + 127 = 130 = 10000010
尾数:00110100000000000000000(去掉开头的 1,后面补 0 凑满 23 位)

完整 32 位:
0 10000010 00110100000000000000000
↑     ↑                ↑
符号  指数              尾数

六、为什么 0.1 + 0.2 ≠ 0.3

这是浮点数最经典的面试题和实际问题。

6.1 根本原因:有些十进制小数在二进制中无限循环

十进制中 1/3 = 0.333333... 无限循环,用有限位数永远写不精确。

二进制也有同样的问题。把十进制 0.1 转成二进制:

0.1 × 2 = 0.2  → 取 0
0.2 × 2 = 0.4  → 取 0
0.4 × 2 = 0.8  → 取 0
0.8 × 2 = 1.6  → 取 1
0.6 × 2 = 1.2  → 取 1
0.2 × 2 = 0.4  → 取 0  ← 回到起点,开始循环
...
十进制 0.1 = 二进制 0.00011001100110011...(无限循环)

double 只有 52 位尾数,存不下无限循环,必须截断。截断就有误差,两个带误差的数相加,误差累积,结果就不是精确的 0.3。

这不是编程语言的 bug,是浮点数的先天局限。

6.2 解决方案

需要精确计算时(比如金额),Java 中使用 BigDecimal

BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b));  // 输出 0.3,精确

七、为什么指数用偏移量而不用补码

7.1 补码存指数的问题

用 4 位举例,假设两个浮点数要比较大小:

A 的指数:1101(补码,实际 -3B 的指数:0010(补码,实际 +2

直接按二进制比较:1101 > 0010,但实际 -3 < +2

结果相反。 计算机必须先识别符号位,再分别处理,才能正确比较。电路复杂,速度慢。

7.2 偏移量存指数的优势

偏移量 = 7 时:

A 的指数:-3 + 7 = 40100
B 的指数:+2 + 7 = 91001

直接比较:0100 < 1001,即 -3 < +2正确

偏移量把整个范围平移到了正数区间,存储值的大小顺序就是实际值的大小顺序:【核心】

实际指数:  -7  -6 -5 -4 -3 -2 -1  0  1  2  3  4  5  6  7  8
存储值:     0   1  2  3  4  5  6  7  8  9 10 11 12 13 14 15

这意味着计算机比较两个浮点数时,可以把符号位、指数、尾数拼在一起,当作一个普通整数直接比较,不需要任何特殊处理。这在硬件层面省了大量的电路和时间。

偏移量不是为了表示正负(补码也能),而是为了让比较操作变得简单快速。


八、速查卡片

┌──────────────────────────────────────────────────┐
│  浮点数 = 符号 + 指数 + 尾数                       │
│                                                  │
│  尾数越长 → 精度越高                               │
│  指数越长 → 范围越大                               │
│  精度和范围此消彼长                                │
│                                                  │
│  规格化:尾数小数点后第一位必须是 1                  │
│                                                  │
│  IEEE 754 标准:                                   │
│    float  = 32 位(1 + 8 + 23),指数偏移 127      │
│    double = 64 位(1 + 11 + 52),指数偏移 1023     │
│                                                  │
│  存储值 = 实际指数 + 偏移量                         │
│  偏移量的作用:让位模式的大小顺序=数值大小顺序        │
│                                                  │
│  0.1 + 0.2 ≠ 0.3 是浮点数先天局限,不是 bug        │
│  需要精确计算 → 用 BigDecimal                      │
└──────────────────────────────────────────────────┘