Backport #3803


BigDecimal::ROUND_HALF_DOWN/ROUND_HALF_EVEN behave incorrectly (disagree with JRuby and the rest of the world)

Added by matthw (Matthew Willson) almost 12 years ago. Updated over 10 years ago.



The incorrect behaviour is that all fractional values between 0.5 (inclusive) and 0.6 (non-inclusive) are subject to the rounding policy for 'half', whereas it should only be applied for fractional values exactly equal to 0.5.

This means that, for example, 0.59 is rounded down to 0 by ROUND_HALF_DOWN and ROUND_HALF_EVEN:

 >>"0.59").round(0, BigDecimal::ROUND_HALF_DOWN).to_s('F')
 => "0.0"
 >>"0.56").round(0, BigDecimal::ROUND_HALF_EVEN).to_s('F')
 => "0.0"

The behaviour is actually specified this way in the RDoc:

ROUND_HALF_DOWN: round up if the appropriate digit >= 6, otherwise truncate

But this is most definitely not what most people would expect from 'round half down' (see eg: ). I would expect it to only round down if the fractional part is less than or exactly equal to 0.5. It needs to take into account more than just the first decimal digit to do this; while the first decimal digit of 0.59 may be 5, it is not equal to 0.5 and hence should not be subject to the 'half down' rounding policy. (Note that ROUND_HALF_UP still works correctly, since the correct behaviour here can indeed be implemented by only inspecting the first decimal digit; but this is not the case for ROUND_HALF_DOWN or in general for ROUND_HALF_EVEN)

Rather misleadingly, ROUND_HALF_EVEN is specified as being "banker's rounding":

ROUND_HALF_EVEN: round towards the even neighbor (Banker’s rounding)

But since it incorrectly applies the 'half towards even' policy not just to 0.5, but to all values in the [0.5, 0.6) interval, it is not "banker's rounding" as this is usually defined, and is surely a biased rounding policy, which is exactly what banker's rounding is seeking to avoid (see eg )

I note (as just one example) that Java's BigDecimal specifies and implements ROUND_HALF_DOWN in the correct way:

Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.

And that JRuby's BigDecimal appears to be based on this, hence is correct and hence disagrees with MRI when (for example) applying ROUND_HALF_DOWN to 0.59:

 irb(main):004:0> RUBY_PLATFORM
 => "java"
 irb(main):005:0>"0.59").round(0, BigDecimal::ROUND_HALF_DOWN).to_s('F')
 => "1.0"

Backport to 1.8.7 would be nice if this gets fixed.


ruby_bigdecimal_round_half.patch (3.88 KB) ruby_bigdecimal_round_half.patch Minimal patch with test and docfix matthw (Matthew Willson), 09/15/2010 10:56 AM
ruby_bigdecimal_round_half_with_comments.patch (8.35 KB) ruby_bigdecimal_round_half_with_comments.patch Patch with some extra comments which helped me figure out what's going on matthw (Matthew Willson), 09/15/2010 10:56 AM

Related issues 1 (0 open1 closed)

Has duplicate Ruby master - Bug #4567: BigDecimal::ROUND_HALF_DOWNClosedmrkn (Kenta Murata)04/11/2011Actions

Also available in: Atom PDF