【问题标题】:Is it safe to compare decimals casted from doubles比较从双精度数转换的小数是否安全
【发布时间】:2017-10-11 06:51:35
【问题描述】:

众所周知,用 == 运算符比较双精度值是不安全的。在本例中,它返回 false:

double d1 = 0.11;
double d2 = 0.44 - 0.33;
Console.WriteLine(d1 == d2);

但是,如果我们将值转换为十进制,它会返回 true:

double d1 = 0.11;
double d2 = 0.44 - 0.33;
Console.WriteLine((decimal)d1 == (decimal)d2);

比较从 double 转换的小数是否总是安全的,或者在某些情况下会产生意想不到的结果?

更新: 侯赛因的例子很好,它表明它可能是错误的。但我更感兴趣的是看看是否有相反的例子,当我们期望十进制值相等而它们不相等时。更广泛地说,当我们从 double 转换为 decimal 时究竟会发生什么。

【问题讨论】:

标签: c# .net floating-point


【解决方案1】:

众所周知,在 .net 中比较双精度值是不安全的。在这个例子中它返回 false

你误会了什么是不安全的。

假设任意十进制表示,即使它很短(用十进制编写),也可以精确地以二进制浮点表示,这是不安全的。假设二进制浮点值之间的- 产生的不是最接近数学结果的可表示浮点值是不安全的(特别是假设它总是产生数学结果是不安全的) .

d1 == d2 非常安全,(decimal)d1 == (decimal)d2 也是如此。第一个不会总是返回你想要的东西,但第二个也不会,因为它没有理由按照上面列出的原则。一旦计算了d1d2,就已经完成了近似:

  • 表示近似,因为您似乎认为0.11 应该是数学值 11/100,但它不是,它是最接近的可表示值;
  • 运算近似,因为0.44减去0.33的数学结果可能无法精确表示,在这种情况下将使用最接近的可表示值。

这些近似值复合,并且在 C# 中添加转换为 decimal 的第三个近似值时,会产生您预期的值,这纯属巧合。如果有什么是众所周知的,那应该是在近似值发生后修复它为时已晚,并且添加另一个近似值并没有真正的帮助。请参阅本文中的第一个示例,Excel,这是 Microsoft 的另一款可编程产品。

【讨论】:

    【解决方案2】:

    在这种情况下,您可能会得到想要的结果,但通常您的答案是NO。 当您将值存储在 double 中时,然后将其转换为 decimal 不会提高其精度 (link)。

    考虑下面的例子。等式返回 true,但我们期望 false。

    double myDouble1 = 0.11;
    double myDouble2 = 0.10999999999999999;
    Console.WriteLine((decimal)myDouble1 == (decimal)myDouble2); //true, but we expect false
    

    但如果您将这些值存储为十进制,那么您将得到正确的结果。

    decimal myDecimal1 = 0.11M;
    decimal myDecimal2 = 0.10999999999999999M;
    Console.WriteLine(myDecimal1 == myDecimal2); //false
    

    【讨论】:

    • 为什么你认为目标是提高精度? OP没有提到提高精度。比较时,我们通常希望降低精度,而不是提高精度。
    • 在 Alex 的例子中,我认为他正在寻求提高精度
    【解决方案3】:

    它总是安全的,只要你知道浮点和十进制类型之间的转换语义,并且认识到对实现代码的影响。

    也就是说,除非您有充分的理由进行强制转换,否则按照标准方法检查某些给定容差的相等性会更安全。 (例如(0.999 - 1.00) < 0.01。)

    来自Explicit Numeric Conversions Table (C# Reference)

    • 当您将浮点数或双精度数转换为十进制时,源值为 转换为十进制表示并四舍五入到最接近的数字 如果需要,在小数点后 28 位之后。取决于价值 源值,可能会出现以下结果之一:

      • 如果源值太小而无法表示为十进制,则 结果变为零。

      • 如果源值是 NaN(不是数字)、无穷大或太大而无法 表示为十进制,则会引发 OverflowException。

      • 当您将十进制转换为浮点数或双精度时,十进制值为 四舍五入到最接近的双精度或浮点值。

    【讨论】:

      【解决方案4】:

      MSDN 说:

      decimal 类型有 29 位精度,而这两个值之间的差异只有 30 位精度才能检测到。

      有一个例子演示了十进制值的比较问题。详情见https://msdn.microsoft.com/library/system.decimal.compare(v=vs.110).aspx

      【讨论】:

        【解决方案5】:

        double 存储近似值,因此这种情况不相等:

        double d1 = 0.11;
        double d2 = 0.44 - 0.33;
        Console.WriteLine(d1 == d2);
        

        decimal 存储的值比double 存储的值更接近实际值,因此这将是相等的:

        Console.WriteLine((decimal)d1 == (decimal)d2);
        

        但重要的是要注意,即使decimal 也是一个近似值。例如,您不能使用小数存储 Pi 的确切值。

        众所周知,在 .net 中比较双精度值是不安全的。

        这完全取决于您的计算所需的精度水平。对于某些计算,可以使用它,而对于其他计算则不然。

        System.Double(C# 中的double)和System.Single(C# 中的float)存储为近似值。例如:

        double x = 0.1d;
        

        x 将存储最接近该值的可用双精度值。


        重要区别

        值得注意的是,与double 不同,decimal 不会通过标准化存储自己,但会记住零。例如,试试下面的代码:

        decimal dec1 = 1.000000000m;
        double dbl11 = 1.000000000;
        
        Console.WriteLine(dec1);  // outputs: 1.000000000 (remembers all 9 zeros)
        Console.WriteLine(dbl11); // outputs: 1
        

        <==Try Me==>

        decimal 以 10 为底存储在内存中,但 doublefloat 以 2 为底存储。它们都具有以下组件:尾数、指数和符号。例如:

        44.5 可以用“十进制浮点”表示为尾数 4.45,指数为 1,而 4450 的尾数相同,但指数为 3。

        如果您有兴趣,可以在Floating PointsDecimals 上阅读更多内容。


        有点离题但很有趣的问题

        我正在与某人谈论小数和近似值等问题,他们提出了这个问题:假设您拥有一家商店,并且以 1.00 美元的价格购买了 3 件商品。即便如此,您也想削减开支,让哪个客户不得不承受打击并支付额外的一分钱?

        【讨论】:

        • "double 存储近似值" - 不,double 存储 一个非常精确的值。许多算术运算(包括源代码中的小数转换)会丢失信息以生成double,但值本身是精确的。
        • 那第一个case怎么不等于呢?抱歉明天会回复,以防您回复 - 太晚了,需要睡觉:)
        • double 的转换时会发生近似值,并且可能在执行减法时再次发生 - 但double 值本身不是近似值。这就像谈论int - 如果你写int x = (int) 10.6;,那么它将存储值10。信息在转换中丢失,但值正好是10。
        • @JonSkeet 如果 0.11 存储为 0.11000000000000000055511151231257827021181583404541015625,那怎么不是近似值?在转换过程中肯定会发生近似,但在第一种情况下没有转换。
        • 当然有一个转换——它是从十进制浮点(在源代码中)到二进制浮点。并不是说 0.11 存储为 0.110000000000000000555... - 而是精确存储了 0.110000000000000000555,编译器确定最接近 0.11 的值是 0.110000000000000000555。了解近似发生的位置 - 信息丢失的位置 - 很重要,IMO。
        猜你喜欢
        • 1970-01-01
        • 2012-12-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多