如何在计算不准确时获得异常?

5

每当我们需要高十进制精度时,我们使用小数来进行计算。有没有办法检查精度是否足够进行计算?

我想让以下代码抛出异常:

decimal almostMax = Decimal.MaxValue - 1;
decimal x = almostMax + 0.1m; // This should create an exception, since x equals almostMax.
Assert.AreEqual(x, almostMax); // This does NOT fail.

在实际代码中并不是非常重要,但保持安全性还是很好的。


小数具有28位数字的精度(某些值可以有29位数字,但不是全部)。由于Decimal.MaxValue是一个28位数字,添加0.1m会超出小数的精度,这就是为什么这两个值相等且Assert不会失败的原因。 - PaulF
2
@PaulF,我认为OP已经意识到了这一点。他正在寻找一种在运行时检测此问题的方法。 - Good Night Nerd Pride
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Lukos
我不确定OP是否理解 - 检查值是否接近允许的最大值与检查数字是否接近其精度不同 - 如果添加的值为10到-29,这可能会在接近零时失败。检查十进制值是否具有足够的精度的唯一方法是检查该值具有的数字数 - 可以通过检查结果所需的数字数来使用此方法完成 - 请参见https://msdn.microsoft.com/en-us/library/system.decimal.getbits.aspx。 - PaulF
@PaulF 我非常确定 OP 理解这一点 :-) 我确实正在寻找一种在运行时自动检查此问题的简单方法。我希望避免手动执行它(例如通过实现 SafeDecimal)。 - Andreas
这将会非常复杂 - 你可能会将两个十进制范围内的值相加 - 将一个14位整数值小数与一个具有15位小数位的小数相加,从而超出了十进制的精度。也许你需要做的是检查这两个值是否都不为零,如果它们的和不等于这两个值,则抛出异常。 - PaulF
2个回答

3

这个扩展方法应该会有所帮助。它将反转操作并检查输入参数是否可以从结果正确计算。如果不是这种情况,则该操作导致精度损失。

public static decimal Add(this decimal a, decimal b)
{
    var result = a + b;

    if (result - a != b || result - b != a)
        throw new InvalidOperationException("Precision loss!");

    return result;
}

工作示例: https://dotnetfiddle.net/vx6UYY

如果您想使用像+等常规运算符,则需要使用Philipp Schmid的解决方案并在您自己的十进制类型上实现这些运算符。


1
要小心,它并不检查结果是否准确。当尝试添加 11m 时,如果实际上添加的是 10m,这种方法不会抛出异常。(result - a != b || result - b != a) 应该可以解决这个问题,除非我忽略了特殊情况。 - user743382
这是一个不错的选择,甚至可以保持性能影响在合理水平。 - Andreas

0
你可以创建一个SaveDecimal类并重载+运算符。
https://msdn.microsoft.com/zh-cn/library/aa288467(v=vs.71).aspx
public class SafeDecimal
{
    private decimal DecValue;

    public SafeDecimal(decimal Value)
    {
        DecValue = Value;
    }

    public decimal GetValue()
    {
        return DecValue;
    }

    public static SafeDecimal operator +(SafeDecimal A, SafeDecimal B)
    {
        decimal almostMax = Decimal.MaxValue - 1;

        checked
        {
            if (almostMax <= A.GetValue() + B.GetValue())
                throw new Exception("----scary error message----");
        }

        return new SafeDecimal(A.GetValue() + B.GetValue());
    }
}

1
它是否考虑了负值? - zerkms
不,它没有。我应该修复它,谢谢。 - Schmid
1
C#已经为算术运算提供了溢出保护(请参见此示例:http://csharppad.com/gist/7f4edd08bde1061201ca13dca9332806)。你正在重新实现已经存在的东西,而且问题是关于精度损失而不是溢出。解决方案的一般思路似乎不错。只需更改代码,使`+`在满足OP指定条件时抛出异常,这个答案在我看来就很好了。 - Good Night Nerd Pride
这次我希望翻译得没问题,如果有误请指出。我无法打开你的链接(网站被封锁),Abbondanza,但通过谷歌搜索解决了。说实话,我自己也不知道´checked´关键字。我进行了简短的测试,似乎可以工作。代码仍然没有检查非常小的数字,但我现在有点赶时间,稍后可能会添加。我会阅读每个评论以改进这段代码。 - Schmid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接