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来表示阶码字段的二进制数,范围是00000001∼11111110,emin=1,emax=254
E=e−bias偏置量的值与阶码的位数相关
bias(float)=28−1−1=127bias(double)=211−1−1=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=1−biasM=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=2−n,阶码E=1−(2k−1−1)=−2k−1+2,所以数字值是V=M×2E=2−n−2k−1−2
- 最大的非规格化值的位模式,阶码字段全为0,尾数字段全为1,所以尾数M=f=1−2−n,写成1−ϵ,阶码E=−2k−1+2,所以数字值是V=M×2E=(1−2−n)×2−2k−1−2
- 最小的正规格化值的位模式,阶码字段最低位是1,其余全为0,所以尾数M=1+f=1,阶码E=1−(2k−1−1)=−2k−1+2,所以数字值是V=2−2k−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−ϵ,阶码值E=e−bias=2k−2−(2k−1−1)=2k−1−1,所以数值为V=M×2E=(2−2−n)×22k−1−1=(1−2−n−1)×2k−1
验证
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的是一样的,所以,最好不要用这种浮点数的比较