【问题标题】:How to properly compare decimal values in C#?如何正确比较 C# 中的十进制值?
【发布时间】:2011-05-09 17:13:35
【问题描述】:

我有 C++ 的背景,我知道你无法准确地比较浮点数的相等性。对于 C#,我只是假设相同的策略适用于十进制值或任何浮点值。

基本上,我有两个十进制值,如果它们不相等,我需要执行一些操作。例如:

decimal value1, value2;
// Assume value1 and value2 are set somewhere to valid values.
if( value1 != value2 )
{
    // Do something
}

如果这不能按预期工作,我愿意接受一个解决方案,它可以用误差幅度进行相等比较,比如 .00001 或类似的东西。这个问题的推荐解决方案是什么?

【问题讨论】:

  • 它与双精度数而不是十进制数不完全相同,但请查看stackoverflow.com/questions/485175/…
  • @Mark:好点。撤回。
  • 您发布的代码是比较两位小数的正确方法。如果您想比较两位小数的误差范围,您只需执行if (Math.Abs(value2 - value1) < 0.00001) { ... }
  • 当您说“十进制”时,您是在谈论 the decimal type useful for financial and monetary calculations 还是使用“十进制”作为各种浮点格式的总称,包括 doublefloat、和decimal(如上链接)?
  • 我想澄清我在原始帖子中对“十进制”的使用。基本上,我指的是实际类型“十进制”(实际关键字)。我不太了解十进制类型的底层数据是什么样的,我只是假设它们是浮点值,而对于小数类型,它们永远不会完全相同(就像 C++ 中的浮点数和双精度数一样)。

标签: c# comparison floating-point


【解决方案1】:

您的代码将按预期工作。 C# decimals 经过优化,可以非常准确地表示以 10 为基数的数字,因此如果您要比较的是(金钱,...),一切都应该没问题。

这是 Jon Skeet 对小数准确性的非常清楚的解释:

Difference between decimal, float and double in .NET?

【讨论】:

  • 提供的所有答案都很有帮助。我将此标记为我的答案,主要是因为我意识到我没有完全理解 C# 中的小数是什么。小数似乎不像我想象的那样是浮点数。此外,解释差异的文章链接对我非常有帮助。
  • @Robert:从小数点位置“浮动”的意义上讲,小数是浮动的。小数不是“定点”数字,例如小数点前 15 位和小数点后 10 位。小数和双精度的区别在于,小数是 m x 10^e 形式的数字,而双精度是 m x 2^e 形式的数字。
  • 刚刚看到0.0000 != 0.0 返回true。但是 !(0.000).Equals(0.0) 返回 false。
  • 我有例如 TotalPrice = 10,Quantity = 3,并且 UnitPrice 计算为 UnitPrice = TotaPrice / Quantity。用户可以输入 3 个值中的任何 2 个。如何检查 TotalPrice 是否正确?
【解决方案2】:

我正在研究类似的东西,但要精确而不是误差范围,最后为 Float 编写了一些扩展。不过,这可以很容易地适应任何类型。我进行了一系列复杂的比较,这使它变得美观且易读。

/// <summary>
/// A set of extensions to allow the convenient comparison of float values based on a given precision.
/// </summary>
public static class FloatingPointExtensions
{
    /// <summary>
    /// Determines if the float value is less than or equal to the float parameter according to the defined precision.
    /// </summary>
    /// <param name="float1">The float1.</param>
    /// <param name="float2">The float2.</param>
    /// <param name="precision">The precision.  The number of digits after the decimal that will be considered when comparing.</param>
    /// <returns></returns>
    public static bool LessThan(this float float1, float float2, int precision)
    {
        return (System.Math.Round(float1 - float2, precision) < 0);
    }

    /// <summary>
    /// Determines if the float value is less than or equal to the float parameter according to the defined precision.
    /// </summary>
    /// <param name="float1">The float1.</param>
    /// <param name="float2">The float2.</param>
    /// <param name="precision">The precision.  The number of digits after the decimal that will be considered when comparing.</param>
    /// <returns></returns>
    public static bool LessThanOrEqualTo(this float float1, float float2, int precision)
    {
        return (System.Math.Round(float1 - float2, precision) <= 0);
    }

    /// <summary>
    /// Determines if the float value is greater than (>) the float parameter according to the defined precision.
    /// </summary>
    /// <param name="float1">The float1.</param>
    /// <param name="float2">The float2.</param>
    /// <param name="precision">The precision.  The number of digits after the decimal that will be considered when comparing.</param>
    /// <returns></returns>
    public static bool GreaterThan(this float float1, float float2, int precision)
    {
        return (System.Math.Round(float1 - float2, precision) > 0);
    }

    /// <summary>
    /// Determines if the float value is greater than or equal to (>=) the float parameter according to the defined precision.
    /// </summary>
    /// <param name="float1">The float1.</param>
    /// <param name="float2">The float2.</param>
    /// <param name="precision">The precision.  The number of digits after the decimal that will be considered when comparing.</param>
    /// <returns></returns>
    public static bool GreaterThanOrEqualTo(this float float1, float float2, int precision)
    {
        return (System.Math.Round(float1 - float2, precision) >= 0);
    }

    /// <summary>
    /// Determines if the float value is equal to (==) the float parameter according to the defined precision.
    /// </summary>
    /// <param name="float1">The float1.</param>
    /// <param name="float2">The float2.</param>
    /// <param name="precision">The precision.  The number of digits after the decimal that will be considered when comparing.</param>
    /// <returns></returns>
    public static bool AlmostEquals(this float float1, float float2, int precision)
    {
        return (System.Math.Round(float1 - float2, precision) == 0);
    } 
}

【讨论】:

    【解决方案3】:

    我同意其他答案,但我遇到了一个问题,即一个“真实的”服务器端小数与来自 JSON/浏览器的小数进行比较(并且在某些时候一定是浮点数)。

    我最终将这段代码四舍五入到小数点后 2 位,这在我的情况下已经足够精确了:

    if (Decimal.Round(serverTotalPrice, 2) != Decimal.Round(request.TotalPrice, 2)) {
        throw new ArgumentException("The submitted Total Price is not valid");
    }
    

    【讨论】:

    • 对此类任务使用 round 可能会导致问题,因为 1.116 将四舍五入为 1.12,而 1.113 将四舍五入为 1.11,因此根据您的代码,它们将不相等,您似乎希望它们考虑相等。
    • @iYalovoi 是的,我自己考虑过,但在我的情况下,js 中的浮点版本基于服务器端 .net 中的固定十进制值,因此我的服务器端 1.12 将是在 js 中,像 1.11999999 或 1.12000001 对我来说仍然可以正常工作。
    【解决方案4】:

    我认为this 会解决你的问题。

    基本上有一个decimal.compare方法。

    编辑:这可能是更好的方法:

    Decimal.Equals

    EDIT2:如果您可以按照上面的建议直接比较,那可能会更有效。我会留下这个,因为它可能很有趣。

    【讨论】:

    • 你可能是对的。那里也有一个 decimal.equals 。我没有使用我记得的它们,但它似乎是合适的。如果有人知道不同,我也会很好奇。
    • 这与x == y 有相同的问题,其中 x 是浮点类型。 (小数具有固定精度的事实只是有点相关,因为到达那里的数学是有问题的。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多