C# Getting strange results for a simple math opera

2019-02-27 01:16发布

问题:

I have noticed for some strange behavior while adding two doubles, sometimes it works correct, and sometimes not!

Here is the first example:

double num1 = 0.1 + 0.7; //Guess the result?

Easy - 0.8 !!! or not?

Look at the strange result:

And guess what, the if statement is going inside the else block, and prints the num1 - and no, it doesn't prints 0.799999999999993, it prints 0.8.

So I have gone one more step forward and tried this code:

if (0.1 + 0.7 == 0.8) //Returns false ??
{
    Console.WriteLine("Correct");
}

OK, strange, but now I found the correct way, it should use f (float). As I remember double has many spaces, so it can contain higher numbers, maybe this is the cause.

float num2 = 0.1f + 0.7f;

if (num2 == 0.8f) //Perfect - finally works !!!
{
    Console.WriteLine("Correct");
}
else
{
    Console.WriteLine(num2);
}

But now, I try this - and again it returns false, why?

if (0.1f + 0.7f == 0.8f) //Returns false :(
{
    Console.WriteLine("Correct");
}

The watch results when debugging it:

Can someone explain me what's wrong here? Are those bugs?

Thanks in advance.

回答1:

Floating point arithmetic is not precise. You should read the article What Every Computer Scientist Should Know About Floating-Point Arithmetic. If you want your calculations to be precise in a decimal sense, you should use the decimal type, where 0.1m + 0.7m == 0.8m is true. You should note that decimal uses floating point numbers just like float and double, except that the base is 10, not 2. Since dealing with base 2 is so much easier and highly-optimized, decimal is much slower. It also has its own inaccuracies, it's only precise when dealing with numbers that can be represented in a short number of decimal digits (like 0.7 and 0.1), making it good for financial data.

Another useful article to understand floating point numbers is Wikipedia's Double-precision floating-point format.



回答2:

Call the Math.Round method to ensure that both values have the same precision. The following example modifies a previous example to use this approach so that two fractional values are equivalent:

using System;

public class Example
{
   public static void Main()
   {
      double value1 = .333333333333333;
      double value2 = 1.0/3;
      int precision = 7;
      value1 = Math.Round(value1, precision);
      value2 = Math.Round(value2, precision);
      Console.WriteLine("{0:R} = {1:R}: {2}", value1, value2, value1.Equals(value2));
   }
}
// The example displays the following output: 
//        0.3333333 = 0.3333333: True


回答3:

Here is the answer: http://www.codeproject.com/Articles/383871/Demystify-Csharp-floating-point-equality-and-relat

Floating values are only approximations. To compare two floating point values you need to check the difference between them. The difference cannot be greater than float.Epsilon

Use this to compare these values:

bool AlmostEqualDoubles(double nVal1, double nVal2, int nPrecision = 6)
{
   nPrecision = Math.Max(Math.Min(16, nPrecision), 0);
   double nEpsilon = 1.0;
   for (int i = 0; i < nPrecision; i++) nEpsilon *= 0.1;       
   return (nVal2 - nEpsilon) < nVal1 && nVal1 < (nVal2 + nEpsilon);
}


回答4:

internally, a double is stored in bits, in base-2. All integers can be represented exactly in base-2, but when it comes to fractions, it;s another matter all together.

The same way that we cannot precisely respresent the result of 1/3 in our decimal system, in a binary system you cannot represent all fractions. Since decimal is base-10, it is easy to represent x/10 in decimal, but in base-2, it is impossible for some x.

So whenever you use doubles, always be aware of some rounding errors.

For this reason, doubles are not to be used whenever exact numbers are required, as with monetary amounts.