玩命加载中 . . .

2.4-浮点数


2.4 浮点数

2.4.1 浮点数表示

V=(1)S×M×2E

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来表示阶码字段的二进制数,范围是0000000111111110,emin=1,emax=254

E=ebias

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

bias(float)=2811=127bias(double)=21111=1023

所以,阶码的范围是:Emin=126,Emax=127

f表示尾数字段的二进制数,尾数M=1+f

非规格化的值

阶码字段的值全为0

可以表示0:

s=0,M=f=0,V=+0.0s=1,M=f=0,V=0.0

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

E=1biasM=f

特殊值

阶码全为1,尾数全为0

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

s=0,f=0,V=+s=1,f=0,V=

也可以表示NaN

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

规律

考虑k位阶码,n位小数

  • 值0.0的位模式全为0
  • 最下的非规格化值的位模式,除了最低位是1,其余为都是0,所以尾数M=f=2n,阶码E=1(2k11)=2k1+2,所以数字值是V=M×2E=2n2k12
  • 最大的非规格化值的位模式,阶码字段全为0,尾数字段全为1,所以尾数M=f=12n,写成1ϵ,阶码E=2k1+2,所以数字值是V=M×2E=(12n)×22k12
  • 最小的正规格化值的位模式,阶码字段最低位是1,其余全为0,所以尾数M=1+f=1,阶码E=1(2k11)=2k1+2,所以数字值是V=22k1+2
  • 值1.0的位模式,阶码字段最高位为0,其余都为1,尾数字段都为0,所以尾数M=1,阶码E=0
  • 最大的正规格化值的位模式,符号位为0,阶码最高位0,其余为1,尾数字段都为1,所以小数值f=12n,尾数M=1+f=22n,写作2ϵ,阶码值E=ebias=2k2(2k11)=2k11,所以数值为V=M×2E=(22n)×22k11=(12n1)×2k1

验证

1.0的位模式是

cpp
0011 1111 1000 0000 0000 0000 0000 0000

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

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

cpp
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的最大数

cpp
   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
cpp
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是一样的,所以如果放到判断语句里面,结果可能会出问题

cpp
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 !