110

I know you can't rely on equality between double or decimal type values normally, but I'm wondering if 0 is a special case.

While I can understand imprecisions between 0.00000000000001 and 0.00000000000002, 0 itself seems pretty hard to mess up since it's just nothing. If you're imprecise on nothing, it's not nothing anymore.

But I don't know much about this topic so it's not for me to say.

double x = 0.0;
return (x == 0.0) ? true : false;

Will that always return true?

Daniel Daranas
  • 22,454
  • 9
  • 63
  • 116
Gene Roberts
  • 2,192
  • 3
  • 17
  • 16

10 Answers10

124

It is safe to expect that the comparison will return true if and only if the double variable has a value of exactly 0.0 (which in your original code snippet is, of course, the case). This is consistent with the semantics of the == operator. a == b means "a is equal to b".

It is not safe (because it is not correct) to expect that the result of some calculation will be zero in double (or more generally, floating point) arithmetics whenever the result of the same calculation in pure Mathematics is zero. This is because when calculations come into the ground, floating point precision error appears - a concept which does not exist in Real number arithmetics in Mathematics.

Daniel Daranas
  • 22,454
  • 9
  • 63
  • 116
  • 1
    The double variable may have a value of exactly 0.0 or -0.0 and it will still evaluate to true. It may be safe to expect the result of a floating point calculation in some circumstances to be exactly 0.0, I.E. if some value is multiplied by exactly +-0.0, or divided by negative or positive infinity. This can be useful in some circumstances to check for edge cases/invalid input. – Kylelem62 May 03 '22 at 12:50
53

If you need to do a lot of "equality" comparisons it might be a good idea to write a little helper function or extension method in .NET 3.5 for comparing:

public static bool AlmostEquals(this double double1, double double2, double precision)
{
    return (Math.Abs(double1 - double2) <= precision);
}

This could be used the following way:

double d1 = 10.0 * .1;
bool equals = d1.AlmostEquals(0.0, 0.0000001);
Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
  • 4
    You might be having a subtractive cancellation error by comparing double1 and double2, in case these numbers have values very close to each other. I would remove the Math.Abs and check each branch individually d1 >= d2 - e and d1 <= d2 + e – Theodore Zographos Jun 14 '12 at 23:15
  • 1
    "Because 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 Epsilon. Typically, it is many times greater than Epsilon. Because of this, we recommend that you do not use Epsilon when comparing Double values for equality." - https://msdn.microsoft.com/en-gb/library/ya2zha7s(v=vs.110).aspx – Rafael Costa Mar 14 '18 at 15:12
15

For your simple sample, that test is okay. But what about this:

bool b = ( 10.0 * .1 - 1.0 == 0.0 );

Remember that .1 is a repeating decimal in binary and can't be represented exactly, the same as trying to write 1/3 as a base 10 decimal. Now compare that to this code:

double d1 = 10.0 * .1; // make sure the compiler hasn't optimized the .1 issue away
bool b = ( d1 - 1.0 == 0.0 );

I'll leave you to run a test to see the actual results: you're more likely to remember it that way.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
14

From the MSDN entry for Double.Equals:

Precision in Comparisons

The Equals method should be used with caution, because two apparently equivalent values can be unequal due to the differing precision of the two values. The following example reports that the Double value .3333 and the Double returned by dividing 1 by 3 are unequal.

...

Rather than comparing for equality, one recommended technique involves defining an acceptable margin of difference between two values (such as .01% of one of the values). If the absolute value of the difference between the two values is less than or equal to that margin, the difference is likely to be due to differences in precision and, therefore, the values are likely to be equal. The following example uses this technique to compare .33333 and 1/3, the two Double values that the previous code example found to be unequal.

Also, see Double.Epsilon.

