关于浮点:为什么我看到一个双变量初始化为某个值,如21.4为21.399999618530273?

Why do I see a double variable initialized to some value like 21.4 as 21.399999618530273?

1
2
double r = 11.631;
double theta = 21.4;

在调试器中,它们显示为11.63100000000000021.399999618530273

我怎样才能避免这个?


这些精度问题是由于浮点数的内部表示法造成的,您无法避免。

顺便说一下,在运行时打印这些值通常会导致正确的结果,至少使用现代C++编译器。对于大多数操作来说,这不是什么问题。


我喜欢Joel的解释,它处理Excel2007中类似的二进制浮点精度问题:

See how there's a lot of 0110 0110 0110 there at the end? That's because 0.1 has no exact representation in binary... it's a repeating binary number. It's sort of like how 1/3 has no representation in decimal. 1/3 is 0.33333333 and you have to keep writing 3's forever. If you lose patience, you get something inexact.

So you can imagine how, in decimal, if you tried to do 3*1/3, and you didn't have time to write 3's forever, the result you would get would be 0.99999999, not 1, and people would get angry with you for being wrong.


如果您有如下值:

1
double theta = 21.4;

你想做的是:

1
2
3
if (theta == 21.4)
{
}

你必须有点聪明,你需要检查theta的值是否真的接近21.4,但不一定是这个值。

1
2
3
if (fabs(theta - 21.4) <= 1e-6)
{
}


这部分是平台特定的-我们不知道您使用的是什么平台。

这在一定程度上也是一个知道你真正想看到什么的案例。调试器在某种程度上显示了存储在变量中的精确值。在我关于.NET中二进制浮点数的文章中,有一个C类,它允许您看到存储在double中的绝对精确的数字。在线版本目前不起作用-我将尝试在另一个站点上安装一个。

如果调试器看到"实际"值,它必须对要显示的内容进行判断调用-它可以显示舍入到小数点后几位的值,或者更精确的值。有些调试器在读取开发人员的思想方面比其他调试器做得更好,但这是二进制浮点数的一个基本问题。


如果您希望在精度极限下保持稳定,请使用定点decimal类型。有一些开销,如果您希望转换为浮点值,则必须显式转换。如果你真的转换为浮点,你将重新引入那些似乎困扰你的不稳定性。

或者,你可以克服它,学会使用有限的浮点运算精度。例如,可以使用舍入使值收敛,或者使用epsilon比较来描述公差。"epsilon"是您设置的定义公差的常量。例如,如果两个值在0.0001范围内,则可以选择将它们视为相等。

我突然想到,您可以使用运算符重载使epsilon比较透明。那会很酷的。

对于尾数指数表示,必须计算epsilon以保持在可表示的精度内。对于数字n,epsilon=n/10e+14

System.Double.EpsilonDouble类型的最小可表示正值。它对我们来说太小了。阅读微软关于平等测试的建议


我以前(在我的博客上)遇到过这个问题——我想令人惊讶的是,"非理性"的数字是不同的。

这里的"非理性"只是指它们不能用这种格式准确表示。实无理数(如π-pi)根本无法精确表示。

大多数人都熟悉1/3不使用十进制:0.3333333333333…

奇怪的是1.1不能在浮动中工作。人们期望十进制值在浮点数中起作用,因为他们是这样想的:

1.1 is 11 x 10^-1

当他们实际上在基地2时

1.1 is 154811237190861 x 2^-47

你不能避免它,你只需要习惯一些浮点数是"非理性"的事实,就像1/3那样。


在我看来,21.399999618530273是21.4的单精度(浮点)表示。看起来调试器正在从double强制转换为float。


避免这种情况的一种方法是使用一个使用表示十进制数的替代方法的库,例如bcd


计算机算术的危险


您不能避免这样做,因为您使用的是固定字节数的浮点数。实数和它的有限符号之间不可能存在同构。

但大多数时候你可以忽略它。21.4==21.4仍然是真的,因为它仍然是相同的数字,有相同的错误。但是21.4f==21.4可能不是真的,因为float和double的错误是不同的。

如果您需要固定的精度,也许您应该尝试固定的点号。甚至整数。例如,我经常使用int(1000*x)来传递调试寻呼机。


如果您正在使用Java,并且需要精确性,那么可以使用浮点类来计算浮点运算。它速度较慢,但更安全。


如果它困扰您,您可以自定义调试期间某些值的显示方式。小心使用:-)

使用调试器显示属性增强调试


根据JavaDoc

"如果数字运算符的至少一个操作数是double类型,则使用64位浮点运算执行操作,结果数字运算符是double类型的值。如果另一个操作数不是双精度数,则为首先将(§5.1.5)扩展为通过数字促销(§5.6)输入double。"

这是来源


参考一般的十进制算术

在比较浮点数时也要注意,有关更多信息,请参阅此答案。