Formatting Doubles for Output in C#

Formatting doubles for output in C#

The problem is that .NET will always round a double to 15 significant decimal digits before applying your formatting, regardless of the precision requested by your format and regardless of the exact decimal value of the binary number.

I'd guess that the Visual Studio debugger has its own format/display routines that directly access the internal binary number, hence the discrepancies between your C# code, your C code and the debugger.

There's nothing built-in that will allow you to access the exact decimal value of a double, or to enable you to format a double to a specific number of decimal places, but you could do this yourself by picking apart the internal binary number and rebuilding it as a string representation of the decimal value.

Alternatively, you could use Jon Skeet's DoubleConverter class (linked to from his "Binary floating point and .NET" article). This has a ToExactString method which returns the exact decimal value of a double. You could easily modify this to enable rounding of the output to a specific precision.

double i = 10 * 0.69;
Console.WriteLine(DoubleConverter.ToExactString(i));
Console.WriteLine(DoubleConverter.ToExactString(6.9 - i));
Console.WriteLine(DoubleConverter.ToExactString(6.9));

// 6.89999999999999946709294817992486059665679931640625
// 0.00000000000000088817841970012523233890533447265625
// 6.9000000000000003552713678800500929355621337890625

Formatting a double to two decimal places

string.Format will not change the original value, but it will return a formatted string. For example:

Console.WriteLine("Earnings this week: {0:0.00}", answer);

Note: Console.WriteLine allows inline string formatting. The above is equivalent to:

Console.WriteLine("Earnings this week: " + string.Format("{0:0.00}", answer));

How do I display a decimal value to 2 decimal places?


decimalVar.ToString("#.##"); // returns ".5" when decimalVar == 0.5m

or

decimalVar.ToString("0.##"); // returns "0.5"  when decimalVar == 0.5m

or

decimalVar.ToString("0.00"); // returns "0.50"  when decimalVar == 0.5m

C# double type formatting

Double values only hold 15 to 16 digits, you have 17 (if I counted right). Because 64 bit double numbers only hold 16 digits, your last digit is getting truncated and therefore when you convert the number to scientific notation, the last digit appears to have been truncated.

You should use Decimal instead. Decimal types can hold 128 bits of data, while double can only hold 64 bits.

Using String Format to show decimal up to 2 places or simple integer

An inelegant way would be:

var my = DoFormat(123.0);

With DoFormat being something like:

public static string DoFormat( double myNumber )
{
var s = string.Format("{0:0.00}", myNumber);

if ( s.EndsWith("00") )
{
return ((int)myNumber).ToString();
}
else
{
return s;
}
}

Not elegant but working for me in similar situations in some projects.

double.ToString(string) with custom numeric format string does not produce the expected result

Floating-point numbers are approximations: they have a limited number of bits, and they do their best to represent a number close to the one you asked for within the constraints of the available bits.

Most of the time this works fine, but things start breaking down as you reach the limits of their precision which is around 16 digits for double and 9 digits for float.

Specifically, a double cannot represent 63712373026.615219 exactly. With G50 or Jon Skeet's DoubleConverter, we can take a look at the exact number that the double does represent:

63712373026.615219.ToString("G50"); // 63712373026.6152191162109375

We're fine up to the 7th decimal place, but see how the closest representable number to 63712373026.615219 is actually a little bit larger?

With some trial-and-error, we can see the range of values which all get represented as 63712373026.6152191162109375:

63712373026.6152230.ToString("G50"); // 63712373026.61522674560546875
63712373026.6152229.ToString("G50"); // 63712373026.6152191162109375
63712373026.615219.ToString("G50"); // 63712373026.6152191162109375
63712373026.6152154.ToString("G50"); // 63712373026.6152191162109375
63712373026.6152153.ToString("G50"); // 63712373026.61521148681640625

The precision limitations of double mean that everything between 63712373026.6152154 and 63712373026.6152229 gets stored as the number 63712373026.6152191162109375.

This presents a problem for the formatter: if you asked for 63712373026.615219.ToString("0.000000"), should it give you 63712373026.615223 or 63712373026.615215 or anything in between?

In practice, what it appears to do is to work out the range of possible values which the double might be representing, and then round to the digits which are common to all. Since 63712373026.6152229 and 63712373026.6152154 and everything in between all start with 63712373026.6152, that is what the formatter works with. Which is why it will print 63712373026.615200 if you force it to: it knows that it doesn't have enough information to fill in those last 2 digits.


Note that I think the round-trip and G17 formats are misleading you slightly. Round-trip basically prints the fewest digits which will be parsed back into the same underlying double value. So 63712373026.615219 contains the smallest number of decimal places which gets parsed back into 63712373026.6152191162109375.

Note that they fixed R on .NET 5:

63712373026.615219.ToString("R"); // 63712373026.61522

G17 just prints 17 digits, regardless of the underlying value of the double. Because double only has around 16 digits of precision, this is also enough to safely round-trip the double.

This can be seen with simpler values, such as 0.1. double, not being base 10, can't exactly represent 0.1. Instead its closest value is:

0.1.ToString("G99"); // 0.1000000000000000055511151231257827021181583404541015625

However:

0.1.ToString("R"); // 0.1

The shortest value which gets represented as 0.1000000000000000055511151231257827021181583404541015625 is 0.1, so this is what R returns, even though it doesn't quite match the underlying representation. This is fine, because parsing 0.1 will result in a double whose underlying representation is 0.1000000000000000055511151231257827021181583404541015625, thus successfully round-tripping it.

Format double in C#

Using format strings is explained in:

  • Standard Numeric Format Strings
  • Custom Numeric Format Strings

For example, try:

  • (0.56789).ToString("F2")
  • (0.56789).ToString("0.00").

Note that the resulting value is NOT truncated, but rounded in both cases, resulting in "0.57".

Format to two decimal places


Console.WriteLine("{0:N2}", ((double)n) / 1450);


Related Topics



Leave a reply



Submit