First, when converting with %f
, printf
rounds the number to six digits after the decimal point. To see the full values in this case, you can use %.20f
:
#include <stdio.h>
int main(void)
{
float m = 4.2;
printf("%.20f\n", m * 1);
printf("%.20f\n", m * 10);
printf("%.20f\n", m * 100);
printf("%.20f\n", m * 1000);
}
Output:
4.19999980926513671875
42.00000000000000000000
419.99996948242187500000
4200.00000000000000000000
To understand what is happening, consider the actual value of m
. In your C implementation, float
is implemented using 24 bits for the significand (the fraction portion of the floating-point number). This is often described as being 24 binary digits with a radix point (the general version of a decimal point) after the first bit, such as 1.000011001100110011001102. With the sign and exponent, the floating-point form would be +1.000011001100110011001102•22.
However, we can also scale the significand to be an integer less than 224, by adjusting the exponent correspondingly. +1.000011001100110011001102•22 = +1000011001100110011001102•2−21. In decimal, 1000011001100110011001102 is 8,808,038, and 2−21 is 1/2,097,152. And 8,808,038 / 2,097,152 = 4.19999980926513671875. This representation using integers less than 224 is mathematically equivalent to the form with the radix point, but it lets us see some of the rounding effects more easily, as we will see below.
When we multiply by 10 using ordinary real-number arithmetic, the result would be 88,080,380 / 2,097,152 = 88,080,380 / 221. However, that numerator does not fit in the 24 bits of the float
format your C implementation uses. We have to adjust to bring it under 224 = 16,777,216. Adjustments are made by adjusting the exponent, which multiplies or divides the significand by powers of two. We can adjust the exponent by three and divide the numerator by 23, which gives 11,010,047.5 / 218. But now the numerator is not an integer. To fit it into the format, it is rounded to the nearest integer. 11,010,047 and 11,010,048 are equally far from 11,010,047.5. The rule for ties is to use the choice with the even low digit, so 11,010,048 is used.
So the result of m * 10
is 11,010,048 / 218 = 11,010,048 / 262,144 = 42.
Now consider multiplying by 100. The real-number result would be 880,803,800 / 221. To get the numerator under 16,777,216, we adjust the exponent by 6, dividing the numerator by 64. The result is 13,762,559.375 / 215. Again we round the numerator to an integer, giving 13,762,559 / 215. Observe in this case we happened to round down instead of rounding up. It so happens that the fraction landed under ½, so we rounded down. 13,762,559 / 215 = 13,762,559 / 32,768 = 419.999969482421875.
What is happening here is that multiplying by various powers of 10—1, 10, 100, 1000 (in binary: 1, 10102, 11001002, 11111010002)—produces various results in those fractions. Since we started with a number just under 4.2 (4.19999980926513671875), when there is a rounding up, the result reaches a multiple of 4.2. When there is a rounding down, it does not.