C语言笔记之数据类型(三)

浮点型数据

一、非整数的表示

除了整数,平时的计算也离不开非整数,即带有小数部分的那些数。在数字系统中,整数和非整数合称为有理数,有理数和无理数合称为实数(好吧,这和本文主旨没关系,但为了显示一下我曾经是数学系的学生。。。)

非整数由一个”.“号来凸显,十进制表示法中,”.“号左边的数字的权为10的正整数幂,幂值按离小数点的距离远近依次为0、1、2...而右边为10的负整数幂,幂值由近及远依次为-1、-2、-3...

例如:12.25 = 1 * 10 ^ 1 + 2 * 10 ^ 0 + 2 * 10 ^ (-1) + 5 * 10 ^ (-2)

二进制表示是类似的,不同的是底数由10变成了2,如:11.01 = 1 * 2 ^ 1 + 1 * 2 ^ 0 + 0 * 2 ^ (-1) + 1 * 2 ^ (-2) =3.25

然而,就像十进制无法精确的表示1/3等分数一样,二进制在表示非整数时,也是近似的。造成这一现象的原因在于,我们书写一个数字时,编码长度总是有限的,比如1/3 = 0.3333333.....我们不可能把3一直写下去;而对于二进制,我们无法用它精确地表示十进制的0.4,我感觉,无论哪种进制,在表示数字时,都是存在缝隙的。


二、非整数的存储

要存储形如11.101二进制的非整数,一个自然的想法就是分为整数部分和小数部分分别存储,这就是所谓的定点表示法——它规定小数点左边几个位用来表示整数;右边几个位表示小数。这种办法很直观,但是效率低下,而且对于超出定点范围的数它只能进行截断,从而无法精确表示。

注意到,一个二进制的非整数,其小数点向左移动一位相当于原数除以2;向右移动一位相当于乘以2。比如:

11.01 × 2 = 110.1;11.01 / 2 = 1.101

再联想到十进制的科学记数法,我们发现二进制也可以表示成x * 2 ^ y这种形式,其中x是一个定点数,它的小数点左边即整数部分只有一位,且只能是1不能是0(因为如果是0,我们总可以将小数点向右移动直到遇见1);右边只能有固定位数的小数部分。y是2的幂,其大小为小数点移动的位数,当然根据向左还是向右,要添加适当的正负号。

这样,每个二进制非整数都要通过移动小数点来变成以上形式(该过程称为规范化),所以这种表示法称为浮点表示法。用这种方法表示的二进制非整数,我们只需要存储三个部分:数的符号,尾数,指数(小数点移动的位数含方向)。

三、单精度和双精度

IEEE定义了几种存储浮点数的标准,常见的两种就是单精度和双精度。单精度格式采用32位即4个字节来存储一个实数(可以包括整数),其中符号位占1位,尾数占23位,指数占8位;双精度格式采用8个字节64位存储一个实数,符号一位,尾数52位,指数11位。

下面谈谈这三部分实际是如何存储的。符号位无需多说,不是0就是1。关键在于指数的存储,它的存储形式决定了尾数的形式。指数存储为二进制时分为三种情况:全为0;全为1;其他。

1、其他情况。。。

和想象中的不同,这种情况下指数并非以二进制补码的形式直接存储的,不过,无论怎样,它必须是一个有符号数,因为显然存在指数为负数的情况。

我们把指数存储的实际位模式当成一个无符号数,其值为e;而指数的实际值为E,那么E = e - Bias,Bias是一个偏移值,大小为2 ^ (k - 1) - 1,k是指数所占的位的长度。计算可知,对于单精度,Bias=127;对于双精度,Bias=1023。

由于此时e不全为0且不全为1,那么E的范围就是:-126~+127(单精度)或-1022~+1023(双精度)。

此时,尾数就只存储小数点右边的小数部分,而左边的部分默认为1。尾数的实际值 M=1+f,其中 f 是将实际存储的位模式转换成小数得到的值,有0<= f < 1

2、全为0

这时,E = 1 - Bias = -126 或 -1022;而M = f 。

在上面那种情况下,我们是无法表示0.0的,因为M 总是默认大于等于1。现在,我们只需要将f 的位模式全部置为0,就可以表示0了。然而麻烦的是,还有一个符号位,IEEE规定,+0.0和-0.0并不完全相同。

3、全为1

此时,若尾数的位模式也全为0,则这个浮点数被解读成无穷,根据符号位,还分为正无穷和负无穷;若尾数的位模式不全为0,那么这个浮点数被解读为NaN,即not a number,比如对-1求平方根,就会返回这样的结果。


四、运算与类型转换

1、舍入

由于长度的限制,浮点数只能近似的表示实数。当一个实数超出浮点数所能表示的精度范围时,我们需要一种方式来舍弃一部分数值来得到一个近似值。比如,用2.0来表示1.99999。

IEEE定义了四种舍入方式:向上舍入、向下舍入、向零舍入、向偶舍入。一张图说明(《深入理解计算机系统》):

技术分享

2、运算

最重要的一点就是:浮点数加法运算不具有结合性!无论加法还是乘法。

比如,假入n是一个非常大的浮点数,那么:3.14 + (n - n) = 3.14, 而3.14 + n - n = 0

其他的就没有什么特殊之处了,只是最后结果可能会被舍入。

3、C语言中的类型转换

C语言提供了两种不同的浮点数据类型:float和double,可惜它们并不总是对应单精度和双精度,因为C语言标准并不要求机器使用IEEE标准。不过大多数情况下,它们是对应的。

下面是类型转换,整形以int为例(以后会补上实验)

int转向float,数字不会溢出,但会舍入;

int、float转向double时,保留精确原值;

浮点型数据转换成int时,值会向零舍入;

double转向float,溢出成为无穷或者由于double精度太高而被舍入。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。