Discussion:
Printing double values
(too old to reply)
Stefan Ram
2017-03-31 18:03:30 UTC
Permalink
I made this observation:

When I add 0.00000000000002 to 133.2, the result
is 133.20000000000002, but the »2« at the end is
hidden, even when »F14« is used!

Only when I use »R14« is the final »2« printed.

I don't know the rules by which the F14 format
chooses to suppress the final »2«.

public static class Program
{ public static void Main()
{
global::System.Console.WriteLine
( 133.2 + 0.00000000000002 );

global::System.Console.WriteLine
( "{0:F14}", 133.2 + 0.00000000000002 );

global::System.Console.WriteLine
( "{0:R14}", 133.2 + 0.00000000000002 ); }}

133.2
133.20000000000000
133.20000000000002
Real Troll
2017-04-01 02:45:00 UTC
Permalink
Post by Stefan Ram
When I add 0.00000000000002 to 133.2, the result
is 133.20000000000002, but the »2« at the end is
hidden, even when »F14« is used!
Only when I use »R14« is the final »2« printed.
This is because what R format is supposed to do. It converts the
numeric value to string and then it reconverts back to its equivalent value.

<https://msdn.microsoft.com/en-us/library/dwhawy9k%28v=vs.110%29.aspx#RFormatString>
Post by Stefan Ram
I don't know the rules by which the F14 format
chooses to suppress the final »2«.
F is the standard "Fixed-Point ("F") Format" Specifier. F14 means it
has 14 decimal places in the number. See this:

<https://msdn.microsoft.com/en-us/library/dwhawy9k%28v=vs.110%29.aspx#FFormatString>

Try something like this:

double myNumber01 = 133.2;
double myNumber02 = 0.00000000000002;
double myNumber03 = myNumber01 + myNumber02;
Console.WriteLine(myNumber01.ToString("F14",
CultureInfo.InvariantCulture));
Console.WriteLine(myNumber02.ToString("F14",
CultureInfo.InvariantCulture));
Console.WriteLine(myNumber03.ToString("F14",
CultureInfo.InvariantCulture));
Console.WriteLine(myNumber03.ToString("R14",
CultureInfo.InvariantCulture));
Post by Stefan Ram
public static class Program
{ public static void Main()
{
global::System.Console.WriteLine
( 133.2 + 0.00000000000002 );
global::System.Console.WriteLine
( "{0:F14}", 133.2 + 0.00000000000002 );
global::System.Console.WriteLine
( "{0:R14}", 133.2 + 0.00000000000002 ); }}
133.2
133.20000000000000
133.20000000000002
Real Troll
2017-04-01 03:10:00 UTC
Permalink
You can see all the number formatters at this link:

<https://msdn.microsoft.com/en-us/library/dwhawy9k%28v=vs.110%29.aspx>
Arne Vajhøj
2017-04-02 00:59:37 UTC
Permalink
Post by Stefan Ram
When I add 0.00000000000002 to 133.2, the result
is 133.20000000000002, but the »2« at the end is
hidden, even when »F14« is used!
Only when I use »R14« is the final »2« printed.
I don't know the rules by which the F14 format
chooses to suppress the final »2«.
public static class Program
{ public static void Main()
{
global::System.Console.WriteLine
( 133.2 + 0.00000000000002 );
global::System.Console.WriteLine
( "{0:F14}", 133.2 + 0.00000000000002 );
global::System.Console.WriteLine
( "{0:R14}", 133.2 + 0.00000000000002 ); }}
133.2
133.20000000000000
133.20000000000002
Bunch of comments. Relative long reply.

First.

.NET Core is open source and I am sure that String.Format is
included in that so you could look at the code and see what
it actually does.

Second.

https://msdn.microsoft.com/en-us/library/dwhawy9k(v=vs.110).aspx#FFormatString

https://msdn.microsoft.com/en-us/library/dwhawy9k(v=vs.110).aspx#RFormatString

document F (fixed point) and R (roundtrip) format.

It clearly states that R ignores number of decimals specified. So
specifying it just contributes to the confusion.

Third.

I think a lot of this comes from the fact 133.20000000000002 has 17
digits and IEEE double only has 15.95 decimal digits.

Trying to discuss what happens with decimals not supported
by the type can be difficult. One obvious assumption would
be undefined or implementation specific.

Fourth.

The following program should shed some light on some
of the issues.

using System;
using System.Runtime.InteropServices;