Stu Mackellar
  • 11,510
  • 1
  • 38
  • 59
  • 1
    It's also possible for not-quite-equivalent values to compare as equal. One would expect that if `x.Equals(y)`, then `(1/x).Equals(1/y)`, but that's not the case if `x` is `0` and `y` is `1/Double.NegativeInfinity`. Those values declare as equal, even though their reciprocals do not. – supercat Sep 26 '12 at 20:44
  • @supercat: They are equivalent. And they don't have reciprocals. You could run your test again with `x = 0` and `y = 0`, and you'd still find that `1/x != 1/y`. – Ben Voigt Aug 05 '16 at 14:48
  • @BenVoigt: With `x` and `y` as type `double`? How do you compare the results to make them report unequal? Note that 1/0.0 is not NaN. – supercat Aug 05 '16 at 15:15
  • @supercat: Ok, it's one of the things that IEEE-754 gets wrong. (First, that `1.0/0.0` fails to be NaN as it should be, as the limit is not unique. Secondly, that infinities compare equal to each other, without paying any attention to degrees of infinity) – Ben Voigt Aug 05 '16 at 15:35
  • @BenVoigt: If the zero was a result of multiplying two very small numbers, then dividing 1.0 into that should yield a value which compares greater than any number of the small numbers had the same sign, and less than any number if one of the small numbers had opposite signs. IMHO, IEEE-754 would be better if it had an unsigned zero, but positive and negative infinitesimals. – supercat Aug 05 '16 at 16:21
  • @supercat: Yes, that would be one way to fix the ambiguity around reciprocal-after-underflow. It still doesn't help the problem that +Inf is a solution to `x*x == x` when it really should not be (infinities should be ordered with respect to all finite numbers and their counterpart with opposite sign, but unordered with respect to another infinity of the same sign). – Ben Voigt Aug 05 '16 at 16:42
  • @BenVoigt: Doing floating-point properly requires multiple kinds of comparisons. For some kinds of mathematical computations, inconsistent orderings can make sense, but for "data-processing" operations like sorting (which may form part of some mathematical processes like taking a median) fullness and consistency are more important than "mathematical correctness". For most purposes, the most important aspect of an "==" operator is that it behave as an equivalence relation; by that standard, an "==" operator that makes anything unequal to itself is broken. – supercat Aug 05 '16 at 17:00
  • @supercat: You can already do `isfinite(x)? x == y : fpclassify(x) == fpclassify(y)` for an equivalence relationship (it would be nice to have a standard function for this, sure). But given that `x == x` is false for NaN, it seems clear that it should also be false for the other non-finite classes. – Ben Voigt Aug 05 '16 at 17:02
  • @BenVoigt: The authors of IEEE-754 wanted to allow comparisons using only the built-in relational operators so it would be usable even on implementations without functions like isfinite(x), fpclassify(x), etc. If only one value compares unequal to itself "x==y || (x!=x && y!=y)" will be an equivalence relation. If neither +INF nor -INF compared equal to itself, it would be necessary to add even more terms to that monstrosity. – supercat Aug 05 '16 at 18:14
7

The problem comes when you are comparing different types of floating point value implementation e.g. comparing float with double. But with same type, it shouldn't be a problem.

float f = 0.1F;
bool b1 = (f == 0.1); //returns false
bool b2 = (f == 0.1F); //returns true

The problem is, programmer sometimes forgets that implicit type cast (double to float) is happening for the comparison and the it results into a bug.

Yogee
  • 1,412
  • 14
  • 22
3

If the number was directly assigned to the float or double then it is safe to test against zero or any whole number that can be represented in 53 bits for a double or 24 bits for a float.

Or to put it another way you can always assign and integer value to a double and then compare the double back to the same integer and be guaranteed it will be equal.

You can also start out by assigning a whole number and have simple comparisons continue to work by sticking to adding, subtracting or multiplying by whole numbers (assuming the result is less than 24 bits for a float abd 53 bits for a double). So you can treat floats and doubles as integers under certain controlled conditions.

Kevin Gale
  • 4,350
  • 6
  • 30
  • 31
  • I agree with your statement in general (and upvoted it) but I believe it really depends if IEEE 754 floating point implementation is used or not. And I believe every "modern" computer uses IEEE 754, at least for storage of floats (there are weird rounding rules that differ). – Mark Lakata Mar 20 '17 at 18:18
2

