浮点数float的惊人累加误差!!-CSDN论坛-CSDN.NET-中国最大的IT技术社区
http://bbs.csdn.net/topics/390549664
两段进行浮点数累加的代码。如果无误差的话,输出应该是40000.
============
浮点数的精度由其尾数的最低位决定,比如float类型,小数点后有23位二进制位,最右边一位可以表示的十进制数据的值为2^(-23).
所以有下面的结论:
float:2^23 = 8388608,一共七位,这意味着最多能有7位有效数字,但绝对能保证的为6位,也即float的精度为6~7位有效数字;
double:2^52 = 4503599627370496,一共16位,同理,double的精度为15~16位。
============
double ans1=0;
for (int i = 0; i < 4000000; i++){
ans1 += 0.1;
}
printf("%f\n", ans1); //399999.999979,如果换成float类型呢?
// note:虽然double的精度有16位,但输出时总是只有6位小数,这里的总有效位数12。
float ans2 = 0;
for (int i = 0; i < 4000000; i++){
ans2 += 0.1;
}
printf("%f\n", ans2); //384524.781250,看来float还真是误差挺大的。
================讨论=================
计算机中的计算总是会有误差的,但如何减少误差呢?
下面是一些好的建议:
1。用double类型不用float。
2。对于类似上面这种情况每5次加0.5,下面是运行的结果:
float ans3 = -0.5;
for (int i = 0; i < 4000000; i++){
if (i % 5 == 0)ans3 += 0.5; //399999.500000,虽然还是有误差,但误差小了许多。
}
printf("%f\n", ans3);
3. 用整型(银行系统里元、角(分)分开处理)
4. 设计出无误差的方案(至少有修正方案)。windows里的计算器的界面虽然没大变,但内核其实不断地被重写,vista起各种运算的都能精确到任意位(不会出现 1/3 * 3 != 1之类的情况了)
我记得 Bjarne Stroustrup 的书里举了一个例子,好像是海湾战争中,美军发射的一个导弹摧毁自己另一个地方的军营,原因就是因为浮点数的累积误差。。。
5。按照下面这种方式精度会提高许多。解释见下文。
float f = 0.1;
float sum = 0;
for( int i=0; i<2000; i++)
{
float tmp_sum =0;
for (int j = 0;j<2000;j++)
tmp_sum+=f;
sum += tmp_sum;
}
我猜是这样:
因为float一共只有6位有效数字(10进制),如果整数部分的位数多了,相应的小数部分的位数就少了,因此当一个很大的数(如100000)去加一个很小的数(如0.1),就会形成很大的误差
用楼主的累加方式,一开始误差不大,但到后来,sum越来越大,相应的误差也就越来越大
而用这种分次累加后再相加的方式,使得相加的两数大小差距都比较小,所以每次相加产生的误差也就小。
tips:
浮点数是7位有效数字,不是小数点后7位,因此尽量要避免两个数量级差别很大的数相加,因为这样误差会变大.极端例子:
float fValue1 = 123456789; //本身这个输出就不准确,float的精度(有效数字)只有6-7位,正如你所看到的第7位后面的数据,计算完全靠猜了。
printf("%f\n", fValue1); //123456792.000000,
printf("%f\n", (double)fValue1); //123456792.000000
//1.23457e+008 对于浮点数,double或float类型如果由于数据过大,默认使用了科学计数法,有效数字是6位。看来还是cout输出float类型时靠谱,有一说一啊!!!,虽然最后一位是四舍五入得来的。cout << fValue1 << endl;
float fVlaue2 = fValue1 + 1; //1.23457e+008
if (fVlaue2 == fValue1) printf("same \n"); //真的是这样哦!!!!你会发现+1根没加一样
6。关于一个很大的数加一个很小的数会发生什么???
你的f是0.1,float位数是23,当sum足够大的时候,会出现 sum+f==sum 的情况,这个是ieee标准,
和C++没关系,事实上编译器应该已经做了浮点精度调整了,你这结果误差算小的了.
避免这种误差的方法就是浮点数,永远不要让一个很大的数去加上一个很小的数.
不知你这段代码的目的是什么,但如果你改成这样,误差会小很多:
float f = 0.1;
float sum = 0;
for( i=0; i<100; i++)
{
int sumEachBig=0;
for(....k<400....)
int sumEachSmall=0;
for(....j<100.....)
sumEachSmall += f;
}
}
}
懒得写了...简单写一下看得懂就行..其实也就是你说的换了个结构精度提高了的那段代码.
总之就是"浮点数,永远不要让一个很大的数去加减一个很小的数"