3

I know there are a lot of questions on here about why float equality comparison is usually a bad idea. I understand float representation issues, rounding issues, silent promotion of floats to doubles, the dangers in relying upon arithmetic at the bit level, etc. But it seems to me that this should be fine, and no questions I found seem to cover this:

static const float MARKER = -500.0f; // some value well outside the range of valid values
std::vector<float> some_floats = {MARKER, 0.5f, 100.0f, 9.5f, MARKER, 0.f};
for (size_t i = 0; i< some_floats.size(); ++i) {
    if (some_floats[i] == MARKER) {             
       std::cout << i << std::endl;
    } else {
       // do some math
    }
}

The output is as expected:

0
4

If I have -Wfloat-equal enabled (in gcc, but similar in other compilers), it will flag the comparison line as dangerous: comparing floating point with == or != is unsafe. And pretty much all the answers on here say not to use == or !=, period. But I don't see why it's a problem here. I'm only setting the constant once and re-using it everywhere else it's used, and there is never any manipulation of that constant (e.g. arithmetic). Am I missing something? What about 0.0f, even if not set as a constant?

jtbr
  • 1,149
  • 13
  • 13
  • related: https://stackoverflow.com/questions/39108471/is-it-ok-to-compare-floating-points-to-0-0-without-epsilon – 463035818_is_not_an_ai Jan 28 '21 at 15:05
  • what I dont understand about your question is: You say "I don't see why it's a problem here." but do you actually see a problem? Is the code not doing what you want it to do? – 463035818_is_not_an_ai Jan 28 '21 at 15:06
  • I mean, apart from the warning, is there anything wrong with the code? – 463035818_is_not_an_ai Jan 28 '21 at 15:07
  • 1
    When you turn on `-Wfloat-equal`, the compiler warning is checking if you're comparing floats with `==`. It has no way of knowing if this happens to be one of the situations where _actually_ the comparison does exactly what you want it to. It's easy enough to see it's the case in this code, but to make such a feature remotely useful it would have to be much, much more general. – Nathan Pierson Jan 28 '21 at 15:08
  • Well, you look at floating points. The main reason to use them is doing floating point math. Once you actually do math on the floats and compare them it is not going well for you. If you simple set a float and compare them it is going to work (at least I don't see why not). However, if you do not do math with the floats, what is the point of using float and not just another more suited type. –  Jan 28 '21 at 15:14
  • 1
    There is nothing wrong with comparing equality between `float` values that were set by specific literals, expect that it can form a bad habit. `-500.f == -500.f` is `true`. The risk is when you compare `float` values that were calculated such that you would expect them to be mathematically equal, but in practice they won't compare equal. – François Andrieux Jan 28 '21 at 15:15
  • @user32434999 This is a toy example, but I do indeed want to use the floats, but might need to mark some entries as different for some reason and need to be treated differently. For example they're out of bounds and should be set explicitly rather than manipulated arithmetically. – jtbr Jan 28 '21 at 16:55
  • @jtbr You can certainly mark them like this, I would suggest using std::numeric_limits::min() - rather than 500.f - to avoid when rescalign the problem in some manner to have marker be invalid quickly. You can also use std::optional, if no performance argument prevents. –  Jan 29 '21 at 09:50
  • @jtbr I also edited the answer to reflect that last comment. –  Jan 29 '21 at 09:56
  • std::numeric_limits::min() is just a constant like 500.0f. My question is about whether I can use == or != to compare such a value, despite it being floating point. It appears the answer is yes, assuming the same compiler is used for the whole program. – jtbr Jan 29 '21 at 12:05

2 Answers2

5

As long as you're confident MARKER and its copies won't be altered by arithmetic functions or something, there's no issue doing a simple comparison.
Maybe consider not using -Wfloat-equal globally but disable warnings locally:

    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wfloat-equal"
    /* your code */
    #pragma GCC diagnostic pop

Or a portable equivalent: https://www.fluentcpp.com/2019/08/30/how-to-disable-a-warning-in-cpp/

m88
  • 1,968
  • 6
  • 14
  • 1
    If floats are not going to be altered, why use them? Turn warnings off is generally not a good idea. As soon someone is adding something there the compiler won't issue any warnings. Usually when you do that there is a smarter way to do it. In our case here, it is not using floats in the first place. By only suggesting this without telling the downside you will enforce coding like that :( If you added these pragmas to my code I would be upset. –  Jan 28 '21 at 15:34
  • 1
    Maybe one part of his code uses `some_floats` non-arithmetically, just checking which constants are present in the vector, and transfers (move/copy) `some_floats` to another part of his code where arithmetic operations happen, but "naïve" comparisons don't. – m88 Jan 28 '21 at 16:02
  • I am saying, usually there is a better way and this warning makes you think twice. I cannot think of a good example where there is no other way which would avoid the warning. It is also bad since some other person who looks at the code will see a float and think, hey, I am supposed to do math with that, and wreaks havoc upon your code. Granted there may be some cases, but the general rule should be: it is a big no no. At least imho. –  Jan 28 '21 at 16:19
  • 1
    @user32434999 In this case, the alternative would probably be to have a whole other vector (of `bool`, say) around that says how to treat the entries in the float vector (or vector of structs or something like that). Not very efficient, or intuitive, either. – jtbr Jan 28 '21 at 17:00
  • @jtbr I don't disagree that you can do it like that for performance sake in some situations. I would argue to maybe set the marker value to something like std::numeric_limits::min() just to be safe that nobody re-scales the problem and MARKER is suddenly well within range. However, if you want to intuitive and signal that an entry is either a valid number or nothing, you could also use std::optional. If it is invalid make it std::nullopt. It should however not be the canonical option in my opinion. Anyway, if that's the way you guys want it, fine by me ;) –  Jan 28 '21 at 19:57
