# 浮点数
数学中稠密的实数无法利用有限的计算机储存进行表示,出于精度和范围的平衡,目前世界上最广为使用的浮点数标准是 IEEE 754 标准
通常来说,浮点数的精度分为
- 单精度(Float):占用 4 字节,32 位,约 7 位十进制有效数字,在 C 语言中通过后缀
f/F来表示,例如0.5F - 双精度(Double):占用 8 字节,64 位,约 16 位十进制有效数字,在 C 语言中默认的浮点数类型,例如
0.5
在该标准中,一个 32 位的浮点数被划分为三个区域:
- 符号位(Sign, 1 bit):0 表示正数,1 表示负数
- 指数位(Exponent, 8 bits):采用偏移码(Biased representation)存储,决定了数值的量级范围。
- 尾数位(Fraction/Mantissa, 23 bits):存储有效数字。在规格化表示中,小数点前默认有一个隐藏的 “1”,因此实际有效位数为 24 位。
其数学表达式通常可抽象为:
关于浮点数,需要知道以下事实:
- 计算机中小数等价为 的负幂次方的和,因此
0.5F严格等于 ,同时0.1F略微大于 ,而0.7F略微小于 。 - 等同于整数的浮点数会被修正
参考以下 C 语言代码
#include <stdio.h> | |
int main() { | |
float a = 0.1F; | |
float b = 0.7F; | |
float c = 16777217.0F; | |
printf("a=%.10f\n", a); | |
printf("b=%.10f\n", b); | |
printf("c=%.10f\n", c); | |
} | |
// output: | |
// a=0.1000000015 | |
// b=0.6999999881 | |
// c=16777216.0000000000 |
该类错误被称为舍入误差「丸め誤差」,是由于浮点数的有限精度导致的。
舍入误差会导致运算的结合律不成立,例如 (0.1F + 0.3F) + 0.5F 与 0.1F + (0.3F + 0.5F) 的结果分别为 0.8999999762 和 0.9000000358
虽然我们知道 0.1F 略大于 ,但是如果将其累加十次,会变得比 更大,这种现象叫做信息丢失「情報落ち」,是在运算过程中,由于有效位数不足,小量被大数 “吞掉”,或者做差时前面很多位抵消,导致有用信息丢失。
#include <stdio.h> | |
int main() { | |
int i; | |
float x,s; | |
x=0.1; | |
s=0.0; | |
for(i=0;i<10;i++) s=s+x; | |
printf("s=%.10f\n", s); | |
} | |
// output: | |
// s=1.0000001192 |
关于浮点数的误差,还需要知道的是有效数字丢失「桁落ち」,这是两个非常接近的数相减时,前面许多相同的位被抵消掉,导致结果的有效数字大幅减少的现象。
让我们看以下二次方程求根代码示例
#include <stdio.h> | |
#include <math.h> | |
int main() { | |
float a,b,c,d,x1,x2; | |
a=2; b=12340.0; c=3; | |
d=b*b-4*a*c; | |
printf("a=%f b=%f c=%f d=%f\n", a, b, c, d); | |
x1=(-b+sqrt(d))/(2*a); | |
x2=(-b-sqrt(d))/(2*a); | |
printf("x1=%8f\n", x1); | |
printf("x2=%8f\n", x2); | |
} | |
// output: | |
// x1=-0.000162 | |
// x2=-6170.000000 |
然而,我们不妨代入计算可以得到
可以看出,虽然 的结果是正确的,但 的结果出现了大幅错误,这是因为分子两项的值过于接近
这种情况下应该使用 来计算 ,以避免有效数字丢失