2

I've got a problem with rounding using sprintf. I get passed '%0.0f' as the format. sprintf rounds not as expected: 0.5 should be rounded to 1, instead it rounds to 0, which is against the general rounding rule, whereas 1.5, 2.5 etc. is being rounded correctly:

sprintf('%0.0f', 0.5)
=> "0"


sprintf('%0.0f', 1.5)
=> "2"

Why is this so and how can I achieve my expected behaviour?

nintschger
  • 1,786
  • 5
  • 30
  • 45
  • 1
    Actually, every even number rounds wrong - 2.5 gives 2, for example. – Joe Feb 09 '18 at 14:48
  • 3
    Relevant: https://bugs.ruby-lang.org/issues/12548 and https://bugs.ruby-lang.org/issues/12958 – Joe Feb 09 '18 at 14:50

3 Answers3

4

sprintf performs banker's rounding, which rounds 0.5 to the nearest even number. This method is often used by statisticians, as it doesn't artificially inflate averages like half-up rounding.

The Float#round method (in Ruby 2.4+) accepts a parameter which can be one of:

  • half: :up (the default)
  • half: :down
  • half: :even (banker's rounding)

Apparently you are expecting round's default, so you can just do a .round to your number before printing.

Mark Thomas
  • 37,131
  • 11
  • 74
  • 101
  • _"sprintf performs banker's rounding"_ – oh indeed, is this documented? – Stefan Feb 09 '18 at 16:22
  • 1
    @Stefan Not sure, but the default `round` behavior [almost got changed](http://www.rubyguides.com/2016/12/new-ruby-features/) to banker's rounding in Ruby 2.4, but at the last minute it was reverted. Too much of a breaking change, I guess. – Mark Thomas Feb 09 '18 at 16:27
  • @Stefan Ruby almost certainly inherits it from C's `printf` which specifies it in the standard https://stackoverflow.com/questions/10357192/printf-rounding-behavior-for-doubles – Max Feb 09 '18 at 16:49
  • @Max. I think you're on to something. According to the [spec](http://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html), "The low-order digit shall be rounded in an implementation-defined manner." But it appears that most C implementations use [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) which specifies banker's rounding. – Mark Thomas Feb 09 '18 at 16:57
  • @Max so depending on my C library, it could round either way? – Stefan Feb 09 '18 at 17:04
  • 2
    @Stefan glancing at the source it looks like it might not actually call C's sprintf but instead reimplements the behavior of it. I would just call it interpreter-implementation-specific and leave it at that. – Max Feb 09 '18 at 17:20
3

how can I achieve my expected behaviour?

Round the float before you give it to sprintf:

2.4.0 :001 > sprintf('%0.0f', 0.5.round)
 => "1" 

2.4.0 :002 > sprintf('%0.0f', 1.5.round)
 => "2" 
7stud
  • 46,922
  • 14
  • 101
  • 127
0

This is expected behavior for printf. The default rounding mode is to round to the nearest valid value, and in case of a tie to pick the even number. Since 0.5 is treated as being in exactly the middle of 0 and 1, it tends to 0 because it is the even number.

imbuedHope
  • 508
  • 3
  • 14