【问题标题】:c++ floating point precision loss: 3015/0.00025298219406977296c++浮点精度损失:3015/0.00025298219406977296
【发布时间】:2011-02-01 18:53:41
【问题描述】:

问题。

Microsoft Visual C++ 2005 编译器,32 位 windows xp sp3,amd 64 x2 cpu。

代码:

double a = 3015.0; 
double b = 0.00025298219406977296;
//*((unsigned __int64*)(&a)) == 0x40a78e0000000000  
//*((unsigned __int64*)(&b)) == 0x3f30945640000000  
double f = a/b;//3015/0.00025298219406977296;

计算结果(即“f”)是 11917835.000000000 (((unsigned __int64)(&f)) == 0x4166bb4160000000) 虽然它应该是 11917834.814763514 (即 ((unsigned __int64 )(&f)) == 0x4166bb415a128aef)。
IE。小数部分丢失。
不幸的是,我需要小数部分才能正确。

问题:
1) 为什么会这样?
2) 我该如何解决这个问题?

附加信息:
0) 结果是直接从“watch”窗口获取的(它没有打印,我没有忘记设置打印精度)。我还提供了浮点变量的十六进制转储,所以我对计算结果非常肯定。
1)f = a/b的反汇编为:

fld         qword ptr [a]  
fdiv        qword ptr [b]  
fstp        qword ptr [f]  

2) f = 3015/0.00025298219406977296;产生正确的结果(f == 11917834.814763514 , ((unsigned __int64)(&f)) == 0x4166bb415a128aef ),但看起来在这种情况下结果只是在编译时计算:

fld         qword ptr [__real@4166bb415a128aef (828EA0h)]  
fstp        qword ptr [f]  

那么,我该如何解决这个问题呢?

附:我找到了一个临时解决方法(我只需要除法的小数部分,所以我现在只使用 f = fmod(a/b)/b),但我仍然想知道如何正确解决这个问题 - 双倍精度应该是 16 位十进制数字,所以这样的计算应该不会引起问题。

【问题讨论】:

    标签: c++ x86 double floating-accuracy


    【解决方案1】:

    您是否在程序中的任何地方使用了 directx,因为这会导致浮点单元切换到单精度模式,除非您在创建设备时明确告诉它不要这样做,并且会导致这种情况

    【讨论】:

    • 这是一个正确的答案。程序使用 Direct3D,当然,计算发生在设备创建之后。有趣的是,我知道 D3D 调整 FPU 精度,但我完全忘记了,因为最近几年我没有看到这个错误。问题解决了。
    • 创建设备时应该使用什么标志? Direct2D 是否存在同样的问题?
    【解决方案2】:

    有趣的是,如果您将 a 和 b 都声明为浮点数,您将得到准确的 11917835.000000000。所以我的猜测是在某处发生了向单精度的转换,无论是在如何解释常量或稍后在计算中。

    不过,考虑到您的代码非常简单,这两种情况都有些令人惊讶。您没有使用任何奇异的编译器指令,对所有浮点数强制单精度?

    编辑:您是否确实确认编译的程序会产生错误的结果?否则,(错误的)单精度转换最可能的候选者将是调试器。

    【讨论】:

    • 反汇编清楚地表明,没有转换为单精度。
    • 反正不在这三行。
    【解决方案3】:

    如果您需要精确的数学运算,请不要使用浮点数。

    帮自己一个忙,获得一个支持有理数的 BigNum 库。

    【讨论】:

    • 他不需要11917834.814763514100059144562708,他只需要11917834.814763514。为了获得机器内置的精度而放弃性能和内存的数量级似乎有点不合理(请原谅双关语)。
    • 当然,我们无权期望准确,但我们仍然有权要求浮点规范向我们承诺的那种正确程度!
    • 无意冒犯,但我认为仅在一次计算中使用 bignums 有点过分,至少在这种情况下是这样。
    【解决方案4】:

    我猜你是在打印出数字而没有指定精度。试试这个:

    #include <iostream>
    #include <iomanip>
    
    int main() { 
        double a = 3015.0; 
        double b = 0.00025298219406977296;
        double f = a/b;
    
        std::cout << std::fixed << std::setprecision(15) << f << std::endl;
        return 0;
    }
    

    这会产生:

    11917834.814763514000000

    这对我来说是正确的。我使用的是 VC++ 2008 而不是 2005,但我猜区别在于您的代码,而不是编译器。

    【讨论】:

    • 不,我没有打印数字,结果是直接从“手表”窗口获取的。
    • 你试过打印吗?也许这个错误在监视窗口中!
    【解决方案5】:

    你确定你是在 fstp 指令之后检查 f 的值吗?如果您打开了优化,也许观察窗口可能会显示稍后某个时间点的值(这似乎有点合理,因为您说您稍后会查看 f 的小数部分 - 是否有一些指令最终掩盖了它以某种方式出来?)

    【讨论】:

      猜你喜欢
      • 2021-05-09
      • 1970-01-01
      • 1970-01-01
      • 2017-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多