Search code examples
rubyjrubybigdecimalrounding-error

Error in BigDecimal calculation


Consider the following spec:

require 'bigdecimal'

def total_percent(amounts)
  percent_changes = amounts.each_cons(2).map { |a|
    (a[1] - a[0]) / a[0] * BigDecimal.new('100.0')
  }
  (percent_changes.map { |pc| BigDecimal.new('1') + pc / BigDecimal.new('100') }.inject(BigDecimal.new('1'), :*) - BigDecimal.new('1')) * BigDecimal.new('100')
end

describe 'total_percent' do

  specify {
    values = [10000.0, 10100.0, 10200.0, 10000.0].map { |v|
      BigDecimal.new(v.to_s)
    }
    total_percent(values).class.should == BigDecimal
    total_percent(values).should == BigDecimal.new('0.0')
  }

end

The method total_percent calculates the total difference of a list of values in percent. Please ignore the algorithm itself (the same result can be achieved by looking at the first and last values only).

The spec fails because the result of the calculation is not equal to 0.0. The question is where is it losing precision.

Edit: Using JRuby 1.6.5 on OS X 10.7.2.


Solution

  • It's not that it's losing precision, it's that some of the divisions aren't representable by a BigDecimal.

    The question is why does JRuby swallow/define-away the exception it should be throwing when you 100.0/10200.0 etc. JRuby may define a rounding mode, or its operators may wrap themselves in a catch of the ArithmeticException generated by the same calculation (without a rounding mode) in Java (appended below).

    Try setting your own rounding mode, or do an acceptable-delta comparison (I forget the term).

    The exception

    java.lang.ArithmeticException: Non-terminating decimal expansion;
        no exact representable decimal result.