Weird float rounding behavior with printf

It is as expected, it is "round to even", or "Banker's rounding".

A related site answer explain it.

The issue that such rule is trying to solve is that (for numbers with one decimal),

  • x.1 up to x.4 are rounded down.
  • x.6 up to x.9 are rounded up.

That's 4 down and 4 up.
To keep the rounding in balance, we need to round the x.5

  • up one time and down the next.

That is done by the rule: « Round to nearest 'even number' ».

In code:

sh LC_NUMERIC=C printf '%.0f ' "$value"
awk echo "$value" | awk 'printf( "%s", $1)'


Options:

In total, there are four possible ways to round a number:

  1. The already explained Banker's rule.
  2. Round towards +infinite. Round up (for positive numbers)
  3. Round towards -infinite. Round down (for positive numbers)
  4. Round towards zero. Remove the decimals (either positive or negative).

Up

If you do need "round up (toward +infinite)", then you can use awk:

value=195.5

awk echo "$value" | awk '{ printf("%d", $1 + 0.5) }'
bc echo "scale=0; ($value+0.5)/1" | bc

Down

If you do need "round down (Toward -infinite)", then you can use:

value=195.5

awk echo "$value" | awk '{ printf("%d", $1 - 0.5) }'
bc echo "scale=0; ($value-0.5)/1" | bc

Trim decimals.

To remove the decimals (anything after the dot).
We could also directly use the shell (works on most shells - is POSIX):

value="127.54"    ### Works also for negative numbers.

shell echo "${value%%.*}"
awk echo "$value"| awk '{printf ("%d",$0)}'
bc echo "scale=0; ($value)/1" | bc


It is not a bug, it is intentional.
It is doing a type of round to nearest (more on that later).
With exactly .5 we can round either way. At school you where probably told to round up, but why? Because you then do not have to examine any more digits e.g. 3.51 round up to 4; 3.5 could go ether way, but if we only look at first digit and round .5 up then we always get it right.

However, if we look at the set of 2 digit decimals: 0.00 0.01, 0.02, 0.03 … 0.98, 0.99, we will see there are 100 values, 1 is an integer, 49 have to be rounded up, 49 have to be rounded down, 1 ( 0.50 ) could go ether way. If we always round up, then we get on average numbers that are 0.01 too big.

If we extend the range to 0 → 9.99, we have 9 value extra that round up. Thus making our average a bit bigger than expected. So one attempt at fixing this is: .5 rounds towards even. Half the time it rounds up, half the time it rounds down.

This changes the bias from upwards, to towards even. In most cases this is better.