要想了解浮点数在内存中的存储,那么首先我们来看这样一段代码。
#include <stdio.h>
int main()
{
int n = 9;
float *pFloat = (float *)&n;
printf("n的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
*pFloat = 9.0;
printf("num的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
return 0;
}
看一下输出结果。
同样是n和*pFloat,输出结果怎么会差距这么大呢?
我们得了解浮点数在计算机内部的表示方法。
单精度浮点数(float):
单精度浮点数使用 32 位(4 字节)存储,其格式一般如下:
- 符号位:1 位,表示数的正负。
- 指数部分:8 位,用于存储指数的值。
- 尾数部分:23 位,用于存储尾数的值。
按照 IEEE 754 标准,单精度浮点数的存储格式如下:
| 符号位 | 指数部分 | 尾数部分 |
|--------|--------------|----------------------|
| 1bit | 8 bits | 23 bits |
双精度浮点数(double):
双精度浮点数使用 64 位(8 字节)存储,其格式一般如下:
- 符号位:1 位,表示数的正负。
- 指数部分:11 位,用于存储指数的值。
- 尾数部分:52 位,用于存储尾数的值。
按照 IEEE 754 标准,双精度浮点数的存储格式如下:
| 符号位 | 指数部分 | 尾数部分 |
|--------|--------------|-------------------------------------|
| 1bit | 11 bits | 52 bits |
浮点数的存储示例:
根据IEEE 754标准,任意一个二进制浮点数V可以表示成以下形式:
(-1)^s * M * 2^E
其中:
- (-1)^s 表示符号位,s为0时表示正数,s为1时表示负数。
- M 表示尾数部分(也称为有效数字或者尾数),是一个小于1并且大于等于0的二进制小数,通常是一个1位的整数和一个小数点后的小数位。
- 2^E 表示指数部分,E是指数值。
假设我们要表示的单精度浮点数为 V = -12.75
。
首先,将 -12.75
转换为二进制表示:
- 整数部分
-12
的二进制表示为1100
。 - 小数部分
0.75
的二进制表示为0.11
。
将整数部分和小数部分组合起来,得到 -12.75
的二进制表示为 -1100.11
。
然后,按照 IEEE 754 标准,我们需要将 -1100.11
表示为科学计数法的形式 (-1)^s * M * 2^E
。
- 符号位 s 为 1,因为
-12.75
是负数。 - M 为
1.10011
(即尾数部分,其中小数点左侧的第一位是隐藏的,因此省略了)。请注意,这是一个标准化表示,因为尾数的最高位是1。 - E 为 3,因为小数点左移3位得到
1.10011
。
将以上各部分组合起来,我们得到 -12.75
的单精度浮点数表示为:
s = 1 (负数) M = 1.10011 E = 3 即:(-1)^1 * 1.10011 * 2^3
这就是 -12.75
的单精度浮点数表示。
指数部分(E)的特殊规定:
-
偏移量表示:指数部分通常使用偏移量表示法。这意味着指数的真实值与存储值之间存在一个固定的偏移量。
-
指数范围:对于单精度浮点数,指数部分通常使用8位,允许指数范围为-126到+127(实际上,偏移量是127,因此指数的有效范围是-126到+127)。对于双精度浮点数,指数部分通常使用11位,允许指数范围为-1022到+1023(偏移量是1023)。
-
特殊值:指数部分全为0表示的是非规格化数或零,而指数部分全为1表示的是特殊值(如无穷大和NaN)。
尾数部分(M)的特殊规定:
-
标准化表示:在标准化表示中,尾数的最高位始终是1,并且不存储在浮点数中。因此,尾数部分实际上是一个小数,范围在1到2之间(或者在0到1之间,如果是非规格化数)。
-
隐藏位:由于标准化表示中不存储尾数的最高位,因此这个最高位被隐含地假定为1。因此,实际存储的尾数部分只包含小数点后面的位数。
-
精度与范围的平衡:尾数部分的位数确定了浮点数的精度。更多的位数可以提供更高的精度,但会减少表示的范围。
通过这些特殊的规定,IEEE 754标准确保了浮点数可以有效地表示各种大小和精度的数值,并在各种情况下保持了数值的一致性和可靠性。