浮点数的运算误差主要来源于两个方面,“转换误差”和“运算舍入”。
float a = 0.65f;
float b = 0.6f;
float c = a - b;
此时c=0.5吗?运行结果其实是0.0499999523,为什么?是我们输入十进制的0.65和0.6其实转换成二进制时为:(0.65)10 = (0.101001100110011001100110011001100110011......)2
(0.6) 10 = (0.10011001100110011001100110011001100110011......)2
省略号就是无穷尽。而实际的存储只能是取前几位,造成转换误差。实际上我们输入十进制整数是能无误差转换成二进制,而小数却不一定(进制转换方法不详述)。
来看什么时候这个数字会无法表示呢?那么只有两种情形:
1)幂数不够表示了:这种情况往往出现在数字太大了,超过幂数所能承受的范围,那么这个数字就无法表示了。如幂数最大只能是10,但是这个数字用科学计数法表示时,幂数一定会超过10,就没办法了。
2)尾数不够表示了:这种情况往往出现在数字精度太长了,如1.3434343233332这样的数字,虽然很小,还不超过2,这种情况下幂数完全满足要求,但是尾数已经不能表示出来了这么长的精度。
有意思的是,有时候有些不大的整数也有可能造成转换误差,比如:对一个单精度浮点数(4 Bytes)赋值“20014999”,由于这个数表示成浮点式二进制为(注意是按照浮点二进制转换,有别于整数二进制):所谓“运算舍入”误差,这个十进制运算是一样的(代数运算对进制无偏向性)。1/3=0.3333333…… ,以及按照级数计算出得一些无理数如π=3.1415926549…… 。这种运算舍入误差在二进制中同样存在,因此带来精度问题。还有一些运算中的舍入误差跟特定的机器实现有关,比如可能出现在加减法中、乘法中(这在下一篇博文中会详细说明)。
总之浮点数至少最后一位是不精确的,甚至有些在一段运算过后可能产生较大的精度误差。
浮点运算很少是精确的,只要是超过精度能表示的范围就会产生误差。一般在科学计算中浮点数的精度能够满足,不行就用double或者更多Bytes的扩展浮点类型。但是有些使用要注意,比如比较判断时,开头c代码中,判断 c==0.5 就会得到 false,因此一般使用 abs(c-0.5)<=0.0001 。还有一些情况是金融货币领域,对数字要求精确,比如一个人存了15000000元,你不能说他存了14999999。这时候有一些其他的解决办法。比如 C#中提供了Decimal类型,VB中提供了Currency类型,这些都是用很大的资源开销来处理精度,并不适合做科学计算,但是适合需要很精确的场合。