📘 第02课:单精度浮点格式详解

🎯 本课目标

📖 单精度格式总览

IEEE 754单精度浮点数(binary32)使用32位编码:

Bit: 31 30 23 22 0 ┌──┬──────────────────┬────────────────────────────┐ │S │ E7 E6 E5 E4 E3 E2 E1 E0 │ M22 M21 ... M1 M0 │ │ │ 8位指数 │ 23位尾数 │ └──┴──────────────────┴────────────────────────────┘ 值 = (-1)^S × 2^(E-127) × 1.M (规格化数) 值 = (-1)^S × 2^(-126) × 0.M (非规格化数)

字段详解

字段位宽位位置含义
S (Sign)1[31]0=正数, 1=负数
E (Exponent)8[30:23]偏移码,Bias=127
M (Mantissa)23[22:0]小数部分(隐含1.或0.)

📖 单精度值域与精度

规格化数范围

项目指数E实际指数
最小规格化数1-126±1.0...0 × 2^(-126) ≈ ±1.175×10^(-38)
最大规格化数254+127±1.1...1 × 2^(+127) ≈ ±3.403×10^(+38)
1.01270±1.0
2.01281±2.0

精度分析

单精度有23位尾数 + 1位隐含位 = 24位有效数字

相对精度 = 2^(-24) ≈ 5.96 × 10^(-8) ≈ 7位十进制有效数字
⚠️ 常见误区:说"单精度有7位十进制精度"是指约7位有效数字,并不意味着能精确表示所有7位十进制数!例如0.1在单精度中无法精确表示。

ULP(Unit in the Last Place)

ULP是浮点数可表示的最小间距。对于指数为e的规格化数:

ULP = 2^(e-23) = 2^(E-150)

当E=127(值为1.0附近)时,ULP = 2^(-23) ≈ 1.19×10^(-7)

当E=200(值为2^73附近)时,ULP = 2^(50) ≈ 1.13×10^15 ——巨大!

📖 十进制到单精度的转换

转换步骤

  1. 确定符号位S
  2. 将绝对值转换为二进制
  3. 规格化:将二进制小数点移到1.xxx形式,得到指数e
  4. 计算偏移指数 E = e + 127
  5. 取尾数的小数部分(23位),不足补零,超出截断或舍入
  6. 组合 S | E[7:0] | M[22:0]

实例:转换 -6.625

1) S = 1 (负数) 2) 6.625 = 4 + 2 + 0.5 + 0.125 = 110.101₂ 3) 规格化: 1.10101 × 2² e = 2 4) E = 2 + 127 = 129 = 10000001₂ 5) M = 10101000000000000000000 (1.10101 的小数部分) 6) 结果: 1 10000001 10101000000000000000000 十六进制: C0D40000

📖 单精度到十进制的转换

实例:解析 0x41200000

0x41200000 = 0100 0001 0010 0000 0000 0000 0000 0000 S = 0 (正数) E = 10000010₂ = 130 实际指数 = 130 - 127 = 3 M = 01000000000000000000000 隐含1: 1.010 × 2³ = 1010.0₂ = 8 + 2 = 10.0

🔧 Verilog实现:单精度解码器

