Skip to content

Commit b7aa4ca

Browse files
authored
Explain allowable diff in Double.Equals() (#41209)
1 parent d0e4a86 commit b7aa4ca

File tree

2 files changed

+55
-39
lines changed

2 files changed

+55
-39
lines changed

docs/fundamentals/runtime-libraries/snippets/System/Double/Equals/csharp/equalsabs1.cs

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,50 @@
1-
// <Snippet1>
2-
using System;
1+
using System;
32

43
public class Example
54
{
6-
public static void Main()
7-
{
8-
double value1 = .1 * 10;
9-
double value2 = 0;
10-
for (int ctr = 0; ctr < 10; ctr++)
11-
value2 += .1;
12-
13-
Console.WriteLine("{0:R} = {1:R}: {2}", value1, value2,
14-
HasMinimalDifference(value1, value2, 1));
15-
}
16-
17-
public static bool HasMinimalDifference(double value1, double value2, int units)
18-
{
19-
long lValue1 = BitConverter.DoubleToInt64Bits(value1);
20-
long lValue2 = BitConverter.DoubleToInt64Bits(value2);
21-
22-
// If the signs are different, return false except for +0 and -0.
23-
if ((lValue1 >> 63) != (lValue2 >> 63))
24-
{
25-
if (value1 == value2)
5+
// <Snippet1>
6+
public static void Main()
7+
{
8+
// Initialize the values.
9+
double value1 = .1 * 10;
10+
double value2 = 0;
11+
for (int ctr = 0; ctr < 10; ctr++)
12+
value2 += .1;
13+
14+
Console.WriteLine($"{value1:R} = {value2:R}: " +
15+
$"{HasMinimalDifference(value1, value2, 1)}");
16+
}
17+
18+
public static bool HasMinimalDifference(
19+
double value1,
20+
double value2,
21+
int allowableDifference
22+
)
23+
{
24+
// Convert the double values to long values.
25+
long lValue1 = BitConverter.DoubleToInt64Bits(value1);
26+
long lValue2 = BitConverter.DoubleToInt64Bits(value2);
27+
28+
// If the signs are different, return false except for +0 and -0.
29+
if ((lValue1 >> 63) != (lValue2 >> 63))
30+
{
31+
if (value1 == value2)
32+
return true;
33+
34+
return false;
35+
}
36+
37+
// Calculate the number of possible
38+
// floating-point values in the difference.
39+
long diff = Math.Abs(lValue1 - lValue2);
40+
41+
if (diff <= allowableDifference)
2642
return true;
2743

28-
return false;
29-
}
30-
31-
long diff = Math.Abs(lValue1 - lValue2);
32-
33-
if (diff <= (long) units)
34-
return true;
35-
36-
return false;
37-
}
44+
return false;
45+
}
46+
// The example displays the following output:
47+
//
48+
// 1 = 0.99999999999999989: True
49+
// </Snippet1>
3850
}
39-
// The example displays the following output:
40-
// 1 = 0.99999999999999989: True
41-
// </Snippet1>

docs/fundamentals/runtime-libraries/system-double-equals.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,21 @@ Rather than comparing for equality, one technique involves defining an acceptabl
3434
:::code language="vb" source="./snippets/System/Double/Epsilon/vb/Equals_25051.vb" id="Snippet2":::
3535

3636
> [!NOTE]
37-
> Because <xref:System.Double.Epsilon> defines the minimum expression of a positive value whose range is near zero, the margin of difference between two similar values must be greater than <xref:System.Double.Epsilon>. Typically, it is many times greater than <xref:System.Double.Epsilon>. Because of this, we recommend that you do not use <xref:System.Double.Epsilon> when comparing <xref:System.Double> values for equality.
37+
> Because <xref:System.Double.Epsilon> defines the minimum expression of a positive value whose range is near zero, the margin of difference between two similar values must be greater than <xref:System.Double.Epsilon>. Typically, it is many times greater than <xref:System.Double.Epsilon>. Because of this, we recommend that you **don't** use <xref:System.Double.Epsilon> when comparing <xref:System.Double> values for equality.
3838
39-
A second technique involves comparing the difference between two floating-point numbers with some absolute value. If the difference is less than or equal to that absolute value, the numbers are equal. If it is greater, the numbers are not equal. One alternative is to arbitrarily select an absolute value. This is problematic, however, because an acceptable margin of difference depends on the magnitude of the <xref:System.Double> values. A second alternative takes advantage of a design feature of the floating-point format: The difference between the integer representation of two floating-point values indicates the number of possible floating-point values that separates them. For example, the difference between 0.0 and <xref:System.Double.Epsilon> is 1, because <xref:System.Double.Epsilon> is the smallest representable value when working with a <xref:System.Double> whose value is zero. The following example uses this technique to compare .33333 and 1/3, which are the two <xref:System.Double> values that the previous code example with the <xref:System.Double.Equals(System.Double)> method found to be unequal. Note that the example uses the <xref:System.BitConverter.DoubleToInt64Bits%2A?displayProperty=nameWithType> method to convert a double-precision floating-point value to its integer representation.
39+
A second technique involves comparing the difference between two floating-point numbers with some absolute value. If the difference is less than or equal to that absolute value, the numbers are equal. If it's greater, the numbers are not equal. One alternative is to arbitrarily select an absolute value. That's problematic, however, because an acceptable margin of difference depends on the magnitude of the <xref:System.Double> values. A second alternative takes advantage of a design feature of the floating-point format: The difference between the integer representation of two floating-point values indicates the number of possible floating-point values that separates them. For example, the difference between 0.0 and <xref:System.Double.Epsilon> is 1, because <xref:System.Double.Epsilon> is the smallest representable value when working with a <xref:System.Double> whose value is zero. The following example uses this technique to compare .33333 and 1/3, which are the two <xref:System.Double> values that the previous code example with the <xref:System.Double.Equals(System.Double)> method found to be unequal. The example uses the <xref:System.BitConverter.DoubleToInt64Bits%2A?displayProperty=nameWithType> method to convert a double-precision floating-point value to its integer representation. The example declares the values as equal if there are no possible floating-point values between their integer representations.
4040

4141
:::code language="csharp" source="./snippets/System/Double/Equals/csharp/equalsabs1.cs" interactive="try-dotnet" id="Snippet1":::
4242
:::code language="fsharp" source="./snippets/System/Double/Equals/fsharp/equalsabs1.fs" id="Snippet1":::
4343
:::code language="vb" source="./snippets/System/Double/Equals/vb/equalsabs1.vb" id="Snippet1":::
4444

45-
The precision of floating-point numbers beyond the documented precision is specific to the implementation and version of the .NET Framework. Consequently, a comparison of two particular numbers might change between versions of the .NET Framework because the precision of the numbers' internal representation might change.
45+
> [!NOTE]
46+
> For some values, you might consider them equal even when there *is* a possible floating-point value between the integer representations. For example, consider the double values `0.39` and `1.69 - 1.3` (which is calculated as `0.3899999999999999`). On a little-endian computer, the integer representations of these values are `4600697235336603894` and `4600697235336603892`, respectively. The difference between the integer values is `2`, meaning there *is* a possible floating-point value between `0.39` and `1.69 - 1.3`.
47+
48+
### Version differences
49+
50+
The precision of floating-point numbers beyond the documented precision is specific to the implementation and version of .NET. Consequently, a comparison of two particular numbers might change between versions of .NET because the precision of the numbers' internal representation might change.
51+
52+
## NaN
4653

47-
If two <xref:System.Double.NaN?displayProperty=nameWithType> values are tested for equality by calling the <xref:System.Double.Equals%2A> method, the method returns `true`. However, if two <xref:System.Double.NaN> values are tested for equality by using the equality operator, the operator returns `false`. When you want to determine whether the value of a <xref:System.Double> is not a number (NaN), an alternative is to call the <xref:System.Double.IsNaN%2A> method.
54+
If two <xref:System.Double.NaN?displayProperty=nameWithType> values are tested for equality by calling the <xref:System.Double.Equals%2A> method, the method returns `true`. However, if two <xref:System.Double.NaN?displayProperty=nameWithType> values are tested for equality by using the *equality operator*, the operator returns `false`. When you want to determine whether the value of a <xref:System.Double> is not a number (NaN), an alternative is to call the <xref:System.Double.IsNaN%2A> method.

0 commit comments

Comments
 (0)