0

我有一个应用程序,我在其中累积十进制值(加法和减法)。我使用十进制类型而不是双精度以避免累积错误。但是,我遇到了一种行为与我所期望的不太一样的情况。

我有 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 可能违反此假设,因此我想更好地了解为什么会发生这种情况以及如何解决它。

4

2 回答 2

2

C# 类型 Decimal 与 COBOL 中使用的十进制类型不同,COBOL 实际上存储每个半字节一个十进制数字,并使用类似于手工进行十进制数学的数学方法。相反,它是一种浮点类型,只是假设数量不会变得如此之大,因此它使用较少的位来表示指数,并将剩余的 128 位而不是 64 位用于双精度,以大大提高精度。

但是作为浮点表示,即使是非常简单的小数值也不能精确表示:例如,0.1 需要二进制重复小数,并且可能无法存储为精确值。(它不是,对于双精度数;十进制可能会以不同的方式处理该特定值,但总的来说这是正确的。)

因此,仍然需要使用典型的浮点数学程序进行比较,在该程序中,通过仅将值接受到某个点来比较、加法、减法等。由于大约有 23 个小数位的精度,因此选择 16 作为您的标准,例如,忽略最后的那些。

要获得很好的参考,请阅读每个计算机科学家应该了解的关于浮点精度的知识。

于 2013-10-08T16:12:33.977 回答
1

Decimal类型是一种浮点类型,其精度比从一开始就内置到 .NET 中的任何其他类型都多,并且其值都可以简明地以 base-10 格式表示。然而,它体积庞大且速度慢,并且因为它是浮点类型,它不再能够满足“精确”类型的典型公理(例如,对于任何 X 和 Y,(X+Y-Y)==X应该返回 true 或抛出溢出异常) . 我猜想它是浮点类型而不是定点类型,因为对小数点右侧的位数犹豫不决。在实践中,使用 128 位定点格式可能会更快,并且同样有用,但Decimal类型就是这样。

顺便说一句,像 PL/I 这样的语言可以很好地处理定点类型,因为它们认识到精度是存储位置的函数,而不是的函数。不幸的是,.NET 没有提供任何很好的方法,通过它可以将变量定义为保存 aFixed(6,3)并自动缩放和移动Fixed(5,2)存储在其中的 a。将精度作为值的一部分意味着将值存储到变量中将更改变量在小数点右侧表示的位数。

于 2013-10-08T22:45:17.813 回答