C标准规定,float类型必须至少能表示6位有效数字,且取值范围至少是10^-37\~10^+37。前一项规定指float类型必须至少精确表示小数点后的6位有效数字,如33.333333。后一项规定用于方便地表示诸如太阳质量、一个质子的电荷量或国家债务之类的数字。通常,系统存储一个浮点数要占用32位。其中8位用于表示指数的值和符号,剩下24位用于表示非指数部分(也叫做尾数或有效数)及其符号。
C语言提供的另一种浮点类型是double(意为双精度)。double类型和float类型的最小取值范围相同,但至少必须能表示10位有效数字。一般情况下,double占用64位而不是32位。一些系统将多出的32位全部用来表示非指数部分,这不仅增加了有效数字的位数(即提高了精度),而且还减少了舍入误差。另一些系统把其中的一些位分配给指数部分,以容纳更大的指 数,从而增加了可表示数的范围。无论哪种方法,double类型的值至少有13 位有效数字,超过了标准的最低位数规定。
C语言的第三种浮点类型是long double,以满足比double类型更高的精度要求。不过,C只保证long double类型至少与double类型的精度相同。
默认情况下,编译器假定浮点型常量是double类型的精度。例如,假设some是float类型的变量,编写下面的语句:
some \= 4.0 * 2.0;
通常,4.0和2.0被存储为64位的double类型,使用双精度进行乘法运算,然后将乘积截断成float类型的宽度。这样做虽然计算精度更高,但是会减慢程序的运行速度。
在浮点数后面加上f或者F后缀可以覆盖默认设置,编译器会将浮点型常量看做float类型,如2.3f和9.11E9F。使用l或L后缀使得数字称为long double类型,如54.3l和4.32L。注意,建议使用L后缀,因为字母l和数字1很容易混 淆。没有后缀的浮点型常量是double类型。
C99 标准添加了一种新的浮点型常量格式——用十六进制表示浮点型常 量,即在十六进制数前加上十六进制前缀(0x或0X),用p和P分别代替e和 E,用2的幂代替10的幂(即,p计数法)。如下所示:
0xa.1fp10
十六进制a等于十进制10,.1f是1/16加上15/256(十六进制f等于十进制15),p10是210或1024。0xa.1fp10表示的值是(10 + 1/16 + 15/256)×1024(即,十进制10364.0)。
注意,并非所有的编译器都支持C99的这一特性。
printf()函数使用%f转换说明打印十进制记数法的float和double类型浮点 数,用%e打印指数记数法的浮点数。如果系统支持十六进制格式的浮点 数,可用a和A分别代替e和E。打印long double类型要使用%Lf、%Le或%La 转换说明。
// 以两种方式显示float类型的值
#include <stdio.h>
int main()
{
float aboat = 32000.0;
double abet = 2.14e9;
long double dip = 5.32e-5;
printf("%f can be written %e\n", aboat, aboat);
printf("And it's %a in hexadecimal, powers of 2 notation\n", aboat);
printf("%f can be written %e\n", abet, abet);
printf("%Lf can be written %Le\n", dip, dip);
return 0;
}
输出结果为
32000.000000 can be written 3.200000e+04
And it's 0x1.f4p+14 in hexadecimal, powers of 2 notation
2140000000.000000 can be written 2.140000e+09
0.000053 can be written 5.320000e-05
浮点数的上溢和下溢
假设系统的最大float类型值是3.4E38,编写如下代码:
float toobig = 3.4E38 * 100.0f;
printf("%e\n", toobig);
会发生什么?这是一个上溢(overflow)的示例。当计算导致数字过大,超过当前类型能表达的范围时,就会发生上溢。这种行为在过去是未定义的,不过现在C语言规定,在这种情况下会给toobig赋一个表示无穷大的特定值,而且printf()显示该值为inf或infinity(或者具有无穷含义的其他内 容)。
当除以一个很小的数时,情况更为复杂。float类型的数以指数和尾数部分来存储。存在这样一个数,它的指数部分是最小值,即由全部可用位表示的最小尾数值。该数字是float类型能用全部精度表示的最小数 字。现在把它除以 2。通常,这个操作会减小指数部分,但是假设的情况 中,指数已经是最小值了。所以计算机只好把尾数部分的位向右移,空出第 1 个二进制位,并丢弃最后一个二进制数。以十进制为例,把一个有4位有 效数字的数(如,0.1234E-10)除以10,得到的结果是0.0123E-10。虽然得 到了结果,但是在计算过程中却损失了原末尾有效位上的数字。这种情况叫 作下溢(underflow)。C语言把损失了类型全精度的浮点值称为低于正常的 (subnormal)浮点值。因此,把最小的正浮点数除以 2将得到一个低于正常 的值。如果除以一个非常大的值,会导致所有的位都为0。现在,C库已提供了用于检查计算是否会产生低于正常值的函数。
还有另一个特殊的浮点值NaN(not a number的缩写)。例如,给asin() 函数传递一个值,该函数将返回一个角度,该角度的正弦就是传入函数的 值。但是正弦值不能大于1,因此,如果传入的参数大于1,该函数的行为是 未定义的。在这种情况下,该函数将返回NaN值,printf()函数可将其显示为 nan、NaN或其他类似的内容。
浮点数舍入错误
给定一个数,加上1,再减去原来给定的数,结果是多少?你一定认为是1。但是,下面的浮点运算给出了不同的答案:
#include <stdio.h>
int main()
{
float a, b;
b = 2.0e20 + 1.0;
a = b - 2.0e20;
printf("%f \n", a);
return 0;
}
输出为
4008175468544.000000
得出这些奇怪答案的原因是,计算机缺少足够的小数位来完成正确的运 算。2.0e20是 2后面有20个0。如果把该数加1,那么发生变化的是第21位。 要正确运算,程序至少要储存21位数字。而float类型的数字通常只能储存按 指数比例缩小或放大的6或7位有效数字。在这种情况下,计算结果一定是错 误的。另一方面,如果把2.0e20改成2.0e4,计算结果就没问题。因为2.0e4 加1只需改变第5位上的数字,float类型的精度足够进行这样的计算。
0 Comments latest
No comments.