My (somewhat naive) view of compilers is that they can only do
optimization that has no visible effects on the behavior of the
program.
Here is what the different floating point behaviors (strict/fast/precise) mean with VC (I realize you're on linux, but the same must count/exist on linux as these are CPU features). As you can see, it's intricate.
In general what you are trying to do is not possible (that way), you'll need to allow for some delta between values that if the difference is smaller that delta, this qualifies them as being equal. So far so good so. We have our friends FLT_EPSILON and DBL_EPSILON from the STL. These don't help you much, the delta defined by those two is the difference between 2 consecutive floats on the interval from 1. to 2.. So towards 0., the EPSILON is getting smaller, over 2. it's getting bigger, suggesting different approaches are needed for different parts of the real numbers line.
Daniel Lemire has written about this subject and has suggested solutions in the past (search the blog for more articles pertaining to this subject), for only to (partially) revert those opinions later. It's quite a problem really. In case you are really working with fractions (as in your example 3 * (1/3)), it would be better to use a fractions library.