6

May question is: What is a standard way to compare float with zero?

As far as I know direct comparison:

if ( x == 0 ) { 
  // x is zero?
} else {
  // x is not zero??

can fail with floating points variables.

I used to use

float x = ...
...
if ( std::abs(x) <= 1e-7f ) { 
  // x is zero, do the job1
} else {
 // x is not zero, do the job2
...

Same approach I find here. But I see two problems:

  1. Random magic number 1e-7f ( or 0.00005 at the link above ).
  2. The code harder to read

This is such a common comparison, I wonder whether there is a standard short way to do this. Like

 x.is_zero();
Community
  • 1
  • 1
klm123
  • 12,105
  • 14
  • 57
  • 95
  • 1
    What is your use case? Why are you comparing a float to zero? – David Schwartz Nov 03 '13 at 16:08
  • 6
    No, there's no standard, because the appropriate value of epsilon depends on the application. – Barmar Nov 03 '13 at 16:09
  • It depends what you're trying to do. Where does the number come from? Why does it matter if it's zero? Is there a limit below which job1 won't work? – Alan Stokes Nov 03 '13 at 16:10
  • @David Schwartz, I have a lot of use cases and tired of this ugly code with always different magic numbers. For example I need to know is there a need to rotate and object or rotation angle == 0, or whether two objects have same X position. – klm123 Nov 03 '13 at 16:11
  • 4
    @klm123 You see why those two different use cases would call for a different algorithm? In fact, both of those should probably be in relation to the size of the object. A .01 degree rotation of a small object is negligible. Of a large object, not so much. You have to think through each case. – David Schwartz Nov 03 '13 at 16:14

3 Answers3

7

To compare a floating-point value with 0, just compare it:

if (f == 0)
    // whatever

There is nothing wrong with this comparison. If it doesn't do what you expect it's because the value of f is not what you thought it was. Its essentially the same problem as this:

int i = 1/3;
i *= 3;
if (i == 1)
    // whatever

There's nothing wrong with that comparison, but the value of i is not 1. Almost all programmers understand the loss of precision with integer values; many don't understand it with floating-point values.

Using "nearly equal" instead of == is an advanced technique; it often leads to unexpected problems. For example, it is not transitive; that is, a nearly equals b and b nearly equals c does not mean that a nearly equals c.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • 1
    This is the wrong answer. floating point impression is not as clear and obvious as integer truncation. Programmers should treat floating points as unpredictable. – Phlip Nov 03 '13 at 17:09
  • 4
    @Phlip: No, programmers should understand the sources of errors when using floating point numbers, and design their algorithms with appropriate tolerances. – Mike Seymour Nov 03 '13 at 18:03
  • 3
    @Phlip: No, floating-point numbers have a well-defined semantics. Treating floating-point as "unpredictable" because you're too lazy to understand it is not the way to go. – tmyklebu Nov 03 '13 at 18:19
  • 4
    @Phlip - you're right that it's not as clear and obvious as integer truncation (although for many beginners, integer truncation is **not** obvious; witness the number of questions on StackExchange for which the answer is "you're doing integer math; change it to floating point"). Nevertheless, it's **essentially** the same problem, and the solution is to understand why your numbers aren't what you expect them to be and to revise your expectations accordingly. Unfortunately, programming courses generally don't teach floating point very well, and the result is that programmers are afraid of it. – Pete Becker Nov 03 '13 at 18:32
  • The problem with advising a newb to use `float == 0.0` is it will appear to work in test, then it will get put into production. – Phlip Nov 03 '13 at 19:21
  • 3
    @Phlip - what do you suggest as an alternative? If someone doesn't understand floating-point math, adding more stuff that they don't understand (like "nearly equal") makes it worse. If you think that floating-point math is unpredictable it means you don't know enough about it to use it. In that case, don't use it until you've learned more about it. – Pete Becker Nov 03 '13 at 20:04
  • There is one sense in which floating-point is less predictable than integer, which is that AFAIK C++ doesn't specify the accuracy of all mathematical operations for floating point, so *portable* code must allow for implementation variety. Barring overflow, integer arithmetic is accurate. C++ doesn't specify the precision either, but you can look that up from `numeric_limits`. That said, IEEE 754 does specify accuracy, so usually you're on firmer ground, and C++ lets you check whether you have IEEE or not. – Steve Jessop Nov 03 '13 at 22:19
  • 3
    @SteveJessop: “Barring overflow, integer arithmetic is accurate” is not a true statement. Integer arithmetic gets the wrong answer when a 5% annual interest rate is converted to a monthly rate. Integer arithmetic is not associative, even when there is no overflow. Even when dollars are scaled to pennies, as a number of Stack Overflow answers recommend, integer arithmetic gets incorrectly rounded answers when a product price is multiplied by a sales tax rate. – Eric Postpischil Nov 04 '13 at 05:05
  • 1
    @SteveJessop - regardless, "nearly equal" is not the answer, nor is "it's unpredictable". – Pete Becker Nov 04 '13 at 13:24
  • @EricPostpischil: integer arithmetic is accurate in the sense that the standard defines the result, and the implementation must produce that result. This isn't the case with floating-point. It's not capable of representing your problem involving twelfth roots, but that's a separate issue from the accuracy of the operations it does perform. That said, the thing it calls `/` is a fairly useless operation in a lot of cases. And of course not all arithmetic is associative (in mathematics, let alone in C++), since subtraction is a non-associative operator. Again, that's not an issue of accuracy. – Steve Jessop Nov 04 '13 at 15:19
  • @PeteBecker: sure, "it's unpredictable" is not in itself the answer. We can do rather better than just throw our hands in the air and act as if floating-point arithmetic is an unsolvable mystery. But the fact remains that the result of `1 / 3` is guaranteed whereas I believe the result of `1.0 / 3` isn't forbidden from varying by an ulp or even more between implementations. If not that example, then you can come up with cases where stuff like rounding mode affects the answer and isn't guaranteed. So the approach to dealing with floating-point is different from that for integers. – Steve Jessop Nov 04 '13 at 15:30
  • @SteveJessop - I didn't say that the solution is the same. I said that the **problem** is **essentially** the same. – Pete Becker Nov 04 '13 at 15:36
  • @SteveJessop: It is the case with floating-point arithmetic. Floating-point arithmetic is accurate in the sense that the standard defines the result, and the implementation must produce that result. Floating-point is not capable of representing the problem involving twelfth roots, but that is a separate issue from the accuracy of the operations it does perform. The **same** attributes and failures you claim for integer arithmetic hold for floating-point arithmetic. Only the specific values involved differ. – Eric Postpischil Nov 04 '13 at 15:39
  • @PeteBecker: Hmm, I may be conflating your position with that of tmyklebu. Anyway, all I mean to say is that unpredictability is a part of the issue – Steve Jessop Nov 04 '13 at 15:41
  • @EricPostpischil: "Floating-point arithmetic is accurate in the sense that the standard defines the result, and the implementation must produce that result". I believe this statement to be false, and have said so earlier. Given whose answer I'm arguing under, I was quite strongly expecting to be slammed down if that belief is incorrect and the C++ standard does in point of fact demand accurate floating-point operations :-) – Steve Jessop Nov 04 '13 at 15:42
  • @SteveJessop: “The standard” for floating-point arithmetic is the IEEE 754 standard. And the reason `/` is a “fairly useless operation” is because it is inaccurate. In other words, you are saying integer arithmetic is accurate, but only because you dismiss operations in which it is inaccurate as useless. That does not make any sense. I am pointing out you have a personal bias in favor of integer arithmetic, but there is no objective mathematical reason for it. Integer arithmetic is “accurate” only for a few limited operations with limited values. Floating-point is also accurate within limits. – Eric Postpischil Nov 04 '13 at 15:48
  • Note that this question has both C++ and floating-point tags. There is no basis for answering using the C++ standard and not the floating-point standard; one is not more privileged than the other. – Eric Postpischil Nov 04 '13 at 15:51
  • But anyway, I raised the issue of IEEE 754 when I said that *portable* C++ code must allow for implementation variety. If you think that the IEEE organization has sort of magicked that variety away then lucky you for never having dealt with a non-IEEE-754 floating point implementation. One would hope that they're becoming rarer over time, of course. – Steve Jessop Nov 04 '13 at 16:32
  • IMO the critical difference With integer and floating point math is, with integer math you can "know" both the initial value and the exact result value of for example division, when you write the code. Not so with floats, often you don't even "know" if simple literal can be accurately represented, unless you are a rare binary float representation guru. From human programmer point of view this is a big difference. – hyde Nov 05 '13 at 05:19
5

There is no standard way, because whether or not you want to treat a small number as if it were zero depends on how you computed the number and what it's for. This in turn depends on the expected size of any errors introduced by your computations, and perhaps on errors of physical measurement that determined your original inputs.

For example, suppose that your value represents the length of a journey in miles in some mapping software. Then you are happy to treat 1e-7 as equal to zero because in that context it is a very small number: it has come about because of a rounding error or other reason for slight inexactness.

On the other hand, suppose that your value represents the size of a molecule in metres in some electron microscopy software. Then you certainly don't want to treat 1e-7 as equal to zero because in that context it's a very large number.

You should first consider what would be a suitable accuracy to present your value: what's the error bar, or how many significant figures can you reasonably display. This will give you some idea with what tolerance it would be appropriate to test against zero, although it still might not settle the case. For the mapping software, you can probably treat a journey as zero if it's less than some fixed value, although the value itself might depend on the resolution of your maps. For the microscopy software, if the difference between two sizes is such that zero lies with the 95% error range on those measurements, that still might not be sufficient to describe them as being the same size.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
4

I don't know whether my answer useful, I've found this in irrlicht's irrmath.h and still using it in engine's mathlib till nowdays:

const float ROUNDING_ERROR_f32 = 0.000001f;

//! returns if a equals b, taking possible rounding errors into account
inline bool equals(const float a, const float b, const float tolerance = ROUNDING_ERROR_f32)
{
        return (a + tolerance >= b) && (a - tolerance <= b);
}

The author has explained this approach by "after many rotations, which are trigonometric operations the coordinate spoils and the direct comparsion may cause fault".

Netherwire
  • 2,669
  • 3
  • 31
  • 54
  • This is exactly what I use, see the post. – klm123 Nov 03 '13 at 17:51
  • @klm123 I think the point here is the `tolerance` parameter, which you should usually supply. Having a numeric const default value for it is slightly counter-productive I think, and I would either not have default, or use a sentinel value, and then have a modifiable default (used when sentinel value is given). – hyde Nov 28 '13 at 06:29