【C语言】浮点数在内存中的存储原理

浮点数是什么?

带有小数点的数就是浮点数,浮点数能表示小数整数。浮点数家族有3种类型:float,double和long double类型。

例子:

  • 浮点数3.14表示小数3.14
  • 浮点数3.0表示整数3

为什么叫做浮点数?

在数学的科学计数法中,小数点在不同的位置也能表示同一个小数。
例子:

  • 一个小数1234.5
  • 用科学计数法可以等价于1.2345 x 103
  • 也可以等价于12.345 x 102

观察这3个相同值中的小数点,像是在左右“浮动”,所以可以叫做浮点数。

浮点数存储规则

浮点数的规定标准是由IEEE(Institute of Electrical and Electronics Engineers)制定的IEEE 754标准。IEEE 754标准为浮点数提供了一套统一的表示方法和运算规则,使得不同计算机系统之间可以进行浮点数的交互和计算。

根据IEEE 754标准,任意的二进制浮点数由三个部分组成:符号位、指数位和尾数位

  • 符号位用于表示浮点数的正负;
  • 指数位用于表示浮点数的数量级;
  • 尾数位用于表示浮点数的精度。

用式子表示任意的二进制浮点数:(-1)S * M * 2E

  • S是符号位,如果S等于0就是正数,S等于1就是负数;
  • E是指数位,E是几,就是乘以2的几次方;
  • M是尾数位,可以理解为小数点后面的数。

计算机只需存储符号位S,指数位E和尾数位M,就能存储一个浮点数。

已知一个十进制的浮点数,如何获取S,E和M呢?下面通过一个例子说明:

  1. 设一个十进制的浮点数为V并且令V = 5.5(假设这个浮点数是5.5)
  2. 把5.5转换为二进制的浮点数101.1。具体的换算过程如下图:
    (需要注意二进制浮点数的权重,小数点右边从2的-1次方开始再到2的-2次方,以此类推)
    Alt
  3. 把二进制的浮点数101.1转换成二进制的科学计数法的形式:1.011 x 22
  4. 最后在1.011 x 22的前面再补上符号位即可,因为5.5是正数,所以乘上-1的0次方:(-1)0 x1.011x22
    此时就能在(-1)0 x1.011x22 式子中得知S是0,E是2,M是1.011
    Alt
    再举一个浮点数为9.0的例子:
    Alt

IEEE 754规定,存储浮点数时,根据精度的不同有两种存储方式:

  1. 32个比特位的单精度浮点数,其中最高的1位是符号位S(红色区域),紧接着的8个比特位是指数位E(绿色区域),在指数位后面的23个比特位是尾数位M(紫色区域)。
    Alt
  2. 64个比特位的双精度浮点数(双精度浮点数比单精度浮点数更精确),其中最高的1位是符号位S(红色区域),紧接着的11个比特位是指数位E(绿色区域),在指数位后面的52个比特位是尾数位M(紫色区域)。
    Alt

在上面提到过,内存想要存储一个浮点数,只需存储符号位S,指数位E和尾数位M即可。
根据IEEE 754的规定,之前的例子中算出来的指数位E和尾数位M还不能直接存储到内存中,E和M还有额外的要求:

  • 尾数位M:规定M的取值范围是 1<=M<2,所以存储M时,整数部分的1直接省略,这样做的目的是可以少存储一个比特位,读取的时候再将整数部分的1补上即可,这就是为什么M叫做尾数位,因为存储M时只存储小数部分
  • 指数位E:规定存储E时,首先E得是一个无符号整型(unsigned int 类型)。
    在存储32个比特位的单精度浮点数中,E(8bit)的取值范围是0 ~ 255;
    在存储64个比特位的双精度浮点数中,E(11bit)的取值范围是0 ~ 2047。
    但因为实际上科学计数法中的E可以是负数(以0.5为例)
    Alt
    为了避免E存储的是负数,IEEE 754规定存储E的真实值时,需要先加上一个中间数再存储到内存中。
    在存储32个比特位的单精度浮点数中,这个中间数是127;
    在存储64个比特位的双精度浮点数中,这个中间数是1023。
    只有概念不好理解,举个例子,还是以0.5为例:
    0.5转换成二进制的科学计数法中,E的真实值是-1,
    如果0.5是以32位浮点数存储,那么E的真实值-1先加上127得到126,再把126存储到内存中;
    Alt
    如果0.5是以64位浮点数存储,那么E的真实值-1先加上1023得到1022,再把1022存储到内存中。
    Alt

最后以5.5为例,完整的演示5.5怎么存储到内存中:
Alt
Alt


在内存中取出一个浮点数时,指数位E分为3种情况:

  1. E全为0,此时的浮点数一定是非常接近于0的数,直接表示0。
    在32位上E的真实值是-126(1-127),在64位上E的真实值是-1022(1-1023),
    M的整数部分不还原1,而是还原0,变成0.xxxx。
  2. E全为1,如果M全为0,表示正无穷大或负无穷大。
  3. 一般情况:把指数E的存储值减去127(或1023)得到E的真实值,
    M的整数部分还原1,变成1.xxxx,符号位直接取,
    最后用(-1)S * M * 2E得到结果。

一道例题

问以下代码分别输出的是什么:

#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;
}

output:
Alt
注释:

#include<stdio.h>
int main()
{
    
    
	int n = 9;//整型的空间大小是32位
	// 00000000 00000000 00000000 00001001

	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n); //以整数的形式打印变量n中的数据9

	//通过浮点型指针pFloat访问00000000 00000000 00000000 00001001的时候,
	//是以浮点数的角度看待00000000 00000000 00000000 00001001的,
	//所以00000000 00000000 00000000 00001001会被看做
	//    0 00000000 00000000000000000001001
	//    S E        M
	//    0 -126     0.00000000000000000001001
	// E在内存中是全0,所以E是-126
	printf("*pFloat的值为:%f\n", *pFloat);//%f小数点右边只打印6位
	*pFloat = 9.0;
	//1001.0 - 二进制的9.0
	//1.001 * 2^3 - 二进制的科学计数法
	//(-1)^0 * 1.001 * 2^3 
	//S = 0  E = 3  M = 1.001
	//0 10000010 00100000000000000000000
	//9.0是以浮点数的形式放进变量n,但是变量n以整数的形式打印时,
	// 会被认为01000001000100000000000000000000是补码,最高位是符号位,后面是数值位
	//0 1000001000100000000000000000000 
	printf("num的值为:%d\n", n);//1,091,567,616
	printf("*pFloat的值为:%f\n", *pFloat);//9.0
	//注意打印的数据和打印的类型需要匹配
	return 0;
}

完。

猜你喜欢

转载自blog.csdn.net/weixin_73276255/article/details/131724430
今日推荐