'Can you explain this double to string conversion behavior in C#?

I'm trying to figure out why this happens and what is C# doing here.

Let's say we have a double: 277.3599853515625 (that's 13 digits after the period)

Then 277.3599853515625.ToString() -> "277.359985351563"

We lost a digit and it looks like the number got rounded UP.

But Math.Round(277.3599853515625,12) -> 277.359985351562 (looks like normal math rounding results in rounding DOWN)

I thought maybe if I give ToString() the formatting I want it would do the correct thing (give me the entire number):

277.3599853515625.ToString("0.#############") -> "277.359985351563" (that's 13 # signs, and still lost a digit and rounded UP)

If I reduce the last digit from 5 to 4, it rounds DOWN:

277.3599853515624.ToString("0.#############") -> "277.359985351562"

So it is clearly doing the rounding, but the rounding rules are different from normal math rounding. My first thought was that it's just treating 5 different, when normal rounding rounds 5 down, the ToString rounds it up, but look at this:

277.3599853515624999.ToString("0.#############") -> "277.359985351563" (WHAT?!?!?!?)

Do you have any idea what is happening here and what exactly C#'s logic in ToString() does?

The reason I'm asking is that I need to understand how to replicate the same behavior in a different language.

Thank you.



Solution 1:[1]

  1. 277.3599853515625.ToString() -> "277.359985351563"
    

    or

    277.3599853515624.ToString("0.#############") -> "277.359985351562"
    

    In this case, the ToString method is using MidpointRounding.AwayFromZero so that is why it converts 2 to 3 when the last digit is 5. For Reference, use this link: https://docs.microsoft.com/en-us/dotnet/api/system.midpointrounding?view=net-6.0#system-midpointrounding-awayfromzero

  2. Math.Round(277.3599853515625,12) -> 277.359985351562**
    

    In this case, Math.Round uses MidpointRounding.ToEven by default and rounds midpoint values to the nearest even number. Need to explicitly define specific MidpointRounding if ToEven is not required. For Reference, use this link: https://docs.microsoft.com/en-us/dotnet/api/system.math.round?view=net-6.0

  3. 277.3599853515624999.ToString("0.#############") -> "277.359985351563"
    

    (WHAT?!?!?!?)

    Here, there are two concepts. One is that ToString considers this 277.3599853515624999 as Double type, so it is a 16 digit number; that is why you are getting 16 digits.

    Console.WriteLine(277.3599853515624999.GetType()); // System.Double;
    

    Double-15-16 digits (64 bit)

    Decimal -28-29 significant digits (128 bit)
    

    Thus, if change this (277.3599853515624999) to (277.3599853515624999m.ToString()),
    then you get 277.3599853515624999

    And the second one is that there is also rounding done by MidpointRounding.AwayFromZero.

You can play with the below code:

    Decimal h1=  277.3599853515624999m;
    string hh= "277.3599853515624999";
    string h = 277.3599853515624999m.ToString();
    Console.WriteLine(277.3599853515624999.GetType()); // System.Double;
    Console.WriteLine(h);
    string hhh = Math.Round(277.345,2,MidpointRounding.AwayFromZero).ToString();
    Console.WriteLine(hhh);

I hope now there is a clear picture.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jeremy Caney