前言

引用的链接均采用 CC BY-NC-SA 4.0 许可协议
如果对文章内容有不同意见,欢迎大家到留言板交流讨论。


正文

存储准则

根据国际标准IEEE754(电气和电子工程协会)(下文将简称IEEE754,任意一个二进制浮点数V可以表示成下面的形式:

1
2
3
4
(-1)^S * M * 2^E
(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。

注:
1、这里的有效数字范围是[1,2),可类比十进制中有效数字的范围是[1,10)。
2、指数部分以2为底数,类比十进制中指数部分以10为底数。


根据IEEE754标准规定:
对于32位的浮点数(float型),最高的一位是符号位S,接下里8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数(double型),最高的一位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
code

浮点数的存储与读取方式

浮点数的存储

S :正数S就是0,负数S则是1。

M:M一定是1到2(左闭右开) ,M中小数点前面必然是1,因此1存储在内存中无意义 (如果把1存储进内存反而多占了一位,会降低所保存数字的精度) 。其实只需要在内存中保存小数点后面的几位数字即可。

E:IEEE规定为E开辟的空间只可以存储无符号整数( unsigned int ),但是实际上E却可能是负数,比如0.5。因此IEEE规定E的真实值必须加上一个中间值 (float型E中间值为127,double型为1023),在读取这个浮点数时需要重新减去中间值。


浮点数的读取

1.E不全为0且不全为1

S:直接拿出。

M:从内存中拿出后在前面加上1和小数点

E:减去中间值。

2.E全为0

M:M前面不加1而是加0。

E :E = 1-127 = -126(或者1-1023=-1022)

这样实际上是为了表示无穷小

3.E全为1

S:直接拿出。

M:从内存中拿出后在前面加上1和小数点

E :减去中间值。

由于E加上后127为全1,也就是说E的真实值为128,即该浮点数指数部分是128,显然这是一个极大的数,此时表示±∞(正负号由S决定)

举例

1
2
3
4
5
6
7
8
9
float 5.5
首先转换为2进制 101.1
转换形式
(-1)^0*1.011*2^2
由分析可知 S=0 E=129 M=1.101
整理得到
S E M
0 10000001 01100000000000000000000
将其转化为十六进制为 0x40b00000(可从内存访问得到)

关于内存涉及到大小端存储,将在下文详解

内存的大小端存储

首先,让我们先回答,什么是大小端?
对于一个由2个字节组成的16位整数,在内存中存储这两个字节有两种方法:一种是将低序字节存储在起始地址,这称为小端(little-endian)字节序;另一种方法是将高序字节存储在起始地址,这称为大端(big-endian)字节序。
简单来说,大端是高字节存放到内存的低地址,小端是高字节存放到内存的高地址
现在的绝大多数基于X86和ARM的IDE都是小端存储


那么如何确定内存的大小端呢?
我给出一段C语言编写的代码,供大家学习参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int check_sys()//检测大小端
{
int a = 1;
return *(char*)&a;
//返回1,小端,返回0,大端。
}
int main()
{
if (check_sys)
printf("小端\n");
else
printf("大端\n");
return 0;
}

float型为例,我们都知道1的补码是

1
0000 0000 0000 0000 0000 0000 0000 0001

也就是十六进制的

1
0x00000001

而我们又知道,char*这个指针类型决定了指针解引用操作符(*)只能访问一个字节。通过上述对大小端的描述,我们应该不难理解程序的实现原理。

因此,当大家看见2.3关于浮点数5.5的举例,内存监视显示的是

1
00 00 b0 40

也应该不会感到奇怪了。