You seem to be confusing two sources of rounding (and precision loss) with floating point numbers.
Floating point representation
The first one is due to the way floating point numbers are represented in memory, which uses binary numbers for the mantissa and exponent, as you just pointed. The classic example being :
const float a = 0.1f;
const float b = 0.2f;
const float c = a+b;
printf("%.8f + %.8f = %.8f\n",a,b,c);
which will print
0.10000000 + 0.20000000 = 0.30000001
There, the mathematically correct result is 0.3, but 0.3 is not representable with the binary representation. Instead you get the closest number which can be represented.
Saving to text
The other one, which is where max_digits10
comes into play, is for text representation of floating point number, for example, when you do printf
or write to a file.
When you do this using the %f
format specifier you get the number printed out in decimal.
When you print the number in decimal you may decide how many digits get printed out. In some cases you might not get an exact printout of the actual number.
For example, consider
const float x = 10.0000095f;
const float y = 10.0000105f;
printf("x = %f ; y = %f\n", x,y);
this will print
x = 10.000010 ; y = 10.000010
on the other hand, increasing the precision of printf
to 8 digits with %.8f
will give you.
x = 10.00000954 ; y = 10.00001049
So if you wanted to save these two float values as text to a file using fprintf
or ofstream
with the default number of digits, you may have saved the same value twice where you originally had two different values for x
and y
.
max_digits10
is the answer to the question "how many decimal digits do I need to write in order to avoid this situation for all possible values ?". In other words, if you write your float with max_digits10
digits (which happens to be 9 for floats) and load it back, you're guaranteed to get the same value you started with.
Note that the decimal value written may be different from the floating point number's actual value (due to the different representation. But it is still guaranteed than when you read the text of the decimal number into a float
you will get the same value.
Edit: an example
See the code runt there : https://ideone.com/pRTMZM
Say you have your two float
s from earlier,
const float x = 10.0000095f;
const float y = 10.0000105f;
and you want to save them to text (a typical use-case would be saving to a human-readable format like XML or JSON, or even using prints to debug). In my example I'll just write to a string using stringstream
.
Let's try first with the default precision :
stringstream def_prec;
def_prec << x <<" "<<y;
// What was written ?
cout <<def_prec.str()<<endl;
The default behaviour in this case was to round each of our numbers to 10
when writing the text. So now if we use that string to read back to two other floats, they will not contain the original values :
float x2, y2;
def_prec>>x2 >>y2;
// Check
printf("%.8f vs %.8f\n", x, x2);
printf("%.8f vs %.8f\n", y, y2);
and this will print
10 10
10.00000954 vs 10.00000000
10.00001049 vs 10.00000000
This round trip from float to text and back has erased a lot of digits, which might be significant. Obviously we need to save our values to text with more precision than this. The documentation guarantees that using max_digits10
will not lose data in the round trip. Let's give it a try using setprecision
:
const int digits_max = numeric_limits<float>::max_digits10;
stringstream max_prec;
max_prec << setprecision(digits_max) << x <<" "<<y;
cout <<max_prec.str()<<endl;
This will now print
10.0000095 10.0000105
So our values were saved with more digits this time. Let's try reading back :
float x2, y2;
max_prec>>x2 >>y2;
printf("%.8f vs %.8f\n", x, x2);
printf("%.8f vs %.8f\n", y, y2);
Which prints
10.00000954 vs 10.00000954
10.00001049 vs 10.00001049
Aha ! We got our values back !
Finally, let's see what happens if we use one digit less than max_digits10
.
stringstream some_prec;
some_prec << setprecision(digits_max-1) << x <<" "<<y;
cout <<some_prec.str()<<endl;
Here this is what we get saved as text
10.00001 10.00001
And we read back :
10.00000954 vs 10.00000954
10.00001049 vs 10.00000954
So here, the precision was enough to keep the value of x
but not the value of y
which was rounded down. This means we need to use max_digits10
if we want to make sure different floats can make the round trip to text and stay different.