我有一个应用程序,我在其中累积十进制值(加法和减法)。我使用十进制类型而不是双精度以避免累积错误。但是,我遇到了一种行为与我所期望的不太一样的情况。
我有 x = a + b,其中 a = 487.5M 和 b = 433.33333333333333333333333335M。
计算加法,我得到 x = 920.8333333333333333333333334M。
然后我有 y = 967.8750000000000000000000001M。
我想断言 y - x = y - a - b。然而,
y - x = 47.0416666666666666666666667
y - a - b = 47.04166666666666666666666675
我认为这种错误正是十进制类型要避免的,那么这里发生了什么?
这是重现该问题的代码:
static void Main()
{
decimal a = 487.5M;
decimal b = 433.33333333333333333333333335M;
decimal x = a + b;
decimal y = 967.8750000000000000000000001M;
Console.WriteLine(y - x);
Console.WriteLine(y - a - b);
if (y - x != y - a - b)
Console.WriteLine("x - y != y - a - b");
Console.ReadKey();
}
评论中有一些关于为什么这些高精度是必要的讨论,所以我想我会在这里总结一下。出于显示目的,我当然会对这些操作的结果进行四舍五入,但我对所有内部表示都使用十进制。一些计算沿途采用分数,这导致数字超出小数类型的精度。
但是,我会注意保持一切稳定以进行积累。因此,例如,如果我将一个数量分成三分之三,我取 x/3、x/3,然后是 (x - x/3 - x/3)。这是一个计算物理量的系统,这些物理量经常像这样被分割,所以我不想过早地通过四舍五入来引入偏差。例如,如果我将上面 x=1 的结果四舍五入到小数点后三位,我将得到 0.333、0.333、0.334 作为操作的三个部分。
系统可以做的事情的精确度存在实际的物理限制,但它试图做的事情的逻辑计算应该尽可能保持精确。主要的关键要求是系统的总量不应因这些不同的操作而改变。在上述情况下,我发现 decimal 可能违反此假设,因此我想更好地了解为什么会发生这种情况以及如何解决它。