2

You miss the reason when to use floats. You use floats to do math. As long you only compare float literals it is going to be alright. However as soon you do some manipulation on the numbers and compare them afterwards it is generally not going to be working (even though there are cases where it may be working) as floating point operations are not exact. That is what people mean when saying "Do not compare floats".

Further many compilers have flags for their specify floating-point behavior. For MSVC for instance have a look here . This makes it even more obvious that an exact comparison is never going to be a good idea.

If you want to mark floats like this you can certainly do it, however, I would suggest using std::numeric_limits<float>::min() as the marker value to be safe. Just be sure to not manipulate these values mathematically. Also consider that other developers may take the presence of a float as an indicator that it is valid to manipulate these values, which would not be good. As an alternative to that you may consider std::optional to mark whether the numbers are valid.

Just as a comment on the static: I would further suggest to drop the static as there really is no need for that and the word performance popped up somewhere. I would change that to look like this:

const float MARKER = std::numeric_limits<float>::min();  // some value well outside the range of valid values
std::vector<float> some_floats = {MARKER, 0.5f, 100.0f, 9.5f, MARKER, 0.f};
for (size_t i = 0; i < some_floats.size(); ++i) {
    if (some_floats[i] == MARKER) {
        std::cout << i << std::endl;
    } else {
    // do some math
}

}

  • 1
    The canonical counter-example is `if (b!=0.0) c=a/b`, where the test is correct. The only other value besides 0.0 for which that fails is -0.0, and -0.0 is still equal to 0.0. – MSalters Jan 28 '21 at 15:43
  • How might `static` affect the performance? – TonyK Jan 29 '21 at 10:18
  • From [here](https://stackoverflow.com/questions/21032885/performance-gain-from-static-const-and-global-variables): "Making the variable static may incur a small cost on every call of the function determining if the object was already initialized, especially with C++11 where the initialization is thread-safe. Even if it doesn't need a check, the stack is likely to be in cached memory while a static variable is not." –  Jan 29 '21 at 10:21
  • 1
    I always find amusing that people focus on == and find no problem with < and <=... A very bad workaround that I do not recomnand apart for the joke is to write `if (x <= y && y <= x)` – aka.nice Jan 29 '21 at 13:35
  • @aka.nice :D Maybe its still unclear in my answer. There is nothing wrong in comparing floats for equality, as long it not subject to math, which it usually is. If there is some kind of preprocessing which sets the values to MARKER it is all good. However this preprocessing usually has to to some kind of comparison not using exact equality. –  Jan 29 '21 at 13:43