回 帖 发 新 帖 刷新版面

主题:[原创]让计算机疑惑的一个数

[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个回复)

沙发

你先要明白double占用8个字节,不可能表示出无穷个实数,所以以最近接的 可以表达的数 来替代。

double x=0.32641;
x 其实是 +0.32640999999999997793764805464888922870159149169921875

x=100000.0*x;
x 其实是 +32640.99999999999636202119290828704833984375

x=32641.0;
x 其实是 +32641.0

板凳

[quote]你先要明白double占用8个字节,不可能表示出无穷个实数,所以以最近接的 可以表达的数 来替代。[/quote]

这个我知道啊,但是并不是所有的数都是这样的。0.326451就不会出现这个情况,但是0.32641却会出现这个情况。
只有有限的几个以1结尾的浮点数会出现这种情况。
还是要谢谢你

3 楼

不知道为什么我脑子里反映出一句4舍6入5留双
存储误差可能是正也可能是负的,这点要注意

4 楼

[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 楼

re boxertony:我觉得没有的舍入
而是326451.0确好是浮点数可以精确表达的一个数

+0.32640999999999997793764805464888922870159149169921875 × 100000.0
= 32640.99999999999636202119290828704833984375

+0.326450999999999991185717362895957194268703460693359375 × 1000000.0
= +326451.0

上面6个数都是精确的数,也就是浮点数可以表示的数

6 楼

[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 楼

另外,计算机在计算+0.32640999999999997793764805464888922870159149169921875 × 100000.0
时不象我们人计算时直接把小数点向右移动5位就行了,从+0.326450999999999991185717362895957194268703460693359375 × 1000000.0
= +326451.0
也可以看出来,因为否则的话上面结果应该是326450.999999999991185717362895957194268703460693359375,而非326451.0

8 楼

这是由于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 楼

简单的说,浮点计算存在精度损失。一般来说,计算的步骤越多,损失的程度就越大。假如是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 楼

十分感谢各位的热烈讨论,让我受益匪浅啊。

我来回复

您尚未登录,请登录后再回复。点此登录或注册