2010-03-28 62 views
17

这个问题。C++浮点精度损失:3015/0.00025298219406977296

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(((无符号__int64)(& F))== 0x4166bb4160000000)尽管它应该是11917834.814763514(即((unsigned __int64)(& f))== 0x4166bb415a128aef)。
I.e.小数部分丢失。
不幸的是,我需要小数部分是正确的。

问题:
1)为什么会发生这种情况?
2)我该如何解决这个问题?

附加信息:
0)的结果被直接“手表”窗口(这是不打印,我没有忘记设置打印精度)。我也提供了浮点变量的十六进制转储,所以我对计算结果非常肯定。
1)F = A/B的拆卸是:

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

2)F = 3015/0.00025298219406977296;产生正确的结果(F == 11917834.814763514,((无符号__int64)(& F))== 0x4166bb415a128aef),但它看起来像在这种情况下,结果在编译时简单地计算:

fld   qword ptr [[email protected] (828EA0h)] 
fstp  qword ptr [f] 

所以,我该如何解决这个问题?

P.S.我找到了一个临时解决方法(我只需要除法的小数部分,所以我现在简单地使用f = fmod(a/b)/ b),但我仍然想知道如何正确解决这个问题 - double精度应该是十六进制数字,所以这样的计算不应该导致问题。

回答

15

你在你的程序中使用DirectX的任何地方,导致浮点单元获得切换为单精度模式,除非你明确告诉它不是在创建设备,并会造成正是这种

+1

这是一个正确的答案。程序使用Direct3D,当然,计算发生在设备创建后。有趣的是,我知道D3D调整FPU精度,但我完全忘了它,因为我在过去几年没有看到这个错误。 问题解决。 – SigTerm 2010-03-28 19:24:48

+1

创建设备时应该使用哪个标志? Direct2D存在同样的问题吗? – dalle 2010-08-04 16:22:19

1

我想你会打印出数字而不指定精度。试试这个:

#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,但我猜想它们的区别在于你的代码,而不是编译器。

+0

不,我不打印号码,结果是直接从“观察”窗口中取出。 – SigTerm 2010-03-28 17:14:38

+0

您是否尝试过打印它?也许这个bug是在看窗口! – 2010-03-28 17:29:49

+0

@Martin监视窗口显示完整的精度。 – 2010-03-28 17:37:55

4

有趣的是,如果你声明a和b都是浮点数,那么你将得到正好11917835.000000000。所以我的猜测是,在某个地方发生了单精度转换,无论是常量的解释方式还是以后的计算方式。

但是,考虑到代码的简单性,任何一种情况都有点令人惊讶。你没有使用任何异乎寻常的编译器指令,强制所有浮点数的单精度?

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

+0

反汇编清晰显示,没有单一的精度。 – 2010-03-28 17:52:51

+0

无论如何,不​​在这三条线上。 – 2010-03-28 17:58:06

2

如果你需要精确的数学,不要使用浮点数。

帮你一个忙,并得到一个BigNum库有理性的数字支持。

+3

他不需要11917834.814763514100059144562708,他只需要11917834.814763514。为了获得机器内置的精确度而放弃性能和内存量级似乎有点不合理(赦免双关语)。 – Gabe 2010-03-28 17:58:27

+0

当然,我们无权期待正确性,但我们仍有权要求浮点规范保证我们的正确性。 – AakashM 2010-03-28 18:15:57

+0

没有冒犯性,但我认为仅仅为一次计算使用bignum就有点过分了,至少在这种情况下。 – SigTerm 2010-03-28 20:03:24

0

您确定您正在检查fstp指令之后的f值吗?如果你已经开启了优化,那么观察窗口可能会显示稍后的值(这似乎有点合理,因为你说你稍后在看f的小数部分 - 是否有一些指令会掩盖它出不知何故?)