【编程语言】C
一、浮点数的一般表示方法
- 一般表示法:3.14;
- 科学计数表示法:0.0314E2,即 0.0314 ∗ 1 0 2 0.0314*10^2 0.0314∗102,即3.14。
二、浮点数的类型
float
、double
、long double
。
浮点数的表示范围在头文件float.h
中定义。
三、浮点数存储规则
3.1 引入
#include <stdio.h>
int main()
{
int n = 9;
float* p = (float*)&n;
printf("%d\n", n); // 9
printf("%f\n", *p); // 0.000000
*p = 9.0f;
printf("%d\n", n); // 1091567616
printf("%f\n", *p); // 9.000000
return 0;
}
结果不符合预期,说明整数和浮点数在内存中的存储方式不同。
3.2 IEEE 754表示法
根据IEEE 754国际标准规定,任何一个二进制浮点数V都可以表示成: V = ( − 1 ) S ∗ M ∗ 2 E V=(-1)^S*M*2^E V=(−1)S∗M∗2E。
- ( − 1 ) S (-1)^S (−1)S表示符号位,S=0,V是正数;S=1,V是负数;
- M表示有效数字,范围是[1, 2);
- 2 E 2^E 2E表示指数位。
小数的十进制和二进制的相互转化规则:
- 小数点前的转化规则和整数一致,个位的权重是 2 0 2^0 20,十位的权重是 2 1 2^1 21,百位的权重是 2 2 2^2 22,以此类推;
- 小数点后,十分位的权重是 2 − 1 2^{-1} 2−1,百分位的权重是 2 − 2 2^{-2} 2−2,以此类推。
比如:
5.5(十进制)=101.1(二进制)=
1.011
∗
2
2
1.011*2^2
1.011∗22(二进制科学计数法)=
(
−
1
)
0
∗
1.011
∗
2
2
(-1)^0*1.011*2^2
(−1)0∗1.011∗22(IEEE 754表示法)。即V=101.1时,S=0,M=1.011,E=2;
-0.5(十进制)=-0.1(二进制)=
−
1.0
∗
2
−
1
-1.0*2^{-1}
−1.0∗2−1(二进制科学计数法)=
(
−
1
)
1
∗
1.0
∗
2
−
1
(-1)^1*1.0*2^{-1}
(−1)1∗1.0∗2−1(IEEE 754表示法)。即V=-0.1时,S=1,M=1.0,E=-1。
但是,这种表示方法难以精确保存一些小数,甚至一些小数无法精确保存,就导致了浮点数float
、double
、long double
的精度不同且有限的问题。比如:5.6的二进制近似是101.1001100110011001100110011001100110011001100110011…(无法精确保存)。
因为通过存储S、M、E三个值,就可以还原出来V。又因为M小数点前一定是1,所以,为了提高精度,内存中只存储S、M小数点后的数、E三个值。
对于32位浮点数float
,最高1位bit存S,后8位bit存E,再后23位bit存M小数点后的数。
对于64位浮点数double
,最高1位bit存S,后11位bit存E,再后52位bit存M小数点后的数。
S的范围是[0, 1],E的范围是[0, 255]或[0, 2047],M的范围是[0,
2
23
−
1
2^{23}-1
223−1]或[0,
2
52
−
1
2^{52}-1
252−1]。但实际情况中,E的取值可能是负数,因此为了兼顾E的正负数情况,会对E的真实值加上一个中间值,并把结果存入E对应的比特位。对于float
,中间数是127;对于double
,中间数是1023。
比如,float
类型数据,E的真实值是-1(十进制),那么存入值就是-1+127=126(十进制)。
对于float
类型的5.5,它的S=0,M=1.011,E=2,所以存储的内容是0(S) 10000001(E) 011(M) 00000000000000000000(末尾补0)
,十六进制是40 B0 00 00
。
3.3 取出S、M、E
S可以直接取出。E和M取出时,真实值分为3种情况:
- E不为全0/1
E的真实值为取出值-127/1023,M为1.内存中M值
。
- E为全0
E的真实值为-126(即1-127)/-1022(即1-1023),M为0.内存中M值
。(原理:
1.1
∗
2
−
127
=
0.11
∗
2
−
126
1.1*2^{-127}=0.11*2^{-126}
1.1∗2−127=0.11∗2−126(二进制))
- E为全1
如果内存中M为全0,表示正/负无穷大(取决于S为0/1);否则,表示非数值NaN(Not a Number)。
3.4 回归引入
#include <stdio.h>
int main()
{
int n = 9;
float* p = (float*)&n;
printf("%d\n", n); // 9
printf("%f\n", *p); // 0.000000
*p = 9.0f; printf("%d\n", n); // 1091567616
printf("%f\n", *p); // 9.000000
return 0;
}
解析:
存入n中的是9的补码00000000 00000000 00000000 00010001
。如果从float
视角进行看待,S=0,M=00000000000000000010001,E=00000000,是E为全0情况,所以浮点数
V
=
(
−
1
)
0
∗
0.00000000000000000010001
∗
2
−
126
V=(-1)^0*0.00000000000000000010001*2^{-126}
V=(−1)0∗0.00000000000000000010001∗2−126,是一个极小的浮点数,而%f
默认只打印到小数点后6位,所以是0.000000(如果尝试%.150f
打印到小数点后150位,会发现打印结果并非全0)。
*p = 9.0
是以浮点数的视角进行存储,S=0,M=1.001,E=3,存入内存中的二进制序列为0 10000010 00100000000000000000000
。如果以整数的视角去看待,把它当作补码,转换为十进制原码就是1,091,567,616,和打印结果一致。