5

I have the following code:

 float a = 0.02f * 28f;
 double b = (double)a;
 double c = (double)(0.02f * 28f);
 Console.WriteLine(String.Format("  {0:F20}",  b));
 Console.WriteLine(String.Format("  {0:F20}", c));

However it returns different results whether it's compiled from VS2012 or VS2015 (both have "standard" settings)

In VS2012

0,56000000238418600000
0,55999998748302500000

In VS2015:

0,56000000238418600000
0,56000000238418600000

VS2012 dissasembly:

           float a = 0.02f * 28f;
0000003a  mov         dword ptr [ebp-40h],3F0F5C29h 
            double b = (double)a;
00000041  fld         dword ptr [ebp-40h] 
00000044  fstp        qword ptr [ebp-48h] 
            double c = (double)(0.02f * 28f);
00000047  fld         qword ptr ds:[001D34D0h] 
0000004d  fstp        qword ptr [ebp-50h] 

VS2015 dissasembly:

            float a = 0.02f * 28f;
001E2DE2  mov         dword ptr [ebp-40h],3F0F5C29h  
            double b = (double)a;
001E2DE9  fld         dword ptr [ebp-40h]  
001E2DEC  fstp        qword ptr [ebp-48h]  
            double c = (double)(0.02f * 28f);
001E2DEF  fld         dword ptr ds:[1E2E7Ch]  
001E2DF5  fstp        qword ptr [ebp-50h]  

As we can see the disassembly is not identical in both cases, is this normal? Could this be a bug in VS2012 or VS2015 ? Or is this behaviour controlled by some specific setting that was changed? thanks!

lezebulon
  • 7,607
  • 11
  • 42
  • 73
  • My first guess would be, that there are different buildparams for roslyn and/or different .net versions to cause this – Sebastian L Mar 15 '17 at 09:49
  • 1
    is this debug or release? what's the result in 64-bit build? 32-bit build uses x87 so it's not as fast and consistent like SSE2 – phuclv Mar 15 '17 at 09:51
  • the assembly listings are equal but for addresses. the data segment selector ds:[...] points to a compile-time constant (obviously the result of 0.02f * 28f is baked into the assembly). there is no multiplication at runtime. – Cee McSharpface Mar 15 '17 at 09:52
  • @dlatikay the 2012 version has dword instruction somewhere where the others have only qword instructions (I don't know if it's related) – lezebulon Mar 15 '17 at 09:54
  • doubles always gives different results - it's a binary floating point number. It will try and get as close as possible to the correct number, but won't always. It also depends on how much memory is allocated behind the scenes for it to sit in. – Callum Linington Mar 15 '17 at 09:56

1 Answers1

5

The issue here is that in the Roslyn compiler, constant floating point calculations done at compile time are performed slightly differently from earlier versions - which is resulting in different behaviour.

However, this is not a bug because of this section of the C# standard:

4.1.6 Floating-point types

Floating-point operations may be performed with higher precision than the result type of the operation. For example, some hardware architectures support an “extended” or “long double” floating-point type with greater range and precision than the double type, and implicitly perform all floating-point operations using this higher precision type. Only at excessive cost in performance can such hardware architectures be made to perform floating-point operations with less precision, and rather than require an implementation to forfeit both performance and precision, C# allows a higher precision type to be used for all floating-point operations. Other than delivering more precise results, this rarely has any measurable effects. However, in expressions of the form x * y / z, where the multiplication produces a result that is outside the double range, but the subsequent division brings the temporary result back into the double range, the fact that the expression is evaluated in a higher range format may cause a finite result to be produced instead of an infinity. To force a value of a floating point type to the exact precision of its type, an explicit cast can be used.

What's happening is that you are seeing the results of undefined behaviour resulting from the above, where the floating point calculation being done at compile time by the Roslyn compiler is using a different precision from the calculation done at compile time by earlier compilers.

Note that there was actually a bug in the initial release of the Roslyn compiler which was fixed by Update 2: https://github.com/dotnet/roslyn/issues/7262 - but this is just a point of interest and is not directly related to the differences in results between VS2012 and VS2015 (with Update 2).

Also see the following for more details:

Community
  • 1
  • 1
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • thanks ! regarding the bug report, does it mean that the VS2012 is bugged ? or 2015? or none? – lezebulon Mar 15 '17 at 10:02
  • @lezebulon The first release of the VS2015 compiler had the bug that was fixed in Update 2, but the difference in results you are seeing is unrelated to that and is NOT a bug. I've clarified my answer to make this clearer. – Matthew Watson Mar 15 '17 at 10:04
  • It looks to me like the arithmetic is done at compile-time in both cases (which is what I'd expect) - it's just done in a slightly different way in Roslyn. You mention that you think the arithmetic *isn't* done at compile-time for `double` values... that's not what I see - could you go into more detail? – Jon Skeet Mar 15 '17 at 10:09
  • @Jon Actually you're correct - I'll amend the answer accordingly. (I have based my answer on comments from `gafter` in the pages I've linked - I'm assuming that he is an authoritative source!). – Matthew Watson Mar 15 '17 at 10:13
  • @JonSkeet My comment about "not double" was meant to indicate that these differences don't appear to occur if you use `double` values and types instead of `float`. I've removed the comment since it is redundant (and confusing). – Matthew Watson Mar 15 '17 at 10:22
  • I suspect you'd see differences in some cases anyway, as I believe the whole machinery for the constant calculations is different in Roslyn. It may just be harder to find examples. – Jon Skeet Mar 15 '17 at 11:10