C语言标准C89里规定了3种浮点数,float型、double型和long double型,常见的浮点型长度为float型占4个字节,double型占8个字节,long double型长度要大于等于double型,下面将以float型为例进行介绍,double型和long double型只是比float型位数长,原理是一样的 。
float型可以表示的十进制范围是-3.402823466e38~3.402823466e38,而作为同为4个字节的定点数却只能表示-2147483648~2147483647的范围,使用同样的内存空间,浮点数却能比定点数表示大得多的范围,这是不是太神奇了?既然浮点数能表示这么大的范围,那么我们为何不使用浮点数来代替定点数呢?先不说浮点数实现起来比较复杂,有些处理器还专门配置了硬件浮点运算单元用于浮点运算,主要原因是浮点数根本就无法取代定点数,因为精度问题。鱼和熊掌不可兼得,浮点数表示了非常大的范围,但它失去了精度。
ANSI/IEEE Std 754-1985标准
IEEE 754是最广泛使用的二进制浮点数算术标准,被许多CPU与浮点运算器所采用。IEEE 754规定了多种表示浮点数值的方式,下面介绍32位二进制的float浮点类型。它被分为3个部分,分别是符号位S(sign bit)、指数偏差E(exponent bias)和小数部分F(fraction),这三部分都是对应二进制码的。
浮点表示的一般形式为(科学技术法规则):R=(S) * (1 + F) * 2e (R:实数 S:正负符号 F:小数部分 e:指数,不同于指数偏差)。
例如,3.75的二进制码为11.11,将该二进制码按科学计数法表达为1.111,则向左移动了1位,即e=1,E=e+127=128,F记录的便是小数部分,实际为111000...000。
下面介绍一下小数部分转换为二进制码的方式。类似于整数的形式(如7 = 22 + 21 + 20),小数部分的转换形式为2-1、2-2、2-3、2-4......,例如0.5 = 2-1,即二进制码为0.1,0.05 = 2-5 + 2-6 + 2-9 + 2-10 + 2-13 + 2-14 +...... (可无限循环),即二进制码为0.00001100110011......。如果都以16位计,那么7的二进制码为0000000000000111,0.5的二进制码为0.1000000000000000,0.05的二进制码为0.0000110011001100。这是如何换算出来的呢?且看下面的算法便知:
换算0.5,乘法结果初始为0.5,所有乘数为2,每次用乘法结果 * 乘数,得到新的乘法结果,结果中的整数部分被提取出来,剩余的小数部分继续参加下一次乘法运算,直到剩余小数部分为0,或者无终点(无限循环)。根据表格中的整数部分可知,二进制为0.1。
整数部分 | 乘数 | 乘法结果 | 剩余小数部分 |
0. | 2 | 0.5 | 0.5 |
1 | 1 | 0 | |
结束 |
换算0.05,乘法结果初始为0.05,所有乘数为2,每次用乘法结果 * 乘数,得到新的乘法结果,结果中的整数部分被提取出来,剩余的小数部分继续参加下一次乘法运算,直到剩余小数部分为0,或者无终点(无限循环)。根据表格中的整数部分可知,二进制为0.00001100110011......。
整数部分 | 乘数 | 乘法结果 | 剩余小数部分 |
0. | 2 | 0.05 | 0.05 |
0 | 2 | 0.1 | 0.1 |
0 | 2 | 0.2 | 0.2 |
0 | 2 | 0.4 | 0.4 |
0 | 2 | 0.8 | 0.8 |
1 | 2 | 1.6 | 0.6 |
1 | 2 | 1.2 | 0.2 |
0 | 2 | 0.4 | 0.4 |
0 | 2 | 0.8 | 0.8 |
1 | 2 | 1.6 | 0.6 |
1 | 1.2 | 0.2 | |
无限循环 |
例1:float型浮点数125.5转化成32位二进制浮点数。
125.5的整数和小数部分的二进制码分别为1111101和0.1,于是125.5的二进制码为1111101.1,按科学技术法写为1.1111011*26,即向左移6位,则e=6,E=e+127=133,133的二进制码为10000101。而1.1111011把整数部分的1去掉后,剩下小数部分为1111011,之后补0至23位,构成F。所以125.5的32位二进制浮点数为:
0 10000101 11110110000000000000000
例2:float型浮点数0.5转化成32位二进制浮点数。
类似的,0.5的二进制码为0.1,按科学技术法写为1.0*2-1,即向右移1位,则e=-1,则E=e+127=126,126的二进制码为01111110。而1.0把整数部分的1去掉后,剩下小数部分为0,之后补0至23位,构成F。所以0.5的32位二进制浮点数为:
0 01111110 00000000000000000000000
几个特殊的情形
浮点数的精度
从前文中可以看到,1.xxx这类浮点数中,F部分最小的是2-23,对应的十进制数为1.00000011920928955078125,可以精确表示到小数点后23位,但是一些C语言书上却说float型的有效位只有6~7位,这是为什么呢?原因在于二进制小数与十进制小数没有完全一一对应的关系,二进制小数相比十进制小数来说,是离散而不是连续的,我们来看看下面这些数字:
二进制小数 十进制小数
2-23 1.00000011920928955078125
2-22 1.0000002384185791015625
2-21 1.000000476837158203125
2-20 1.00000095367431640625
2-19 1.0000019073486328125
2-18 1.000003814697265625
这里只需要关注F,上面列出了1.xxx这类浮点数中的6个最小的二进制小数,及其对应的十进制数。可以看到使用二进制所能表示的最小小数是1.00000011920928955078125,其次是1.0000002384185791015625,这两个数之间是有间隔的,如果想用二进制小数来表示8位有效数(只算小数部分,小数点前面的1是隐藏的默认值)1.00000002、1.00000003、1.00000004......这些数是无法办到的,而7位有效数1.0000001可以用2-23来表示,1.0000002可以用2-22来表示,1.0000003可以用2-23+2-22来表示。从这个角度来看,float型所能精确表示的位数只有7位,7位之后的数虽然也是精确表示的,但却无法表示任意一个想表示的数值。
但还是有一些例外的,比如说7位有效数1.0000006这个数就无法用F表示,这也表明二进制小数对于十进制小数来说相当于是离散的,刚好凑不出1.0000006这个数,从这点来看float型所能精确表示的位数只有6位。因此float型的有效位数是6~7位,但这个说法应该不是非常准确,准确来说应该是6位,C语言的头文件中规定也是6位。对于一个很大的数,例如1234567890,它是由于指数E系数而被放大了的,但它的有效位仍然是F所能表示的6~7位有效数字。1234567890用float表示后为1234567936,只有高7位是有效位,后3位是无效的。int型可以准确的表示1234567890,而float浮点数则只能近似的表示1234567890,精度问题决定了float型无法取代int型。