如何使用 Doubles 检测精度的完全损失?

How do I detect total loss of precision with Doubles?

当我 运行 以下代码时,我在两行上都打印了 0:

Double a = 9.88131291682493E-324;
Double b = a*0.1D;
Console.WriteLine(b);
Console.WriteLine(BitConverter.DoubleToInt64Bits(b));

如果运算结果超出范围,我希望得到 Double.NaN。相反,我得到 0。看起来为了能够检测到这种情况何时发生,我必须检查:

这很笨拙。有没有更好的办法? Double.NaN 的设计目的是什么?我假设某些操作必须有 return 它,设计师肯定没有把它放在那里以防万一吧?这可能是 BCL 中的错误吗? (我知道不太可能,但是,这就是为什么我想了解 Double.NaN 应该如何工作)

更新

顺便说一下,这个问题不是double特有的。 decimal 完全相同:

Decimal a = 0.0000000000000000000000000001m;
Decimal b =  a* 0.1m;
Console.WriteLine(b);

这也给出零。

在我的例子中,我需要两倍,因为我需要他们提供的范围(我正在做概率计算)而且我不那么担心精度。

不过,我需要的是能够检测到我的结果何时停止意味着什么,也就是说,当计算将值降得太低以至于它不能再用双精度表示时。

是否有实用的检测方法?

Double 完全按照浮点数规范 IEEE 754 工作。所以不,这不是 BCL 中的错误 - 这只是 IEEE 754 浮点数的工作方式。

原因当然是浮动根本不是设计的目的。 相反,您可能想使用 decimal,这是一个 精确 十进制数,与 float/double 不同。

浮点数中有几个特殊值,意义不同:

  • 无限 - 例如1f / 0f.
  • -无穷大-例如-1f / 0f.
  • NaN - 例如0f / 0fMath.Sqrt(-1)

然而,正如下面的评论者所指出的,虽然 decimal 实际上会检查溢出,但过于接近零 不会 被视为溢出,就像浮点数字。所以如果你真的需要检查这个,你将不得不制作自己的 */ 方法。不过,对于十进制数字,您不必太在意。

如果您需要这种精度的乘法和除法(也就是说,您希望您的除法可以通过乘法反转),您应该改用有理数 - 两个整数(必要时使用大整数)。并使用 checked 上下文 - 这将在溢出时产生异常。

IEEE 754 实际上确实处理了下溢。有两个问题:

  • return 值为 0(或 -1 表示负向回流)。设置了下溢的异常标志,但无法在 .NET 中获取它。
  • 这只会在您太接近零时出现精度损失。但是在那之前很久你就失去了大部分的精确度。无论您拥有的 "precise" 数字早已不复存在 - 操作不可逆,而且不精确。

因此,如果您真的关心可逆性等,请坚持使用有理数。 decimaldouble 都不起作用,C# 与否。如果你不那个精确,你不应该关心下溢 - 只需选择最低的合理数字,并将其下的任何内容声明为"invalid";可能确定您远离 实际 最大精度 - double.Epsilon 显然无济于事。

你只需要 epsilon.

这是一个"small number",它太小了,所以你不再感兴趣了。

您可以使用:

double epsilon = 1E-50;

每当你的一个因素变得小于 epislon 时,你就会采取行动(例如将其视为 0.0)