If you're writing something like
if(val == 0.512)
the question is not, "does 0.512 have an exact representation?". The more important question is, "How was val computed, and how much error might have crept in?"
If no error has crept in, you can use == just fine, even on values like this one:
double val = 0.512; if(val == 0.512) { expected case; } else { surprising case; }
Under reasonable circumstances, this will take the expected case. Yes, it's true, 0.512 can't be represented exactly in binary, so val is likely to end up containing something more like 0.51200000000000001 — but then, in the if(val == 0.512) line, 0.512 can't be represented exactly there, either, so is likely to be converted to the same value, 0.51200000000000001, which will match.
In most cases, however, it's likely that some error has crept in. Here is a simple example:
double val = 1.; val /= 49; val *= 49; val -= 1; if(val == 0) { expected case; } else { surprising case; }
This will usually hit the surprising case, even though, yes, 0.0 is exactly representable.
See also this question and this question.