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的是一样的,所以,最好不要用这种浮点数的比较