If you use an architecture where float and double are IEEE-754 binary32 and binary64, respectively, and integer and floating-point byte order is the same, you might find the attached float-bits command-line utility useful. It should compile with any C99 or later C compiler. In particular, it works fine on 32-bit and 64-bit Intel/AMD architectures.
Simply put, it shows the binary representation of any floating-point number, or the sum, difference, product, or division of a pair of numbers. Run it without arguments, and it shows the usage.
If you use Linux, you can compile and install it using e.g. gcc -Wall -O2 float-bits.c -o float-bits && sudo install -o root -g root -m 0755 float-bits /usr/local/bin/.
If we run float-bits -f 1/3 the output is
1/3 = 0.3333333432674408
0 011111110 (1)0000000000000000000000
/ 0 100000001 (1)0000000000000000000000
= 0 011111010 (1)1010101010101010101011
With regards to the issue OP is having, the key is to look at the result: it is rounded up. (The mathematical evaluation rules in C are such that when the result is stored in a variable, or the expression is cast to a specific numeric type, the compiler must evaluate the value at the specified precision and range. This means that it is not allowed to optimize away entire operations.)
Note that if we run float-bits -f 0.99999997/3 the output is
0.99999997/3 = 0.3333333134651184
0 011111101 (1)1111111111111111111111
/ 0 100000001 (1)0000000000000000000000
= 0 011111010 (1)1010101010101010101010
So, the three numbers closest to one third a single-precision floating-point number can represent are float-bits -f 0.33333332 0.33333334 0.33333336:
0.33333332: 0 011111010 (1)1010101010101010101010
0.33333334: 0 011111010 (1)1010101010101010101011
0.33333336: 0 011111010 (1)1010101010101010101100
Multiplying them by three (float-bits -f 3x0.33333332 3x0.33333334 3x0.33333336) yields
3x0.33333332 = 0.9999999403953552
0 100000001 (1)0000000000000000000000
x 0 011111010 (1)1010101010101010101010
= 0 011111101 (1)1111111111111111111111
3x0.33333334 = 1.0000000000000000
0 100000001 (1)0000000000000000000000
x 0 011111010 (1)1010101010101010101011
= 0 011111110 (1)0000000000000000000000
3x0.33333336 = 1.0000001192092896
0 100000001 (1)0000000000000000000000
x 0 011111010 (1)1010101010101010101100
= 0 011111110 (1)0000000000000000000001
Essentially, when one writes 3.0f * (float)(1.0f / 3.0f) or something equivalent in C (using C99 or later rules), two implicit rounding operations occur. The first one rounds one third to the nearest value representable by a binary32 float up, and the second one rounds the slightly-over-one value to the nearest value representable by a binary32 float, down to exactly one. (Remember that these rounding operations operate on the floating-point number, and can at most add or subtract one unit in the least significant place.)
The answer to OP's question is then that this happens, because when implemented in floating-point math, there are two rounding operations done, and using the default rounding rules the two happen to cancel each other out, giving the unexpected, mathematically correct value.
Floating-point math is still exact math, it's just that after each operation, there is an implicit rounding to the nearest value representable by the used type. (However, there are "unsafe math optimizations" some compilers can do, which fuse multiple operations to one; and the FMA intrinsics are designed to do a fused multiply-add where only one rounding happens, at the end.)