4

Could you please explain this code:

  printf("Value1: %0.1f \n", (float)124/100); // = 1.2
  printf("Value1: %0.1f \n", (float)125/100); // = 1.2
  printf("Value1: %0.1f \n", (float)126/100); // = 1.3
  printf("\n");
  printf("Value2: %0.1f \n", (float)134/100); // = 1.3
  printf("Value2: %0.1f \n", (float)135/100); // = 1.4
  printf("Value2: %0.1f \n", (float)136/100); // = 1.4

Why is the number 125 rounding down to 1.2 and 135 rounding up to 1.4? For example, it also happens with the numbers 105, 115, 125, 155, 165.

Darth-CodeX
  • 2,166
  • 1
  • 6
  • 23
  • 2
    It's probably using banker's rounding. – Szabolcs Apr 22 '22 at 07:43
  • 1
    Observation: This also occurs without a `(float)` cast, as: `printf("%f", 125.0/100.0); // = 1.250000` and using `%.1f // = 1.2`. (?) – S3DEV Apr 22 '22 at 07:50
  • 3
    Which platform/compiler are you using ? On MSVC the line with 125 actually shows 1.3 (rounding as you expected) – wohlstad Apr 22 '22 at 07:52
  • @wohlstad - (clang-7 via [replit.com](https://replit.com/languages/c) for a quick/easy debug.) – S3DEV Apr 22 '22 at 07:56
  • 1
    Your observation is basically the same as comparing floats for identity. If it is precisely the value you state it should be rounded the same way, true. But it is not, because of the duplicate I am going to name. – Yunnosch Apr 22 '22 at 07:57
  • 4
    1.35 rounds up because 1.35 is actually 1.35000002384185791015625 – user3386109 Apr 22 '22 at 07:58
  • 1
    You need to consider the binary representations of these values to understand the rounding. Some of these values don't have exact representations, so you're going to have inexact rounding issues. – Tom Karzes Apr 22 '22 at 07:59
  • 1
    @Yunnosch That duplicate does _not_ explain why the exact representation 1.2500000000 gets rounded to 1.2. – Lundin Apr 22 '22 at 08:02
  • @Lundin True. I missed that detail. I took the duplicate away, but here is the link, because I am sure it is helpful info for this: https://stackoverflow.com/questions/588004/is-floating-point-math-broken However, the question should be focused on that detail, point it out explicitly, maybe mention that my link does not explain it, in the question (because comments are quite close to my thinking....). – Yunnosch Apr 22 '22 at 08:03
  • 2
    you need to compare 1.25 and 1.75 to see the real rounding because they're exactly representable in binary floating-point whereas 1.35 isn't – phuclv Apr 22 '22 at 08:06
  • @wohlstad I noticed it on Keil IDE uVision - ARM compiler V5, then tried online IDE Replit [link](https://replit.com) for C language – Martin_Imrich Apr 22 '22 at 08:15
  • 2
    Well I can't explain why `if(fegetround()==FE_TONEAREST) { printf("Value1: %0.1f %.20f\n", (double)125/100, 1.25); printf("Value1: %0.1f %.20f\n", (double)175/100, 1.75); }` prints `Value1: 1.2 1.25000000000000000000` `Value1: 1.8 1.75000000000000000000` on gcc/glibc x86 Linux. I think it has something to do with printf internals and language lawyering of 7.21.6.1: "A double argument representing a floating-point number is converted to decimal notation in the style [−]ddd.ddd, where the number of digits after the decimal-point character is equal to the precision specification." – Lundin Apr 22 '22 at 08:17
  • 1
    There are two roundings that could both be represented by `FE_TONEAREST`, either ties to even, or ties away from zero, but no standard way to choose between the two if both are supported. Printf only needs to "correctly" round the result with the sign of the error being consistent with the current rounding mode, but rounding ties either to even or away from zero is not specified by the C standard. – Ian Abbott Apr 22 '22 at 09:51
  • @Lundin it's just different rounding methods. Duplicates: [Why is printf round floating point numbers up?](https://stackoverflow.com/q/24120888/995714), [printf rounding behavior for doubles](https://stackoverflow.com/q/10357192/995714), [Float point numbers and incorrect result due to rounding behavior](https://stackoverflow.com/q/40372148/995714) – phuclv Apr 22 '22 at 11:28
  • Does this answer your question? [Why is printf round floating point numbers up?](https://stackoverflow.com/questions/24120888/why-is-printf-round-floating-point-numbers-up) – phuclv Apr 22 '22 at 11:29
  • @phuclv Well that answer quotes the very section of the standard I found too, which is far from clear. So the solution is what, round the number manually before passing it to the broken printf function? – Lundin Apr 22 '22 at 11:31

1 Answers1

4

The C standard does not fully specify either the float format or how rounding is performed. Most C implementations today use the IEEE-754 binary32 format for float. In this format:

  • The representable value nearest 1.24 is 1.2400000095367431640625.
  • The representable value nearest 1.25 is 1.25.
  • The representable value nearest 1.26 is 1.2599999904632568359375.
  • The representable value nearest 1.34 is 1.34000003337860107421875.
  • The representable value nearest 1.35 is 1.35000002384185791015625.
  • The representable value nearest 1.36 is 1.36000001430511474609375.

If you change %0.1f in the printf calls to %.99g, your C implementation will likely show you those values.

The most common rounding rule used is to round to the nearest representable value and, in case there is a tie, to round to the tied value with an even low digit. This is the rule almost always used in arithmetic operations (notably addition, subtraction, multiplication, and division), so your C implementation likely uses it when calculating (float)124/100 and so on.

That rule is also often used when converting numbers from the internal binary float format to numerals in character strings, as with printf. (The rule is used a little less often in this case because some conversion software is not written to use the precision or calculations necessary to always produce correct results according to this rule.) The output you observe is consistent with use of this rule:

  • Rounding 1.2400000095367431640625 to one digit after the decimal point produces “1.2”, since it is closer to 1.2 than to 1.3.
  • Rounding 1.25 to one digit after the decimal point produces “1.2” since 1.2 and 1.3 are tied and 1.2 has the even low digit.
  • Rounding 1.2599999904632568359375 to one digit after the decimal point produces “1.3” since it is closer to 1.3 than to 1.2.
  • Rounding 1.34000003337860107421875 to one digit after the decimal point produces “1.3” since it is closer to 1.3 than to 1.4.
  • Rounding 1.35000002384185791015625 to one digit after the decimal point produces “1.4” since it is closer to 1.4 than to 1.3.
  • Rounding 1.36000001430511474609375 to one digit after the decimal point produces “1.4” since it is closer to 1.4 than to 1.3.
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312