No, it is not OK. So-called denormalized values (subnormal), when compared equal to 0.0, would compare as false (non-zero), but when used in an equation would be normalized (become 0.0). Thus, using this as a mechanism to avoid a divide-by-zero is not safe. Instead, add 1.0 and compare to 1.0. This will ensure that all subnormals are treated as zero.

0

for those curious, the exact IEEE 754 representation for 0.3333… vs. 1/3 are :

mawk '$++NF = ($++_)/((__ = +$(_+_--)) ? __ : !__)' CONVFMT='%.16lX' | column -t

0.3                      _  3FD3333333333333
0.33                     _  3FD51EB851EB851F
0.333                    _  3FD54FDF3B645A1D
0.3333                   _  3FD554C985F06F69
0.33333                  _  3FD555475A31A4BE
0.333333                 _  3FD55553EF6B5D46
0.3333333                _  3FD55555318ABC87

0.33333333               _  3FD5555551C112DA
0.333333333              _  3FD5555554F9B516
0.3333333333             _  3FD55555554C2BB5
0.33333333333            _  3FD5555555546AC5
0.333333333333           _  3FD5555555553DE1
0.3333333333333          _  3FD55555555552FD
0.33333333333333         _  3FD5555555555519

0.333333333333333        _  3FD555555555554F
0.3333333333333333       _  3FD5555555555555 <— 16 3's
0.33333333333333333      _  3FD5555555555555
0.333333333333333333     _  3FD5555555555555
0.3333333333333333333    _  3FD5555555555555
0.33333333333333333333   _  3FD5555555555555
0.333333333333333333333  _  3FD5555555555555

1                        3  3FD5555555555555
RARE Kpop Manifesto
  • 2,453
  • 3
  • 11
  • Does not work for me on Linux, `mawk` waits for some input from stdin. – ks1322 Aug 09 '23 at 20:29
  • @ks1322 : cuz you need to send either column 1 or columns 1+2 through the pipe as input. if you justt send in a single decimal number it'll give you the hex of that. if it's 2 numbers (integer or decimal) it'll calculate the fraction/ratio between them, with auto divide-by-zero protection (they would become / 1 instead, cuz I figured outputting `INF` or `NAN` isn't all that useful compared to just outputting column 1's value ) – RARE Kpop Manifesto Aug 09 '23 at 22:35
-2

Try this, and you will find that == is not reliable for double/float.
double d = 0.1 + 0.2; bool b = d == 0.3;

Here is the answer from Quora.

rickyuu
  • 7
  • 2
-5

Actually, I think it is better to use the following codes to compare a double value against to 0.0:

double x = 0.0;
return (Math.Abs(x) < double.Epsilon) ? true : false;

Same for float:

float x = 0.0f;
return (Math.Abs(x) < float.Epsilon) ? true : false;
David.Chu.ca
  • 37,408
  • 63
  • 148
  • 190
  • 5
    No. From the docs from double.Epsilon: "If you create a custom algorithm that determines whether two floating-point numbers can be considered equal, you must use a value that is greater than the Epsilon constant to establish the acceptable absolute margin of difference for the two values to be considered equal. (Typically, that margin of difference is many times greater than Epsilon.)" – Alastair Maw Jun 06 '12 at 08:49
  • 1
    @AlastairMaw this applies to checking two doubles of any size for equality. For checking equality to zero, double.Epsilon is fine. – jwg Jan 03 '13 at 13:30
  • 4
    No, it's *not*. It's very likely that the value you have arrived at via some calculation is many times epsilon away from zero, but should still be considered as zero. You don't magically achieve a whole bunch of extra precision in your intermediate result from somewhere, just because it happens to be close to zero. – Alastair Maw Jan 03 '13 at 19:01
  • 4
    For example: (1.0/5.0 + 1.0/5.0 - 1.0/10.0 - 1.0/10.0 - 1.0/10.0 - 1.0/10.0) < double.Epsilon == false (and considerably so in magnitude terms: 2.78E-17 vs 4.94E-324) – Alastair Maw Jan 03 '13 at 19:07
  • so, what is the precision recommended, if double.Epsilon is not ok? Would 10 times of epsilon ok? 100 times? – liang May 14 '13 at 16:56