主题:[原创]让计算机疑惑的一个数
tr0217
[专家分:730] 发布于 2010-06-14 17:53:00
[code=c]
#include <iostream>
using namespace std;
int main()
{
double x=0.32641;
x=100000.0*x;
long y=(long)x;
cout<<"x="<<x<<endl<<"y="<<y<<endl;
x=32641.0;
long z=(long)x;
cout<<"z="<<z<<endl;
return 0;
}
[/code]
x=0.32641这个数乘以100000后转换为长整型后最后一个1丢失,直接将32641.0转换为double最后那个1不会丢失,其它位数更多的数也不会出现这个情况。
希望有个高手帮忙看看
回复列表 (共10个回复)
沙发
bruceteen [专家分:42660] 发布于 2010-06-14 20:03:00
你先要明白double占用8个字节,不可能表示出无穷个实数,所以以最近接的 可以表达的数 来替代。
double x=0.32641;
x 其实是 +0.32640999999999997793764805464888922870159149169921875
x=100000.0*x;
x 其实是 +32640.99999999999636202119290828704833984375
x=32641.0;
x 其实是 +32641.0
板凳
tr0217 [专家分:730] 发布于 2010-06-15 11:32:00
[quote]你先要明白double占用8个字节,不可能表示出无穷个实数,所以以最近接的 可以表达的数 来替代。[/quote]
这个我知道啊,但是并不是所有的数都是这样的。0.326451就不会出现这个情况,但是0.32641却会出现这个情况。
只有有限的几个以1结尾的浮点数会出现这种情况。
还是要谢谢你
3 楼
雪光风剑 [专家分:27190] 发布于 2010-06-15 21:48:00
不知道为什么我脑子里反映出一句4舍6入5留双
存储误差可能是正也可能是负的,这点要注意
4 楼
boxertony [专家分:23030] 发布于 2010-06-17 09:30:00
[quote][quote]你先要明白double占用8个字节,不可能表示出无穷个实数,所以以最近接的 可以表达的数 来替代。[/quote]
这个我知道啊,但是并不是所有的数都是这样的。0.326451就不会出现这个情况,但是0.32641却会出现这个情况。
只有有限的几个以1结尾的浮点数会出现这种情况。
还是要谢谢你
[/quote]
因为0.32641*100000.0是以浮点数进行运算的,浮点数运算过程中因为精确度的限制,实际上对最后的结果是存在1入0舍的,0.32641*100000.0最后在需要舍去的位置整好是0,而0.326451*1000000.0在最后需要舍去的位置整好是1,也正因为这一入使得结果变成了326451了,而非326450.
所以实际上不是有限的几个以1结尾的浮点数会出现这种情况,实际上还有很多的数据都可能出现的,随便举一个,比如0.12635*100000.0结果就为12634
5 楼
bruceteen [专家分:42660] 发布于 2010-06-17 15:25:00
re boxertony:我觉得没有的舍入
而是326451.0确好是浮点数可以精确表达的一个数
+0.32640999999999997793764805464888922870159149169921875 × 100000.0
= 32640.99999999999636202119290828704833984375
+0.326450999999999991185717362895957194268703460693359375 × 1000000.0
= +326451.0
上面6个数都是精确的数,也就是浮点数可以表示的数
6 楼
boxertony [专家分:23030] 发布于 2010-06-18 00:38:00
[quote]re boxertony:我觉得没有的舍入
而是326451.0确好是浮点数可以精确表达的一个数
+0.32640999999999997793764805464888922870159149169921875 × 100000.0
= 32640.99999999999636202119290828704833984375
+0.326450999999999991185717362895957194268703460693359375 × 1000000.0
= +326451.0
上面6个数都是精确的数,也就是浮点数可以表示的数[/quote]
我的意思是0.32641*100000的运算结果进行了0舍1入。因为虽然0.32641在计算机虽然是用+0.32640999999999997793764805464888922870159149169921875精确表达的,但是乘以100000以后,其二进制位数显然超过了double类型尾数的52位,这时候必然存在舍入的问题,也正是这个对超过52位部分的舍入造成了不同的数略大于或略小于精确数,也造成了取整后是否比精确值小于1
7 楼
boxertony [专家分:23030] 发布于 2010-06-18 00:45:00
另外,计算机在计算+0.32640999999999997793764805464888922870159149169921875 × 100000.0
时不象我们人计算时直接把小数点向右移动5位就行了,从+0.326450999999999991185717362895957194268703460693359375 × 1000000.0
= +326451.0
也可以看出来,因为否则的话上面结果应该是326450.999999999991185717362895957194268703460693359375,而非326451.0
8 楼
alweeq86 [专家分:1170] 发布于 2010-06-18 11:19:00
这是由于double转换成2进制存储,然后又转换成double时出现的一个误差
我自己做了个double转2进制再转double的函数
[code=c]
double DtoBtoD(double d)
{
int index=0;
int a[200]={0},count=0;
double dd=0.0;
for(; ; )
{
d=d*2;
index++;
if(d>1){a[count++]=-index;d=d-1;}
if(count>=200) break;
}
for(int i=0; i<200; i++)
{
dd+=pow(2.0,a[i]);
}
return dd;
}
[/code]
double x=0.32641;
DtoBtoD(x)=0.32640999999999998
其实在监视窗时就可以看到
double x=0.32641; x=0.32640999999999998
double x=0.32651; x=0.32651000000000002
9 楼
eastcowboy [专家分:25370] 发布于 2010-06-19 05:02:00
简单的说,浮点计算存在精度损失。一般来说,计算的步骤越多,损失的程度就越大。假如是10000个数相加的话,每次计算的精度损失积累起来,得到的结果很可能就不正确。
如果要追究细节的话,就没那么简单了。首先是编译器的干预,因为编译器太“聪明”了,它总是会尝试进行一些优化。大家知道乘法有交换律,结合律,分配率,这些数学规则很可能被编译器利用,从而优化代码。但对于浮点数来说,这导致了一个不太令人满意的情形——我们可能无法精确的控制每一个计算步骤。也许,你的代码在Debug模式下运行正确,到了Release模式下却得到错误结果(参见后面的那个输出结果)。
微软在MSDN上面有一篇文章,专门讲了这个问题:
[url=http://msdn.microsoft.com/zh-cn/library/aa289157(v=VS.71).aspx]Microsoft Visual C++ 浮点优化 http://msdn.microsoft.com/zh-cn/library/aa289157(v=VS.71).aspx[/url]
注意还可以通过控制字来修改浮点数的工作方式。比如舍入处理、运算精度、处以零是否认为是错误,这些都是可以设置的。
楼主的代码,在main函数的开头加上一句“_controlfp(_RC_UP, _MCW_RC);”,然后包含对应的头文件:
#include <cfloat>
则Debug模式输出结果变为:
x=32641
y=32641
z=32641
Release模式的输出结果仍然是:
x=32641
y=32640
z=32641
(测试环境Windows 7 + Visual C++ 2008 Express SP1)
10 楼
tr0217 [专家分:730] 发布于 2010-07-05 08:34:00
十分感谢各位的热烈讨论,让我受益匪浅啊。
我来回复