浮点型的原理介绍及在内存中的存储形式

浮点型的原理介绍及在内存中的存储形式

C语言提供了浮点数据类型,单精度浮点数float和双精度浮点数double。浮点数属于不精确的数据类型,本文将通过float类型的原理和在内存中的存储形式来介绍浮点型不精确的原因。以float类型为例,来展示C语言中浮点型的神秘之处。

float类型介绍

float是C语言的基本数据类型中的一种,表示单精度浮点数。C语言规定单精度浮点型在内存占用4个字节,精度为7位,取值范围为:3.4*10^-38 ~3.4*10^38或者-(3.4*10^-38 ~3.4*10^38)。依据IEEE规定 :float在存储中都分为三个部分:

1、符号位(Sign) : 0代表正,1代表为负

2、指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储

3、尾数部分(Mantissa):尾数部分

其中float和double的存储方式如下图所示:

数据类型 符号位 指数 尾数
float 1bit 8bit 23bit
double 1bit 11bit 52bit

float在内存中的存储规则

计算机只能识别二进制,所以float类型在内存中也是通过二进制存储的。因此float类型需要转换为二进制形态,转换步骤如下:

1)先将这个实数的绝对值化为二进制格式。  

2)将这个二进制格式实数的小数点左移或右移n位,直到小数点移动到第一个有效数字的右边。  

3)从小数点右边第一位开始数出二十三位数字放入第22到第0位。       

4)如果实数是正的,则在第31位放入“0”,否则放入“1”。      

5)如果n 是左移得到的,说明指数是正的,第30位放入“1”。如果n是右移得到的或n=0,则第30位放入“0”。  

6)如果n是左移得到的,则将n减去1后化为二进制,并在左边加“0”补足七位,放入第29到第23位。如果n是右移得到的或n=0,则将n化为二进制后在左边加“0”补足七位,再各位求反,再放入第29到第23位

注意:将绝对值转换为二进制之后,小数点左移n位,n即为该数的指数,指数为阶码为n+127。这其实就是第5和6步骤运算得到的结果。

示例:

通过上述转换规则计算两个浮点数的二进制。数字 13.6 和 数据 1.3

符号位比较简单,存储的是正数那么符号数就是0。如果是负数,则为1。

下面以13.6为例说明指数与尾数的表示方法。首先,我们取出13.6的整数部为13。对其使用短除法(对该数除以2,直至不能再除的一种方法)结果如下:

将各余数自下而上排列,则得到了13的二进制表示。之后,取出13.6的小数部分为0.6对其每次乘2取出整数留下小数,直至得到1。结果如下:

 

这样我们就得到了13.6的二进制表示。为1101.100110011001....... 之后我们需将小数点移动至整数部只有一位。移动后得到1.101100110011001....... 。在此我们将小数点移动了三位。因而三即是该数的指数。而阶码则为指数+127(加127是C语言的内在逻辑,在此我们并不深究。)因而我们得到了13.6的指数,为130。二进制表示10000010。最后的尾数,将原先得到的无线循坏的二进制取前23位即可,如果第24位为0直接舍弃,如果第24为1,则第23位加1。(float类型的尾数有23位,对于没有循环的数字,在后补齐0即可)。因此我们得到13.6的存储数据:

0 1000 0010 101 1001 1001 1001 1001 1010 1001......(第24位为1,所以尾数加1变成10,其他的舍弃)

同理得到1.3在内存的存储数据如下:

0 0111 1111 010 0110 0110 0110 0110 0110   0110......(第24为0,后边的直接舍弃)

接下来通过C程序调试,来查看浮点数在内存中的存储形式:

#include <stdio.h>
int main()
{
    float a = 13.6;
    printf("%p
", &a);
    printf("%.9f
", a);
    return 0;
}

 对上述程序进行调试,根据打印出变量a的地址在内存中找出该浮点数的存储形式:

可以看出变量a在内存中的存储形式为  41 59 99 9a。(小端存储:较高的有效字节存放在较高的存储器地址,较低的有效字节存放在较低的存储器地址)将上述十六进制转换为二进制 0100 0001010110011001100110011010 。该结果与上文中我们计算出的数据一致。对于数字 1.3 使用同样的方式验证。

从内存读取float类型

从上文已经知道了浮点型在内存中如何存储,那是如何读取呢,读取之后为什么会出现精度丢失的现象。本小节来演示下float类型的读取。规则如下:

1)将第22位到第0位的二进制数写出来,在最左边补一位“1”,得到二十四位有效数字。将小数点点在最左边那个“1”的右边。  

2)取出第29到第23位所表示的值n。当30位是“0”时将n各位求反。当30位是“1”时将n增1。  

3)将小数点左移n位(当30位是“0”时)或右移n位(当30位是“1”时),得到一个二进制表示的实数。  

4)将这个二进制实数化为十进制,并根据第31位是“0”还是“1”加上正号或负号即可

注意:读取和存储是相反的过程,阶码也可以通过当前值 -127 来进行转换。

示例:

从上文中,我们已经从内存中获取到 13.6 的存储形式为 0100 0001 0101 1001 1001 1001 1001 1010 现在通过步骤进行该数据的读取:

1、获取尾数得到24位有效数字 1.101 1001 1001 1001 1001 1010 

2、00 0001 0 值为2,第31位是1,所以将n+1。得到n为 00 0001 1

3、将小数点左移3位得到二进制实数 1101. 1001 1001 1001 1001 1010

4、将小数点前后分别计算十进制数字,1101转换为十进制为13。0. 1001 1001 1001 1001 1010 转为10进制数字如下所示:

得到最终实数为 13.6000003814697266。

所以当获取数字时,如果小数位的有效数字小于7位时,该数字是精确的,超过7位数据就出现了不精确位,所以说浮点型是不精确的数据类型。

同样的办法,我们可以获取到实数 1.3 经过转换后得到的实数为 1.2999999523162842

我们通过这两个例子可以得出结论,当读取数据时,根据需要获取的精度,之后的数据进行四舍五入之后舍弃。

结论:

经过对程序的调试,我们已经验证了浮点型不精确的原因以及float有效精度为6位。所以在使用浮点型时应特别注意精度丢失现象,同样的double类型有效精度为15位。计算方式和float类似,有兴趣的同学可以自行验证。

本文参考:https://blog.csdn.net/zcliatb/article/details/41078595

原文地址:https://www.cnblogs.com/jkin/p/14044642.html