0

I faced rather odd behavior when multiplying doubles.

Make a simple console application and try following code.

private static void Main(string[] args)
{
    var b1 = 3.1 * 100D;
    var a1 = 4.1 * 100D;

    // put breakpoint here and observe b1 and a1 values, to me they are  
    // b1=310, a1=409.99999999999994
    Console.WriteLine(a1);
    // notice that Console.WriteLine actually returns 410
}

I suspect this is something with the double representation in binary but I can't explain myself how it is possible, can you help me out?

Also if you cast a1 to decimal it becomes 410 somehow...

I tried compiling against C# 3.0 and C#7.0 both gave the same results. I am reproducing this in Visual Studio 2017.

Pac0
  • 21,465
  • 8
  • 65
  • 74
kuskmen
  • 3,648
  • 4
  • 27
  • 54
  • It is the usual behavior of floating point arithmetic, in several languages. Not all numbers can be represented as floating point as usually defined by the relevant ISO standard (simple or double or any fixed precision). The decimal type is there to allow you to do those calculations without the error. – Pac0 Dec 19 '17 at 12:48
  • 1
    Are you asking why 4.1*100 is not exactly 410, or why 3.1*100 is 310 and not 309.999999999999994? – DodgyCodeException Dec 19 '17 at 12:50
  • @DodgyCodeExceptionI think the answer of both is the same, but lets say I am asking for both. – kuskmen Dec 19 '17 at 12:51
  • If you want to print out the number you see in Visual Studio, you need to add the [Round Trip format option](https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings) to `Console.WriteLine()`. `Console.WriteLine("{0:R}", a1)` – NightOwl888 Dec 19 '17 at 13:00
  • 1
    @NightOwl888 [Microsoft says](https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#RFormatString) the Round Trip format is not recommended for doubles, and that you should use G17 instead. – DodgyCodeException Dec 19 '17 at 13:05
  • in this case you **cannot** declare a1 or b1 as `decimal`. There is no implicit cast from `double` to `decimal`. ar you need to change the expression (cast the 3.1 / 4.1 to decimal, and remove the cast to double on the 100) – Pac0 Dec 19 '17 at 13:09
  • @DodgyCodeException - Thanks. I didn't even know G17 existed. Now I have some code to update... – NightOwl888 Dec 19 '17 at 13:16
  • 1
    @i486 Who said I have problems with using var or this behavior or can't solve my calculation issues? I asked very specific question which you are not answering at all, please stick to the question or do not spam the thread, thanks. – kuskmen Dec 19 '17 at 13:18
  • 2
    I undeleted my answer to answer the specifics on why 3.1 * 100 seems to work. It is just by chance. – Pac0 Dec 19 '17 at 13:42
  • @i486 you do realise decimal has its own rounding problems, don't you? – DodgyCodeException Dec 19 '17 at 14:16
  • By the way, if you used single-precision: `4.1f * 100f == 410` exactly. – DodgyCodeException Dec 19 '17 at 14:43
  • @DodgyCodeException I know. But in this case `decimal` arithmetic will solve the problem. Do you agree? – i486 Dec 19 '17 at 15:08
  • 2
    It depends what you mean by 'problem'. The OP doesn't appear to have a problem, only a question. My take on it is that as long as you understand that many decimal fractions can't be represented in binary, then it's no problem. – DodgyCodeException Dec 19 '17 at 15:11
  • So I cant be curious? Jeez @i486 grow up. – kuskmen Dec 19 '17 at 15:16

1 Answers1

2

Edit

(to answer the "Why 310 == 3.1 * 100" part )

This is only "by chance" that 3.1 * 100 == 310.

It just happens that the particular way of how floating points arithmetics work, the result of 3.1 * 100 gives 310.0000... . This numbers exactly matches the result you get if you convert 310 to a double.

The comparison will happily work in this particular case.

You can have a look at it with the DoubleConverter.ToExactString() function from Jon Skeet

    Console.WriteLine(DoubleConverter.ToExactString(3.1 * 100));
    Console.WriteLine(DoubleConverter.ToExactString(4.1 * 100));

    // output : 
    // 310
    // 409.99999999999994315658113919198513031005859375

(Previous)

In this particular case, the expression 4.1 * 100D is a double multiplied by a double.

The same error would be caused by 4.1 * 100 (double times int -> the int gets implicitly cast as double)

Since no type is specified for your variable, the compiler chooses to interpret the result as double. A rounding error due to floating-point arithmetic occurs in this particular case (4.1 has no exact representation as double)

If the variable is defined as decimal, the result is a decimal, and no rounding error occurs. Note that you cannot just change the varto decimal in the code you provided, you have to change your variables as well :

In this case, the multiplication will then give the exact value as decimal in both cases.

More on the floating-point errors here : Is floating point math broken?

decimal a1 = 4.1 * 100D; // compilation error : no implicit cast from double to decimal

decimal a1 = 4.1M * 100; // valid, and the multiplication will be performed exactly.
Pac0
  • 21,465
  • 8
  • 65
  • 74