1

While testing the float type and printing it with it's format specifier %f I was testing it's rounding methods.

I've declared the variable as float and gave it the value 5.123456. As you know float must represent at least 6 significant figures.

I then changed it's value to 5.1234567 and printed the value with the %f. It baffles me why it prints out as 5.123456. But if I change the variable value to 5.1234568, it prints out as 5.123457. It rounds properly.

If I haven't made myself clear or the explanation is very confusing:

float a = 5.1234567
printf("%d", a); 
// prints out as 5.123456

float a = 5.1234568
printf("%d", a); 
// prints out as 5.123457

I've compiled using CodeBlocks and MinGW, same result.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 2
    The exact value of 5.1234567f is 5.123456478118896484375 which clearly should round down when rounded after the 6. – harold Dec 13 '16 at 17:57
  • 1
    "at least 6 significant figures" doesn't mean 6 digits after the decimal point. You're trying to get 7 or more digits of precision out of a float. – user2357112 Dec 13 '16 at 18:09

3 Answers3

4

OP is experiencing the effects of double rounding

First, the values 5.123456, 5.1234567, etc. are rounded by the compiler to the closest representable float. Then printf() is rounding the float value to the closest 0.000001 decimal textual representation.


I've declared the variable as float and gave it the value 5.123456. As you know float must represent at least 6 significant figures.

A float can represent about 2^32 different values. 5.123456 is not one of them. The closest value a typical float can represent is 5.12345600128173828125 and that is correct for 6 significant digits: 5.12345...

float x = 5.123456f;
//  5.123455524444580078125 representable float just smaller than 5.123456
//  5.123456                OP's code
//  5.12345600128173828125  representable float just larger  than 5.123456 (best)

// The following prints 7 significant digits
// %f prints 6 places after the decimal point.
printf("%f", 5.123456f); // --> 5.123456

With 5.1234567, the closest float has an exact value of 5.123456478118896484375. When using "%f", this is expected print rounded to the closest 0.000001 or 5.123456

float x = 5.1234567f;
//  5.123456478118896484375 representable float just smaller than 5.1234567 (best)
//  5.1234567                OP's code
//  5.1234569549560546875   representable float just larger  than 5.1234567

// %f prints 6 places after the decimal point.
printf("%f", 5.1234567f); // --> 5.123456

Significant digits is not the number of digit after the decimal point. It is the number of digits starting with the left-most (most significant) digit.

To print a float to 6 significant figures, use "%.*e".
See Printf width specifier to maintain precision of floating-point value for more details.

float x = 5.1234567;         
printf("%.*e\n", 6 - 1, x);  // 5.12346e+00
                             // x xxxxx   6 significant digits
Community
  • 1
  • 1
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • I haven't seen decimal to floating-point to decimal round-trips characterized as "double rounding" before. Usually it is described as successive roundings in the same base -- no conversion involved. I think the simpler answer is just that [floats do not have 8 digits of precision in that range](http://www.exploringbinary.com/decimal-precision-of-binary-floating-point-numbers/). – Rick Regan Dec 15 '16 at 13:48
  • @RickRegan Double rounding, even when in the same base, is effectively a _conversion_ from some internal high precision to a lower one. The same principal applies here even though a change in base is also involved. Suggest you post the alternative "floats do not have 8 digits of precision in that range." as an answer. For me, I find the link insufficient as it explains half of the round trip and not the whole round trip issues experienced by OP. – chux - Reinstate Monica Dec 15 '16 at 15:35
  • Actually the article does cover the full round-trip, though more implicitly than explicitly. That's not to say it's a better answer than yours (more of a supplement?). It's mainly your characterization of the conversion as a rounding that I disagree with; 5.1234567 becomes 5.123456478118896484375 (the 7 becomes 478118896484375) -- is that a (decimal) rounding per se? – Rick Regan Dec 16 '16 at 19:52
  • @RickRegan As I see it: the conversion of source code text "5.1234567", which has an exact _value_ of 5.1234567 which has a binary value of `101.0001_1111_1001_1010_1101_1011_1011...` to a `float` incurred a _binary_ rounding to 24-bits: `101.0001_1111_1001_1010_1101_1`. That `float`'s binary value is exactly 5.123456478118896484375. Thus the first rounding. – chux - Reinstate Monica Dec 16 '16 at 20:33
0

There is no exact float representation for the number 5.1234567 you intend to show here.

If you check here: https://www.h-schmidt.net/FloatConverter/IEEE754.html

You can see that this number is converted into 5.1234565, or the double 5.1234564781188965 and this rounds down,

While the number 5.1234568 is representable in float, and has a double representation of 5.123456954956055, and this rounds up.

fernando.reyes
  • 597
  • 2
  • 15
0

There are two levels of rounding going on:

  1. Your constant of 5.1234567 gets rounded to the nearest value which can be represented by a float (5.123456478...).
  2. The float gets rounded to 6 digits when printed.

It will become obvious if you print the value with more digits.

What it comes down to is that the mantissa of a float has 23 bits and this is not the same as 6 decimal digits (or any number of digits really). Even some apparently simple values like 0.1 don't have an exact float representation.

Olivier
  • 1,144
  • 1
  • 8
  • 15