namespace E
{
public static class Util
{
[StructLayout(LayoutKind.Explicit)]
private struct DoubleLong
{
[FieldOffset(0)]public double dv;
[FieldOffset(0)]public long lv;
}
public static long Bits(this double xv)
{
DoubleLong dl;
dl.lv = 0; // just to get rid of compiler error
dl.dv = xv;
return dl.lv;
}
}
public class Program
{
public static void Test(int digits, double delta)
{
double x = 133.2 + delta;
string s1 = String.Format("{0:F" + digits + "}", x);
double x1 = double.Parse(s1);
string s2 = String.Format("{0:R" + digits + "}", x);
double x2 = double.Parse(s2);
Console.WriteLine("delta : {0:F" + digits + "}", delta);
Console.WriteLine("F" + digits + " : {0}", s1);
Console.WriteLine("R" + digits + " : {0}", s2);
Console.WriteLine("original : {0,16:X}", x.Bits());
Console.WriteLine("F" + digits + " back : {0,16:X}",
x1.Bits());
Console.WriteLine("R" + digits + " back : {0,16:X}",
x2.Bits());
}
public static void Test(int digits)
{
Console.WriteLine("digits = {0}", digits);
Test(digits, 0.00000002);
Test(digits, 0.000000002);
Test(digits, 0.0000000002);
Test(digits, 0.00000000002);
Test(digits, 0.000000000002);
Test(digits, 0.0000000000002);
Test(digits, 0.00000000000002);
Test(digits, 0.000000000000002);
Test(digits, 0.0000000000000002);
Test(digits, 0.00000000000000002);
}
public static void Main(string[] args)
{
Test(10);
Test(12);
Test(14);
Test(16);
Console.ReadKey();
}
}
}

output:

