a * b
uses float
arithmetic. 100 * 1.05
uses double
arithmetic.
Your C implementation probably uses IEEE-754 binary64 for double
and binary32 for float
.
1.05
is a double
constant. The decimal numeral 1.05
in source text is converted to the nearest representable double
value, which is 4728779608739021•2−52 = 1.0500000000000000444089209850062616169452667236328125.
float b = 1.05;
converts that value to float
. The result of the conversion is the nearest representable float
value, which is 4728779393990656•2−52 = 4404019•2−22 = 1.0499999523162841796875.
When b
is multiplied by 100 in float
arithmetic, the result is the representable float
value that is nearest the real-arithmetic result. It is 13762559•2−17 = 104.99999237060546875.
When that is printed with six digits after the decimal point, the result is “104.999992”.
In 100 * 1.05
, only double
arithmetic is used; there are no float
values. When the double
1.0500000000000000444089209850062616169452667236328125 is multiplied by 100, the result is the double
nearest the real-arithmetic result. It is 105•20 = 105. When that is printed with six digits after the decimal point, the result is of course “105.000000”.
Summary: There are generally rounding errors in floating-point arithmetic. In float
arithmetic, they are easily visible when more than a few digits are printed. In this example with double
arithmetic, the rounding errors happened to cancel—the double
nearest 1.05 was slightly above 1.05, so the rounding in the conversion rounded up. Then, when that was multiplied by 100, the double
nearest the real-arithmetic result was slightly below it, so the rounding in the multiplication rounded down. These effectively canceled, producing 105.
If you execute printf("%f\n", 100*1.05f);
, 1.05f
will be converted directly to float
instead of to a double
, and the multiplication will be done with float
arithmetic,1 and you will see the same result as for a * b
.
Footnote
1 The C standard allows extra precision to be used, but generally you will see the same result here.