首先,我知道C ++中的double类型已经讨论了很多时间,但是在搜索之后我无法回答我的问题。任何帮助或想法都受到高度赞赏。
我的问题的简化版本是:当我计算a=-0.926909
时,我得到了三个不同的结果(a=-0.926947
,a=-0.926862
和a=b-c+d
)有三种不同的方法和b
,c
和d
的相同值,我不知道应该信任哪一个。
我的问题的详细版本是:
我最近编写了一个程序(在Ubuntu 10.10上用C ++编写)来处理一些数据。一个函数看起来像这样:
void calc() {
double a, b;
...
a = b - c + d; // c, d are global variables of double
...
}
当我使用GDB调试上面的代码时,在调用calc()期间,我在语句{{1}之前记录了b
,c
和d
的值如下:
a = b - c + d
在声明b = 54.7231
c = 55.4051
d = -0.244947
声明之后,我发现a = b - c + d
而不是a=-0.926909
由计算器计算。好吧,到目前为止它还不是很混乱,因为我猜这可能只是一个精确问题。后来由于某种原因,我重新实现了-0.926947
的另一个版本。我们称这个新版本为calc()
。 calc_new()
与calc_new()
几乎相同,但计算calc()
,b
和c
的方式和位置除外:
d
这次我在调试时,void calc_new() {
double a, b;
...
a = b - c + d; // c, d are global variables of double
...
}
,b
和c
在声明d
之前的值与调试a = b - c + d
时的值相同:calc()
,b = 54.7231
,c = 55.4051
。但是,这次声明d = -0.244947
执行后,我得到a = b - c + d
。话虽如此,当我使用相同的a=-0.926862
,a
和a = b - c + d
值计算b
时,我得到了三个c
。我认为d
,a=-0.926862
和a=-0.926909
之间的差异不小,但我无法弄清楚原因。哪一个是正确的?
非常感谢, 汤姆
答案 0 :(得分:2)
如果您希望答案在小数点后第5位和第6位准确,您需要确切知道这些位置的计算输入是什么。你看到输入只有4位小数,你需要显示他们的第5和第6位。然后我想你会看到一个可理解的情况,将你的计算器与小数点后6位相匹配。对于这项工作,Double有足够的精度,如果你取两个非常相似的数字(你不是),那里只会出现精度问题。
编辑:不出所料,提高显示精度还会向您显示calc()和calc_new()为计算提供不同的输入。感谢Mike Seymour和Dietmar Kuhl的评论,他们是第一个看到你实际问题的人。
答案 1 :(得分:1)
让我试着回答一下我怀疑你想问的问题。如果我误解了你的意图,那么你可以忽略答案。
假设我有数字 u = 500.1 和 v = 5.001,,每个精确到四位小数。那么什么是 w = u + v ?回答, w = 505.101,但是小数点后四位,它是 w = 505.1。
现在考虑 x = w - u = 5.000,,它应该等于 v,但不完全。
如果我只改变操作顺序,我可以让 x 完全等于 v ,而不是 x = w - u 或者 x =(u + v) - u,但是 x = v +(u - u)。
那是微不足道的吗?是的,在我的例子中,它是;但是同样的原则适用于你的例子,除了它们实际上不是小数位,而是精确位。
一般来说,为了保持精度,如果你有一些要加总的浮点数,你应该先尝试将小的数加在一起,然后再将较大的数加入总数中。
答案 2 :(得分:1)
我们在这里讨论 smoke 。如果环境中没有任何变化,则表达式如下:
a = b + c + d
如果输入没有改变,必须始终返回相同的值。
没有舍入错误。没有神秘的pragma,一点也不。
如果您今天和明天检查您的银行帐户(并且在那段时间内没有任何变化),我怀疑如果您看到不同的内容,您会发疯的。我们谈论的是程序,而不是随机数字生成器!!!
答案 3 :(得分:1)
正确的是-0.926947。
您看到的差异对于舍入错误(即使是单精度)来说太大了,因为可以检查this encoder。 使用编码器时,您需要输入如下:-55.926909(以解释在先前提交的答案中很好地描述的操作员交换效果的潜在影响。)此外,最后一个重要位的差异很可能是由于舍入效果,但你不会看到任何你的价值观。
使用该工具时,64位格式(Binary64)对应于您的实现的双重类型。
答案 4 :(得分:0)
有理数并不总是在给定的基数中具有终止扩展。十分之一不能用有限数字表示1/3。在基数2中,具有2的幂的分母的有理数将具有终止扩展。其余的不会。所以1/2,1 / 4,3 / 8,7 / 16 ......任何看起来像x /(2 ^ n)的数字都可以准确表示。事实证明,这是有限数列有理数的一个相当稀疏的子集。其他所有内容都会受到尝试在有限容器中表示无限数量的二进制数字而引入的错误的影响。
但补充是可交换的,对吧?是。但是当你开始引入舍入错误时,事情会有所改变。以a = b + c + d为例,假设d不能用有限数量的二进制数表示。也不能。因此将它们加在一起会给我们一些不准确的值,它本身也可能无法用有限数量的二进制数字表示。所以错误之上的错误。然后我们将该值添加到b,这也可能不是二进制的终止扩展。因此,取一个不准确的结果并将其添加到另一个不准确的数字会导致另一个不准确的数字。而且因为我们在每一步都抛弃了精确性,我们可能会在每一步都打破交换的对称性。
我发了一篇帖子:( Perl相关,但这是一个普遍的主题)Re: Shocking Imprecision (PerlMonks),当然还有规范的What Every Computer Scientist Should Know About Floating Point Math,两者都讨论了这个话题。后者更加详细。