//=============================================================
// float32_decoder.sv - IEEE 754 单精度浮点数解码器
// 功能:将32位浮点数解码为符号、实际指数、尾数(含隐含位)
// 验证:Verilator --lint-only
//=============================================================
module float32_decoder (
    input  wire [31:0] fp_in,

    output wire              sign,       // 符号位
    output wire [7:0]           exp_field,  // 原始指数字段
    output wire [22:0]          mant_field, // 原始尾数字段
    output wire [7:0]           exp_actual, // 实际指数(无偏)
    output wire [23:0]          mant_full,  // 完整尾数(含隐含位)
    output wire              is_normal,  // 是否规格化
    output wire              is_denorm,  // 是否非规格化
    output wire              is_special   // 是否特殊值(Inf/NaN)
);

    // 字段提取
    assign sign       = fp_in[31];
    assign exp_field  = fp_in[30:23];
    assign mant_field = fp_in[22:0];

    // 分类判断
    wire exp_is_zero = (exp_field == 8'b0);
    wire exp_is_max  = (exp_field == 8'hFF);

    assign is_normal  = ~exp_is_zero & ~exp_is_max;
    assign is_denorm  = exp_is_zero & (mant_field != 23'b0);
    assign is_special = exp_is_max;

    // 实际指数计算
    // 规格化数: E - 127
    // 非规格化数: -126 (不是-127!)
    assign exp_actual = is_normal ? (exp_field - 8'd127) :
                             8'd130; // -126的无符号表示(用于显示)

    // 完整尾数(含隐含位)
    // 规格化数: 1.M → {1, mant_field}
    // 非规格化数: 0.M → {0, mant_field}
    assign mant_full = is_normal ? {1'b1, mant_field} :
                             {1'b0, mant_field};

endmodule

测试平台

//=============================================================
// tb_float32_decoder.sv - 解码器测试
//=============================================================
module tb_float32_decoder;

    reg  [31:0] fp_in;
    wire              sign;
    wire [7:0]           exp_field;
    wire [22:0]          mant_field;
    wire [7:0]           exp_actual;
    wire [23:0]          mant_full;
    wire              is_normal, is_denorm, is_special;

    float32_decoder uut (
        .fp_in(fp_in), .sign(sign), .exp_field(exp_field),
        .mant_field(mant_field), .exp_actual(exp_actual),
        .mant_full(mant_full), .is_normal(is_normal),
        .is_denorm(is_denorm), .is_special(is_special)
    );

    integer errors = 0;

    task check_normal;
        input [31:0] val;
        input        exp_sign;
        input [7:0]  exp_val;
        input [23:0] mant_val;
        input [255:0] name;
        begin
            fp_in = val;
            #10;
            if (sign !== exp_sign || exp_actual !== exp_val || mant_full !== mant_val || !is_normal) begin
                $display("FAIL %0s: sign=%b exp=%0d mant=%h norm=%b",
                    name, sign, exp_actual, mant_full, is_normal);
                errors = errors + 1;
            end else
                $display("PASS %0s", name);
        end
    endtask

    initial begin
        // 1.0: 0x3F800000, exp=0, mant=1.000...
        check_normal(32'h3F80_0000, 1'b0, 8'd0, 24'h800000, "1.0");
        // 2.0: 0x40000000, exp=1, mant=1.000...
        check_normal(32'h4000_0000, 1'b0, 8'd1, 24'h800000, "2.0");
        // -6.625: 0xC0D40000, exp=2, mant=1.10101...
        check_normal(32'hC0D4_0000, 1'b1, 8'd2, 24'hD40000, "-6.625");
        // 10.0: 0x41200000, exp=3, mant=1.01...
        check_normal(32'h4120_0000, 1'b0, 8'd3, 24'hA00000, "10.0");

        // 特殊值测试
        fp_in = 32'h7F80_0000; #10;
        if (!is_special) begin $display("FAIL +Inf not special"); errors = errors + 1; end
        else $display("PASS +Inf is special");

        fp_in = 32'h0000_0001; #10;
        if (!is_denorm) begin $display("FAIL MinDenorm"); errors = errors + 1; end
        else $display("PASS MinDenorm is denorm");

        $display("\n=== Errors: %0d ===", errors);
        $finish;
    end

endmodule

📊 仿真验证结果

PASS 1.0
PASS 2.0
PASS -6.625
PASS 10.0
PASS +Inf is special
PASS MinDenorm is denorm

=== Errors: 0 ===

✅Verilator验证通过 ✅6/6测试通过

📖 精度陷阱:为什么0.1+0.2≠0.3?

这是浮点数最经典的问题之一。让我们从单精度格式来理解:

0.1₁₀ = 0.00011001100110011001100110011...₂ (无限循环!) 截断为23位尾数: 0.00011001100110011001101 (已舍入) 实际存储值 ≈ 0.100000001490116119384765625 0.2₁₀ = 0.001100110011001100110011...₂ (同样无限循环) 实际存储值 ≈ 0.200000002980232238769531250 0.1 + 0.2 ≠ 0.3 (因为两个数都有舍入误差,误差累积)

这不是"bug",而是有限精度表示的固有特性。在FPU设计中,理解这一点对于舍入逻辑至关重要。

📖 位模式与整数的巧妙关系

单精度浮点数有一个有趣的性质:对于正规格化数,如果把浮点数的位模式当作无符号整数,则整数的大小关系等价于浮点数的大小关系。

float: 0.5 1.0 2.0 3.0 hex: 3F000000 3F800000 40000000 40400000 uint: 1056964608 1065353216 1073741824 1077936128 ← 递增! 这个性质可以用于快速浮点整数转换和比较操作。

📝 练习

练习1:手动计算 π ≈ 3.14159265 的单精度表示,验证你的结果。

练习2:0x42F60000 表示什么值?

练习3:实现一个Verilog模块,将单精度浮点数的ULP(最小表示间距)计算出来。

练习4:写一个测试,验证"0.1 + 0.2 ≠ 0.3"在浮点数中确实成立。

🏆 成就解锁

🏅 单精度学者

✅ 掌握了单精度格式的每个位含义

✅ 能手动进行十进制↔单精度转换

✅ 理解了精度极限与ULP概念

✅ 实现了单精度解码器

下一成就:双精度大师 — 下一课将深入双精度格式