16

I have this tiny piece of code

double s = -2.6114289999999998;
double s7 = Math.Round(s, 7);
double s5 = Math.Round(s, 5);
double s6 = Math.Round(s, 6);

With Platform = Any CPU, I get

s7: -2.611429   
s5: -2.61143    
s6: -2.611429   

With Platform = x64, I get

s7: -2.6114289999999998 
s5: -2.61143    
s6: -2.6114289999999998

Why? (Output copied from VS's Locals window)


The whole piece of code is:

    private void btnAlign_Click(object sender, EventArgs e)
    {
        double s = -2.6114289999999998;
        double s7 = Math.Round(s, 7);
        double s5 = Math.Round(s, 5);
        double s6 = Math.Round(s, 6);
    }
beyond
  • 451
  • 2
  • 15
  • 2
    Can you post the whole code to reproduce? – netaholic Nov 15 '17 at 08:39
  • I'm asking for a colleague - He encountered it and I've vetted it on my own computer. – beyond Nov 15 '17 at 08:45
  • Check out this URL, it might help you understand why. https://stackoverflow.com/questions/2342396/why-does-this-floating-point-calculation-give-different-results-on-different-mac – i3lai3la Nov 15 '17 at 08:46
  • 1
    Confirmed in VS 15.3.3 with .NET 4.5.2. – l33t Nov 15 '17 at 08:46
  • 1
    Possible duplicate of [Why does this floating-point calculation give different results on different machines?](https://stackoverflow.com/questions/2342396/why-does-this-floating-point-calculation-give-different-results-on-different-mac) – Liam Nov 15 '17 at 09:02
  • I'd also recommend reading [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) – Liam Nov 15 '17 at 09:03
  • 1
    If you use Jon Skeet's [DoubleConverter](http://www.yoda.arachsys.com/csharp/doubleconverter.cs). `s6` and `s7` are the exact same value in 32 and 64-bit (which is the same as `s`). However they present differently in the debugger locals window. I would say it's more of an issue with the debugger's presentation than the runtime. – Mike Zboray Nov 15 '17 at 09:16
  • @mikez If you use `s7.ToString("R")` you'll see a difference. – Matthew Watson Nov 15 '17 at 09:21
  • @MatthewWatson Interesting... Assuming Jon Skeets code doesn't have a bug... it seems like the difference then is really how the double is converted to a string. – Mike Zboray Nov 15 '17 at 09:25

4 Answers4

4

The value -2.611429 cannot be represented using 64-bit floating point. When compiling in 32-bit mode the value will instead use 80-bit (extended precision).

l33t
  • 18,692
  • 16
  • 103
  • 180
  • Didn't know c# folded doubles out to more than 64 bit. Thanks. – beyond Nov 15 '17 at 09:10
  • 4
    Specifically, on x64, the SSE2 FPU is used and on x86 the x87 FPU is used. – Matthew Watson Nov 15 '17 at 09:36
  • 3
    `-2.611429` can't be represented exactly with binary-floating point *period*, no matter the precision. The result is a continued fraction that no finite amount of bits can represent. The difference in precision merely causes different results when rounding (and parsing strings), but it is not true (as could be inferred from this answer) that the 80-bit precision is somehow enough. – Jeroen Mostert Nov 15 '17 at 12:22
4

On x64, the SSE2 FPU is used and on x86 the x87 FPU is used.

It is possible (although not advised) to change the x87 precision to be the same as the SSE2 precision (i.e., use lower precision).

You can do that via the _controlfp() API.

The following program demonstrates. Run this program in x86 mode and you'll see how the use of _controlfp(0x00030000, 0x00020000) causes the output to change to be similar to (but not quite the same!) as the x64 version:

using System;
using System.Runtime.InteropServices;

namespace ConsoleApp3
{
    class Program
    {
        static void Main()
        {
            double s = -2.6114289999999998;

            Console.WriteLine(Math.Round(s, 7).ToString("R")); // -2.611429

            if (!Environment.Is64BitProcess)
                _controlfp(0x00030000, 0x00020000);

            Console.WriteLine(Math.Round(s, 7).ToString("R")); // -2.61142897605896
        }

        [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
        static extern uint _controlfp(uint newcw, uint mask);
    }
}

However, you should not mess around with the FPU in this way (and if you do, you should revert to the previous setting asap).

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
2

According to the CSharp language specification 3.0 the following might be the case:

See chapter 4.1.6 at the specification for more information

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.

In short: The C# spec actually states that hardware architectures may have some impact on floating point types (Double, Float).

The language specification as .doc can be found here.

Community
  • 1
  • 1
LOLWTFasdasd asdad
  • 2,625
  • 5
  • 26
  • 39
1

Answered here : https://stackoverflow.com/a/19978623/4891523

x64 managed code will use SSE for double/float computation instead of x87 FPU when using x86 managed code.

Liam
  • 27,717
  • 28
  • 128
  • 190
Adi
  • 475
  • 5
  • 15
  • 4
    x87 has *higher* precision, so how does that explain the above? Also, what's the point of calling Math.Round() if it fails to perform the desired operation? – l33t Nov 15 '17 at 08:50
  • If you think this is the answer then the correct response would be to flag this as a duplicate. That said I don't think this really answers the question. It makes an interesting (potentially related) point but does not explain the relevance to the question or the meaning of this sentence. For me this isn't a dupe, but neither is this the answer – Liam Nov 15 '17 at 08:59