Skip to content

Commit

Permalink
Problem of round-tripping Double values (dotnet#3101)
Browse files Browse the repository at this point in the history
* Problem of round-tripping Double values

* Addressed review comments, revised Single documentation

* Incorporated reviewer comments
  • Loading branch information
Ron Petrusha committed Sep 23, 2017
1 parent e89ea10 commit a11b9ca
Show file tree
Hide file tree
Showing 15 changed files with 122 additions and 36 deletions.
56 changes: 32 additions & 24 deletions docs/standard/base-types/standard-numeric-format-strings.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public static void Main()
StreamWriter sw = new StreamWriter(@".\Doubles.dat");
Double[] values = { 2.2/1.01, 1.0/3, Math.PI };
for (int ctr = 0; ctr < values.Length; ctr++)
sw.Write("{0:R}{1}", values[ctr], ctr < values.Length - 1 ? "|" : "" );
sw.Write("{0:G17}{1}", values[ctr], ctr < values.Length - 1 ? "|" : "" );

sw.Close();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public static void Main()
StreamWriter sw = new StreamWriter(@".\Singles.dat");
Single[] values = { 3.2f/1.11f, 1.0f/3f, (float) Math.PI };
for (int ctr = 0; ctr < values.Length; ctr++)
sw.Write("{0:R}{1}", values[ctr], ctr < values.Length - 1 ? "|" : "" );
sw.Write("{0:G9}{1}", values[ctr], ctr < values.Length - 1 ? "|" : "" );

sw.Close();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public static void Main()
value2 = ((float) Math.Sqrt(value2)) / 3.51f;
Console.WriteLine("{0} = {1}: {2}\n",
value1, value2, value1.Equals(value2));
Console.WriteLine("{0:R} = {1:R}", value1, value2);
Console.WriteLine("{0:G9} = {1:G9}", value1, value2);
}
}
// The example displays the following output:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static void Main()
if (total.Equals(result))
Console.WriteLine("The sum of the values equals the total.");
else
Console.WriteLine("The sum of the values ({0:R}) does not equal the total ({1:R}).",
Console.WriteLine("The sum of the values ({0}) does not equal the total ({1}).",
total, result);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#using <System.Numerics.dll>

using namespace System;
using namespace System::Numerics;

void main()
{
BigInteger value = BigInteger::Pow(Int64::MaxValue, 2);
Console::WriteLine(value.ToString("R"));
}
// The example displays the following output:
// 85070591730234615847396907784232501249


Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Numerics;

public class Example
{
public static void Main()
{
var value = BigInteger.Pow(Int64.MaxValue, 2);
Console.WriteLine(value.ToString("R"));
}
} // The example displays the following output:
// 85070591730234615847396907784232501249


Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Imports System.Numerics

Module Example
Public Sub Main()
Dim value = BigInteger.Pow(Int64.MaxValue, 2)
Console.WriteLine(value.ToString("R"))
End Sub
End Module
' The example displays the following output:
' 85070591730234615847396907784232501249
20 changes: 20 additions & 0 deletions samples/snippets/standard/base-types/format-strings/csharp/g17.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;

public class Example
{
public static void Main()
{
double original = 0.84551240822557006;
var rSpecifier = original.ToString("R");
var g17Specifier = original.ToString("G17");

var rValue = Double.Parse(rSpecifier);
var g17Value = Double.Parse(g17Specifier);

Console.WriteLine($"{original:G17} = {rSpecifier} (R): {original.Equals(rValue)}");
Console.WriteLine($"{original:G17} = {g17Specifier} (G17): {original.Equals(g17Value)}");
}
}
// The example displays the following output:
// 0.84551240822557006 = 0.84551240822557: False
// 0.84551240822557006 = 0.84551240822557006: True
17 changes: 17 additions & 0 deletions samples/snippets/standard/base-types/format-strings/vb/g17.vb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Module Example
Public Sub Main()
Dim original As Double = 0.84551240822557006
Dim rSpecifier = original.ToString("R")
Dim g17Specifier = original.ToString("G17")

Dim rValue = Double.Parse(rSpecifier)
Dim g17Value = Double.Parse(g17Specifier)

Console.WriteLine($"{original:G17} = {rSpecifier} (R): {original.Equals(rValue)}")
Console.WriteLine($"{original:G17} = {g17Specifier} (G17): {original.Equals(g17Value)}")
End Sub
End Module
' The example displays the following output:
' 0.84551240822557006 = 0.84551240822557 (R): False
' 0.84551240822557006 = 0.84551240822557006 (G17): True

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Module Example
Dim sw As New StreamWriter(".\Doubles.dat")
Dim values() As Double = { 2.2/1.01, 1.0/3, Math.PI }
For ctr As Integer = 0 To values.Length - 1
sw.Write("{0:R}{1}", values(ctr),
sw.Write("{0:G17}{1}", values(ctr),
If(ctr < values.Length - 1, "|", ""))
Next
sw.Close()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Module Example
Dim sw As New StreamWriter(".\Singles.dat")
Dim values() As Single = { 3.2/1.11, 1.0/3, CSng(Math.PI) }
For ctr As Integer = 0 To values.Length - 1
sw.Write("{0:R}{1}", values(ctr),
sw.Write("{0:G9}{1}", values(ctr),
If(ctr < values.Length - 1, "|", ""))
Next
sw.Close()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Module Example
Console.WriteLine("{0} = {1}: {2}",
value1, value2, value1.Equals(value2))
Console.WriteLine()
Console.WriteLine("{0:R} = {1:R}", value1, value2)
Console.WriteLine("{0:G9} = {1:G9}", value1, value2)
End Sub
End Module
' The example displays the following output:
Expand Down
9 changes: 6 additions & 3 deletions xml/System/Double.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,19 @@
When accuracy in numeric operations with fractional values is important, you can use the <xref:System.Decimal> rather than the <xref:System.Double> type. When accuracy in numeric operations with integral values beyond the range of the <xref:System.Int64> or <xref:System.UInt64> types is important, use the <xref:System.Numerics.BigInteger> type.
- A value might not round-trip if a floating-point number is involved. A value is said to round-trip if an operation converts an original floating-point number to another form, an inverse operation transforms the converted form back to a floating-point number, and the final floating-point number is not equal to the original floating-point number. The roundtrip might fail because one or more least significant digits are lost or changed in a conversion. In the following example, three <xref:System.Double> values are converted to strings and saved in a file. As the output shows, however, even though the values appear to be identical, the restored values are not equal to the original values.
- A value might not round-trip if a floating-point number is involved. A value is said to round-trip if an operation converts an original floating-point number to another form, an inverse operation transforms the converted form back to a floating-point number, and the final floating-point number is not equal to the original floating-point number. The round trip might fail because one or more least significant digits are lost or changed in a conversion. In the following example, three <xref:System.Double> values are converted to strings and saved in a file. As the output shows, however, even though the values appear to be identical, the restored values are not equal to the original values.
[!code-csharp[System.Double.Structure#7](~/samples/snippets/csharp/VS_Snippets_CLR_System/system.double.structure/cs/precisionlist4.cs#7)]
[!code-vb[System.Double.Structure#7](~/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.double.structure/vb/precisionlist4.vb#7)]
In this case, the values can be successfully round-tripped by using the "R" [standard numeric format string](~/docs/standard/base-types/standard-numeric-format-strings.md) to preserve the full precision of <xref:System.Double> values, as the following example shows.
In this case, the values can be successfully round-tripped by using the "G17" [standard numeric format string](~/docs/standard/base-types/standard-numeric-format-strings.md) to preserve the full precision of <xref:System.Double> values, as the following example shows.
[!code-csharp[System.Double.Structure#8](~/samples/snippets/csharp/VS_Snippets_CLR_System/system.double.structure/cs/precisionlist5.cs#8)]
[!code-vb[System.Double.Structure#8](~/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.double.structure/vb/precisionlist5.vb#8)]
> [!IMPORTANT]
> When used with a <xref:System.Double> value, the "R" format specifier in some cases fails to successfully round-trip the original value. To ensure that <xref:System.Double> values successfully round-trip, use the "G17" format specifier.
- <xref:System.Single> values have less precision than <xref:System.Double> values. A <xref:System.Single> value that is converted to a seemingly equivalent <xref:System.Double> often does not equal the <xref:System.Double> value because of differences in precision. In the following example, the result of identical division operations is assigned to a <xref:System.Double> and a <xref:System.Single> value. After the <xref:System.Single> value is cast to a <xref:System.Double>, a comparison of the two values shows that they are unequal.
[!code-csharp[System.Double.Structure#5](~/samples/snippets/csharp/VS_Snippets_CLR_System/system.double.structure/cs/precisionlist1.cs#5)]
Expand Down
4 changes: 2 additions & 2 deletions xml/System/Single.xml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
[!code-csharp[System.Single.Structure#17](~/samples/snippets/csharp/VS_Snippets_CLR_System/system.single.structure/cs/precisionlist4a.cs#17)]
[!code-vb[System.Single.Structure#17](~/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.single.structure/vb/PrecisionList4a.vb#17)]
In this case, the values can be successfully round-tripped by using the "R" [standard numeric format string](~/docs/standard/base-types/standard-numeric-format-strings.md) to preserve the full precision of <xref:System.Single> values, as the following example shows.
In this case, the values can be successfully round-tripped by using the "G9" [standard numeric format string](~/docs/standard/base-types/standard-numeric-format-strings.md) to preserve the full precision of <xref:System.Single> values, as the following example shows.
[!code-csharp[System.Single.Structure#18](~/samples/snippets/csharp/VS_Snippets_CLR_System/system.single.structure/cs/PrecisionList5a.cs#18)]
[!code-vb[System.Single.Structure#18](~/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.single.structure/vb/PrecisionList5a.vb#18)]
Expand All @@ -125,7 +125,7 @@
[!code-csharp[System.Single.Structure#9](~/samples/snippets/csharp/VS_Snippets_CLR_System/system.single.structure/cs/comparison1.cs#9)]
[!code-vb[System.Single.Structure#9](~/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.single.structure/vb/comparison1.vb#9)]
Calculated values that follow different code paths and that are manipulated in different ways often prove to be unequal. In the following example, one <xref:System.Single> value is squared, and then the square root is calculated to restore the original value. A second <xref:System.Single> is multiplied by 3.51 and squared before the square root of the result is divided by 3.51 to restore the original value. Although the two values appear to be identical, a call to the <xref:System.Single.Equals%28System.Single%29> method indicates that they are not equal. Using the "R" standard format string to return a result string that displays all the significant digits of each <xref:System.Single> value shows that the second value is .0000000000001 less than the first.
Calculated values that follow different code paths and that are manipulated in different ways often prove to be unequal. In the following example, one <xref:System.Single> value is squared, and then the square root is calculated to restore the original value. A second <xref:System.Single> is multiplied by 3.51 and squared before the square root of the result is divided by 3.51 to restore the original value. Although the two values appear to be identical, a call to the <xref:System.Single.Equals%28System.Single%29> method indicates that they are not equal. Using the "G9" standard format string to return a result string that displays all the significant digits of each <xref:System.Single> value shows that the second value is .0000000000001 less than the first.
[!code-csharp[System.Single.Structure#10](~/samples/snippets/csharp/VS_Snippets_CLR_System/system.single.structure/cs/comparison2.cs#10)]
[!code-vb[System.Single.Structure#10](~/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.single.structure/vb/comparison2.vb#10)]
Expand Down

0 comments on commit a11b9ca

Please sign in to comment.