C语言| 浮点数在数据中存储&如何用二进制表示浮点数

目录

一、引入

二、浮点数的存储

2.1 如何将十进制浮点数转化为二进制

2.1.1 将十进制5.875转化为二进制

2.1.2  将十进制5.36转化为二进制

2.2 如何用二进制表示浮点数

2.2.1 国际标准IEEE 754 对二进制浮点数表示的规定

2.2.2 浮点数存的过程

2.2.3 浮点数取的过程

三、举例验证

C语言学习中,如有错误,欢迎在评论区指正!希望您可以点一个免费的赞,祝您生活愉快!


一、引入

在学习本篇文章之前,不妨先看看下面的代码:

#include <stdio.h>
int main() 
{
	int n = 5;
	float* p = (float *) & n;
	printf("n =%d\n", n);
	printf("*p =%f\n", *p);
	*p = 5.0f;
	printf("n =%d\n", n);
	printf("*p =%f\n", *p);
	return 0;
}

它的运行结果是什么?是不是都是5或者5.0呢?

我们来看一下运行结果:

运行结果和我们的猜测有很大的出入,为什么会这样呢?

我们不妨分析一下,程序中有两次赋值:第一次赋值是 n=5; 第二次赋值是 *p=5.0; 指针p指向了类型强转为float*的n的地址。

第一次printf,传入值是n,打印结果是5,这毫无疑问。第二次printf,传入值是*p,这就需要分析一下了,*p是对p所指向的地址上的内容进行解引用,该地址在整型存储数据的形式的解引用下是5,可p的指针类型是float*,这导致解引用所依靠的是浮点数存储数据的形式,最后打印出0,这俨然表明浮点数的存储形式与整型不同。

进行完上述步骤后,*p被赋值为5.0,这意味着我们将p指向的地址上的内容改为根据浮点型存储形式进行解读得到的结果为浮点数5.0的二进制。

第三次printf,传入值是n,n是整型类型,它以整型的存储形式对地址的内容进行解读,得到的值是1084227584,所以打印结果就是1084227584。第四次printf,传入值是*p,以float型的存储形式对地址的内容进行解读,所以打印结果是5.0

我们已经学过整型、字符型在数据中的存储,它们都是以二进制补码的形势存放在相应的内存中,但之前我们对浮点数的存储形式避而不谈,根据上述的例子,我们很容易推断出它的存储形式不同于整型、字符型。那它是如何解决小数部分与整数部分的区分与存储呢?本篇文章将带给你答案。

二、浮点数的存储

2.1 如何将十进制浮点数转化为二进制

我们对十进制整数进行二进制转化时采用除二取余,直到被除数变为0,然后逆序输出。那浮点数呢?浮点数的整数部分仍采用上述方法,而其小数部分,则采用乘2取个位,直到小数部分变为0,然后正序输出。(如果小数部分一直不变为0,则对其进行截断,截断位数由数据类型是float还是double以及自身决定,之后会讲到)

没听懂,没关系,我们将给出两个例子。

2.1.1 将十进制5.875转化为二进制

整数部分5利用除2取余或者口算,很容易得到是101。

小数部分计算过程请看下图:

进行乘二取个位后,我们得到小数部分的转化结果为0.111

所以5.875转化的最终结果为101.111

2.1.2  将十进制5.36转化为二进制

整数部分5二进制依旧是101

小数部分进行二进制转化的计算过程如下图:

对其进行乘二取个位,我们发现它的计算结果是无限不循环,应对结果进行截断。

所以5.36转化为二进制的结果为101.01011100...

根据上述两个例子,再进行一些练习,相信你一定能熟练掌握浮点数转二进制的方法。

2.2 如何用二进制表示浮点数

我们已经学会怎么将十进制浮点数转化为二进制,那它的二进制的正确表示形式是什么呢?

2.2.1 国际标准IEEE 754 对二进制浮点数表示的规定

根据国际标准IEEE 754,任何一个二进制浮点数 V 可以表现为下面的形式: 

例如: 2.1.1中的5.875的二进制101.111,相当于1.01111 x 2^2,按照上面的格式,可以得到S=0,M=1.01111,E=2。

2.2.2 浮点数存的过程

IEEE 754规定:

对于32位的浮点数,最⾼的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M

对于64位的浮点数,最⾼的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M

如图:(取自比特就业课教材)

IEEE 754 对有效数字M和指数E,有一些特别的规定。

前面提到,M>1&M<=2,换言之,M可以写成1.XXXXXXX的形势,其中XXXXXXX表示小数部分。

IEEE 754规定,在计算机内部保存M时,默认这个数的第⼀位总是1,因此可以被舍去,只保存后面的部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第⼀位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第⼀位的1舍去以后,等于可以保存24位有效数字。

按照这些规定,2.1.2中的数1.0101011100... x 2^2,如果储存在float中,将在该数的小数部分的第二十三位进行截断,舍弃第二十四位及以后的数,那么它的小数精度为21位(整数101的首位被舍弃,其余占两位)如果储存在double中,将在小数的第五十二位进行截断,舍弃第五十三位及以后的数,小数精度为50位。所以2.1.2中的数在float的情况下小数算到第21位,在double的情况下小数算到第50位。

而对于指数E,其类型为无符号整型,这意味着如果E为8位(float),取值范围为0~255,如果为11位(double),取值范围为0~2047。当我们储存的浮点数很小时,指数可能会是负数,为了让E能够存储负数,存入内存时E的真实值必须再加上一个中间数,如果是8位的E,要加的中间数是127,如果是11位的E,要加的中间数是1023。

例如2.1.1中的二进制数的指数存储在内存(float类型)中的E的值为129(127+2),即10000001

2.2.3 浮点数取的过程

S取出时不变,M与E变。

指数E从内存中取出可以分为三种情况:

计算值E的二进制全为0

此时,浮点数的指数E的真实值等于-127+1(或者-1023+1),有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数(这导致E+1)。这样做是为了表示±0,以及接近于0的很小的数字。此时输出,浮点数为0

计算值E的二进制全为1

此时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)

计算值E的二进制不全为0或不全为1

此时,将指数E的值减去127,得到真实值,M的第一位补上1(之前存储时为了利用空间将第一位1抹去),S不变,然后进行输出,得到存储的浮点数的值。

三、举例验证

我们利用2.1.1中的数5.875进行验证。 

5.875 按照国际标准进行二进制浮点数表示为1.01111 x 2^2,其中S=0,M=1.01111,E=2

存储在内存中时(float类型):S=0,M=01111,E=129,E转化为二进制为10000001

所以float a=5.875 的内部的存储如下:(即40 bc 00 00)

01000000 10111100 00000000 00000000//40 bc 00 00 

 对下面的代码打断点进行调试,在内存监视中验证我们的结果(注意,一定要调试到第五行之后,第五行之前a的值未进行初始化,即使监视内存也是错误的值)

#include <stdio.h>
int main()
{
    float a = 5.875;
    printf("a= %f\n", a);
    return 0;
}

监视a的地址的内容如下:

因为VS采用的存储方式为小端存储 ,所以需要从右往左读,40 bc 00 00,与我们的理论推导一致。验证成功!

本文完!

C语言学习中,如有错误,欢迎在评论区指正!希望您可以点一个免费的赞,祝您生活愉快!

  • 26
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值