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.
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.