【发布时间】:2013-10-15 15:07:49
【问题描述】:
我有一个应用程序可以累积十进制值(加法和减法)。为了避免累积错误,我使用小数类型而不是双精度。但是,我遇到了一个行为与我所期望的不太一样的情况。
我有 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();
}
在 cmets 中有一些关于为什么需要这些高精度的讨论,所以我想我会在这里总结一下。出于显示目的,我当然会对这些操作的结果进行四舍五入,但我对所有内部表示都使用十进制。一些计算沿途需要分数,这会导致数字超出小数类型的精度。
不过,我会尽量保持一切稳定以备积累。因此,例如,如果我将一个数量分成三分之三,我取 x/3、x/3,然后是 (x - x/3 - x/3)。这是一个计算物理量的系统,这些物理量经常像这样被分割,所以我不想过早地通过四舍五入来引入偏差。例如,如果我将上面 x=1 的结果四舍五入到小数点后三位,我将得到 0.333、0.333、0.334 作为运算的三个部分。
系统可以执行的操作的精确度存在实际的物理限制,但理想情况下,系统尝试执行的操作的逻辑说明应该尽可能精确。主要的关键要求是系统的总量不应因这些不同的操作而改变。在上述情况下,我发现 decimal 可能违反此假设,因此我想更好地了解为什么会发生这种情况以及如何解决它。
【问题讨论】:
-
十进制类型更准确,但也有其局限性。使用这么多小数位的确切意义是什么?这实际上是必需的,还是您只是想知道小数类型的真正限制?
-
@varocarbas,这些是由生产中的应用程序计算的实际数字。我的假设是这样的简单加减法总是适用于小数,即使它们处于精度的极限,因为加法和减法永远不需要更高的精度来表示相同基数的结果。我可以对早期操作的结果进行四舍五入以避免这种过分的精度,但这会引发其他过程问题(即,舍入的正确点是什么?)
-
恐怕你的说法“为小数工作”是错误的。只要在最大允许精度范围内,任何计算都将有效。示例:在允许 3 个小数的类型中,任何涉及更多小数的运算都将被截断,例如 0.333+0.777 或 0.333*0.777;您不能期望给定类型执行超出其可以处理的最大位数的计算。
-
关于四舍五入,从逻辑上讲,中间计算要尽量避免(因为会降低精度)。在最可能的情况下,通过考虑小数类型,您不需要对中间值进行四舍五入。即使在您的某种特殊条件下,您似乎也不必四舍五入任何中间值(只需四舍五入最终结果就足够了)。要四舍五入的小数位数取决于您的预期精度;我个人很少四舍五入超过 6 位小数(标准应用程序为 3-4),但这取决于您的要求。
-
@varocarbas,是的,这显然是乘法或除法的问题,但我不清楚为什么这是加法和减法的问题。 0.333 + 0.777 = 1.110;除非您假设它们是整数分数的截断表示,否则这里没有歧义/截断。在这种情况下,我并不担心,因为数字所代表的确切数量对我来说并不像它在积累下的稳定性那么重要。
标签: c# .net decimal rounding-error