digits = 10
delta : 0.0000000200
F10 : 133.2000000200
R10 : 133.20000001999998
original : 4060A6666671232D
F10 back : 4060A6666671232E
R10 back : 4060A6666671232D
delta : 0.0000000020
F10 : 133.2000000020
R10 : 133.200000002
original : 4060A66666677947
F10 back : 4060A66666677947
R10 back : 4060A66666677947
delta : 0.0000000002
F10 : 133.2000000002
R10 : 133.2000000002
original : 4060A666666681E3
F10 back : 4060A666666681E3
R10 back : 4060A666666681E3
delta : 0.0000000000
F10 : 133.2000000000
R10 : 133.20000000002
original : 4060A66666666926
F10 back : 4060A66666666666
R10 back : 4060A66666666926
delta : 0.0000000000
F10 : 133.2000000000
R10 : 133.20000000000198
original : 4060A666666666AC
F10 back : 4060A66666666666
R10 back : 4060A666666666AC
delta : 0.0000000000
F10 : 133.2000000000
R10 : 133.20000000000019
original : 4060A6666666666D
F10 back : 4060A66666666666
R10 back : 4060A6666666666D
delta : 0.0000000000
F10 : 133.2000000000
R10 : 133.20000000000002
original : 4060A66666666667
F10 back : 4060A66666666666
R10 back : 4060A66666666667
delta : 0.0000000000
F10 : 133.2000000000
R10 : 133.2
original : 4060A66666666666
F10 back : 4060A66666666666
R10 back : 4060A66666666666
delta : 0.0000000000
F10 : 133.2000000000
R10 : 133.2
original : 4060A66666666666
F10 back : 4060A66666666666
R10 back : 4060A66666666666
delta : 0.0000000000
F10 : 133.2000000000
R10 : 133.2
original : 4060A66666666666
F10 back : 4060A66666666666
R10 back : 4060A66666666666
digits = 12
delta : 0.000000020000
F12 : 133.200000020000
R12 : 133.20000001999998
original : 4060A6666671232D
F12 back : 4060A6666671232E
R12 back : 4060A6666671232D
delta : 0.000000002000
F12 : 133.200000002000
R12 : 133.200000002
original : 4060A66666677947
F12 back : 4060A66666677947
R12 back : 4060A66666677947
delta : 0.000000000200
F12 : 133.200000000200
R12 : 133.2000000002
original : 4060A666666681E3
F12 back : 4060A666666681E3
R12 back : 4060A666666681E3
delta : 0.000000000020
F12 : 133.200000000020
R12 : 133.20000000002
original : 4060A66666666926
F12 back : 4060A66666666926
R12 back : 4060A66666666926
delta : 0.000000000002
F12 : 133.200000000002
R12 : 133.20000000000198
original : 4060A666666666AC
F12 back : 4060A666666666AD
R12 back : 4060A666666666AC
delta : 0.000000000000
F12 : 133.200000000000
R12 : 133.20000000000019
original : 4060A6666666666D
F12 back : 4060A66666666666
R12 back : 4060A6666666666D
delta : 0.000000000000
F12 : 133.200000000000
R12 : 133.20000000000002
original : 4060A66666666667
F12 back : 4060A66666666666
R12 back : 4060A66666666667
delta : 0.000000000000
F12 : 133.200000000000
R12 : 133.2
original : 4060A66666666666
F12 back : 4060A66666666666
R12 back : 4060A66666666666
delta : 0.000000000000
F12 : 133.200000000000
R12 : 133.2
original : 4060A66666666666
F12 back : 4060A66666666666
R12 back : 4060A66666666666
delta : 0.000000000000
F12 : 133.200000000000
R12 : 133.2
original : 4060A66666666666
F12 back : 4060A66666666666
R12 back : 4060A66666666666
digits = 14
delta : 0.00000002000000
F14 : 133.20000002000000
R14 : 133.20000001999998
original : 4060A6666671232D
F14 back : 4060A6666671232E
R14 back : 4060A6666671232D
delta : 0.00000000200000
F14 : 133.20000000200000
R14 : 133.200000002
original : 4060A66666677947
F14 back : 4060A66666677947
R14 back : 4060A66666677947
delta : 0.00000000020000
F14 : 133.20000000020000
R14 : 133.2000000002
original : 4060A666666681E3
F14 back : 4060A666666681E3
R14 back : 4060A666666681E3
delta : 0.00000000002000
F14 : 133.20000000002000
R14 : 133.20000000002
original : 4060A66666666926
F14 back : 4060A66666666926
R14 back : 4060A66666666926
delta : 0.00000000000200
F14 : 133.20000000000200
R14 : 133.20000000000198
original : 4060A666666666AC
F14 back : 4060A666666666AD
R14 back : 4060A666666666AC
delta : 0.00000000000020
F14 : 133.20000000000000
R14 : 133.20000000000019
original : 4060A6666666666D
F14 back : 4060A66666666666
R14 back : 4060A6666666666D
delta : 0.00000000000002
F14 : 133.20000000000000
R14 : 133.20000000000002
original : 4060A66666666667
F14 back : 4060A66666666666
R14 back : 4060A66666666667
delta : 0.00000000000000
F14 : 133.20000000000000
R14 : 133.2
original : 4060A66666666666
F14 back : 4060A66666666666
R14 back : 4060A66666666666
delta : 0.00000000000000
F14 : 133.20000000000000
R14 : 133.2
original : 4060A66666666666
F14 back : 4060A66666666666
R14 back : 4060A66666666666
delta : 0.00000000000000
F14 : 133.20000000000000
R14 : 133.2
original : 4060A66666666666
F14 back : 4060A66666666666
R14 back : 4060A66666666666
digits = 16
delta : 0.0000000200000000
F16 : 133.2000000200000000
R16 : 133.20000001999998
original : 4060A6666671232D
F16 back : 4060A6666671232E
R16 back : 4060A6666671232D
delta : 0.0000000020000000
F16 : 133.2000000020000000
R16 : 133.200000002
original : 4060A66666677947
F16 back : 4060A66666677947
R16 back : 4060A66666677947
delta : 0.0000000002000000
F16 : 133.2000000002000000
R16 : 133.2000000002
original : 4060A666666681E3
F16 back : 4060A666666681E3
R16 back : 4060A666666681E3
delta : 0.0000000000200000
F16 : 133.2000000000200000
R16 : 133.20000000002
original : 4060A66666666926
F16 back : 4060A66666666926
R16 back : 4060A66666666926
delta : 0.0000000000020000
F16 : 133.2000000000020000
R16 : 133.20000000000198
original : 4060A666666666AC
F16 back : 4060A666666666AD
R16 back : 4060A666666666AC
delta : 0.0000000000002000
F16 : 133.2000000000000000
R16 : 133.20000000000019
original : 4060A6666666666D
F16 back : 4060A66666666666
R16 back : 4060A6666666666D
delta : 0.0000000000000200
F16 : 133.2000000000000000
R16 : 133.20000000000002
original : 4060A66666666667
F16 back : 4060A66666666666
R16 back : 4060A66666666667
delta : 0.0000000000000020
F16 : 133.2000000000000000
R16 : 133.2
original : 4060A66666666666
F16 back : 4060A66666666666
R16 back : 4060A66666666666
delta : 0.0000000000000002
F16 : 133.2000000000000000
R16 : 133.2
original : 4060A66666666666
F16 back : 4060A66666666666
R16 back : 4060A66666666666
delta : 0.0000000000000000
F16 : 133.2000000000000000
R16 : 133.2
original : 4060A66666666666
F16 back : 4060A66666666666
R16 back : 4060A66666666666

My conclusion is that:
* F does it best to produce a string that with the
limitations on precision in a double is most likely
to contain the "true" value.
* R simple does what it is documented to do: produces
a string that Double.Parse will convert back to the
same bit pattern as the original.

Arne

Continue reading on narkive:
Loading...