玩命加载中 . . .

2.4-浮点数


2.4 浮点数

2.4.1 浮点数表示

float占4个字节,32位,分成3段

  • 最高位表示符号位S,S为0表示正数,S为1表示负数
  • 23位到30位是阶码,共8
  • 0到22位是尾数,共23

double占8个字节,64位,也是分成3段

  • 最高位表示符号位S,S为0表示正数,S为1表示负数
  • 52位到62位是阶码,共11位
  • 0到51位是尾数,共52位
  • 当阶码字段的二进制不全为0,也不全为1时,表示的是规格化的值
  • 当阶码字段全为0时,表示非规格化的值
  • 当阶码字段全为1时,表示特殊值
    • 尾数全为0,表示无穷大或无穷小
    • 尾数不为0,表示“不是一个数”

规格化的值

用$e$来表示阶码字段的二进制数,范围是$0000 0001 \sim 11111110, e_{min}=1, e_{max}=254$

偏置量的值与阶码的位数相关

所以,阶码的范围是:$E_{min} = -126, E_{max} = 127$

$f$表示尾数字段的二进制数,尾数${\color{tomato}{M = 1 + f}}$

非规格化的值

阶码字段的值全为0

可以表示0:

可以表示非常接近0的数,计算公式跟规格化的不一样

特殊值

阶码全为1,尾数全为0

可以表示正无穷大和负无穷大

也可以表示$NaN$

阶码字段全为1,尾数字段不为0

规律

考虑k位阶码,n位小数

  • 值0.0的位模式全为0
  • 最下的非规格化值的位模式,除了最低位是1,其余为都是0,所以尾数$M=f= 2^{-n}$,阶码$E = 1-(2^{k-1}-1)=-2^{k-1}+2$,所以数字值是$V=M \times 2^E = 2^{-n-2^{k-1}-2}$
  • 最大的非规格化值的位模式,阶码字段全为0,尾数字段全为1,所以尾数$M=f=1-2^{-n}$,写成$1-\epsilon$,阶码$E=-2^{k-1}+2$,所以数字值是$V=M \times 2^E=(1-2^{-n}) \times 2^{-2^{k-1}-2}$
  • 最小的正规格化值的位模式,阶码字段最低位是1,其余全为0,所以尾数$M=1+f=1$,阶码$E=1-(2^{k-1}-1)=-2^{k-1}+2$,所以数字值是$V=2^{-2^{k-1}+2}$
  • 值1.0的位模式,阶码字段最高位为0,其余都为1,尾数字段都为0,所以尾数$M=1$,阶码$E=0$
  • 最大的正规格化值的位模式,符号位为0,阶码最高位0,其余为1,尾数字段都为1,所以小数值$f=1-2^{-n}$,尾数$M=1+f=2-2^{-n}$,写作$2-\epsilon$,阶码值$E=e-bias=2^k-2-(2^{k-1}-1)=2^{k-1}-1$,所以数值为$V=M \times 2^E=(2-2^{-n}) \times 2^{2^{k-1}-1}=(1-2^{-n-1}) \times 2^{k-1}$

验证

1.0的位模式是

0011 1111 1000 0000 0000 0000 0000 0000

所以小端模式输出十六进制是
00 00 80 3f

反之,如果位模式是3f 80 00 00,用float来解释的话应该是1.0

typedef unsigned char* byte_pointer;

void show_bytes(byte_pointer start, int len) {
    for (int i = 0; i < len; i++)
        printf(" %.2x", start[i]);   // 两位十六进制
    printf("\n");
}
void show_float(float x) {
    show_bytes((byte_pointer)& x, sizeof(x));
}

int main() {
    int x = 0x3f800000;
    float *y = (float*)&x;  // 强制转换为float解释
    printf("%f\n", *y);
    show_float(*y);
    return 0;
}
1.000000
00 00 80 3f

结果证明确实如此

如果将1.0的解码字段最低位置0,尾数字段全部置1,这应该是小于1.0的最大数

   3    f    8    0    0    0    0    0
0011 1111 1000 0000 0000 0000 0000 0000
0011 1111 0111 1111 1111 1111 1111 1111
   3    f    7    f    f    f    f    f
int main() {
    int x = 0x3f800000;
    float *y = (float*)&x;
    printf("%f\n", *y);
    show_float(*y);

    int x2 = 0x3f7fffff;
    float *y2 = (float*)&x2;
    printf("%.30f\n", *y2);
    show_float(*y2);
    return 0;
}
1.000000
 00 00 80 3f
0.999999940395355224609375000000
 ff ff 7f 3f

结果表明确实很接近,但是有效位数之后的就不准确了

那么,如果指定一个很接近的1的小数,很可能因为这种误差导致他转换得到的位模式和1.0是一样的,所以如果放到判断语句里面,结果可能会出问题

int main() {
    float y = 1.0;
    show_float(y);

    float y2 = 0.99999998;
    show_float(y2);

    if (y == y2) printf("Yes\n");
    else printf("No\n");
    return 0;
}
//  00 00 80 3f
//  00 00 80 3f
// Yes

原因是0.99999998转换成的二进制和1.0的是一样的,所以,最好不要用这种浮点数的比较


文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录