关于c#:比较从双打中输入的小数是否安全

Is it safe to compare decimals casted from doubles

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

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

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

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

比较双精度小数是否总是安全的,或者在某些情况下可以给出意外的结果?

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


It is commonly known that it is not safe to compare double values in .net. In this example it returns false

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

假设一个任意的十进制表示,即使它很短(用十进制表示),也不能完全用二进制浮点表示。假设二元浮点值之间的-产生的结果与数学结果最接近的可表示的浮点值不同(特别是,假设它总是产生数学结果是不安全的)。

d1 == d2是完全安全的,(decimal)d1 == (decimal)d2也是如此。第一个不会总是归还你想要的东西,第二个也不会,因为根据上述原则,这是没有理由的。在计算了d1d2之后,已经进行了近似计算:

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

这些近似值是复合的,加上转换到c中的decimal的第三个近似值,得到您期望的值,这是纯粹的巧合。如果有什么是众所周知的,那就是在一个近似值发生之后,修正它就太晚了,而添加另一个近似值并没有真正的帮助。请参阅本文中的第一个示例Excel,这是微软的另一个可编程产品。


在这种情况下,您可能会得到期望的结果,但通常您的答案是否定的。当您将一个值存储在double中,然后将其转换为十进制时,不会提高其精度(link)。

考虑下面的例子。相等返回真,但我们期望是假。

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

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

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


MSDN表示:

The decimal type has 29 digits of precision, whereas a difference between these two values can be detected only with 30 digits of precision.

有一个例子演示了十进制值的比较问题。有关详细信息,请参阅https://msdn.microsoft.com/library/system.decimal.compare(v=vs.110).aspx


如果您知道浮点和十进制类型之间的转换语义,并认识到对实现代码的影响,那么它总是安全的。

也就是说,除非你有一个好的铸造理由,否则你会更安全地遵循标准方法来检查某些给定公差的相等性。(如(0.999 - 1.00) < 0.01)

从显式数字转换表(C引用):

  • When you convert float or double to decimal, the source value is
    converted to decimal representation and rounded to the nearest number
    after the 28th decimal place if required. Depending on the value of
    the source value, one of the following results may occur:

    • If the source value is too small to be represented as a decimal, the
      result becomes zero.

    • If the source value is NaN (not a number), infinity, or too large to
      be represented as a decimal, an OverflowException is thrown.

    • When you convert decimal to float or double, the decimal value is
      rounded to the nearest double or float value.


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

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

decimaldouble存储的值更接近实际值,因此这将相等:

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

但值得注意的是,即使是decimal也是一个近似值。例如,您不能使用十进制存储pi的精确值。

It is commonly known that it is not safe to compare double values in .net.

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

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

1
double x = 0.1d;

x将存储最接近该值的两倍。

重要区别

需要注意的是,与double不同,decimal不会通过标准化来存储自身,但它会记住零。例如,尝试以下代码:

1
2
3
4
5
decimal dec1 = 1.000000000m;
double dbl11 = 1.000000000;

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

<==尝试==>

decimal作为基10存储在存储器中,而doublefloat存储在基2中。它们都有以下组成部分:尾数、指数和符号。例如:

44.5 could be represented in"decimal floating point" as mantissa 4.45 with an exponent of 1, whereas 4450 would have the same mantissa but an exponent of 3.

如果你好奇的话,你可以多读一些浮点数和小数。

有点离题但很有趣的问题

我正在和某人谈论小数和近似值等问题,他们提出了这样一个问题:假设你拥有一家商店,你花1.00美元买了3件商品。你想减价,这样哪个顾客就必须接受打击并支付额外的一分钱?