-
Never use float for performing arithmetic calculations relating to money.
Floating numbers can sometimes show funky behaviour. It is not Ruby’s fault but the very implementation of floating numbers raises precision issues.
Examples of odd behaviour:
1. 0.33 * 10 => 3.3000000000000003
2. 0.3 - 0.1 => 0.19999999999999998
In order to ensure the integrity of calculations, make sure that all the actors
participating in the calculations are of class BigDecimal
.
Solutions to odd behaviour:
1. (BigDecimal.new(‘0.33’) * BigDecimal.new(‘10’)).to_s => “0.33E1”
2. (BigDecimal.new(‘0.3’) - BigDecimal.new(‘0.1’)).to_s => “0.2E0”
I have found some articles arguing the exchangeability of BigDecimal
class
with Rational
class. While it’s true that Rational
objects are exact
numbers, the expressions can return incorrect result if inexact factors are involved.
Hence, the thumb rule here is ALWAYS USE BIGDECIMAL CLASS.
-
Always round the numbers to desired precision before performing calculations.
It is a common scenario while generating reports that money don’t add up and there are few cents missing. This results due to rounding-off mistakes during calculations.
Below I share an issue of my project where a mere mistake of not rounding-off the commission amount properly resulted in generation of reports with inconsistent values and the cumulative numbers were exceeding the actual values by hundreds of dollars (I hope my client does not read this blog).
Scenario of Odd Behaviour:
```ruby
amount = BigDecimal.new(‘20.33’)
=> #<BigDecimal:282c560,'0.2033E2',18(18)>
rate = BigDecimal.new(50)
=> #<BigDecimal:27849a0,'0.5E2',9(18)>
commission = (amount * rate) / (BigDecimal.new(‘100’))
=> #<BigDecimal:2666ac8,'0.10165E2',18(45)>
payable = amount - commission
=> #<BigDecimal:24678f8,'0.10165E2',18(27)>
payable + commission
=> #<BigDecimal:24537b8,'0.2033E2',18(27)>
payable.round(2) + commission.round(2)
=> #<BigDecimal:24227f8,'0.2034E2',18(27)>
If you noticed, the sum of payable and commission amount do add up to equal the
actual amount but when they were rounded, the sum didn’t match with the
actual amount.
The mistake was not rounding off the `commission` amount. As a result, the
operations thereafter were performed on default precision of `BigDecimal`
object. As long as same precision was maintained, the arithmetic calculations
did add up as shown in second last expression. But the moment, the numbers were
rounded, the rounding rules rounded the numbers separately and the resulting
numbers were now inconsistent.
**Solution to Odd Behaviour:**
```ruby
amount = BigDecimal.new(‘20.33’).round(2)
=> #<BigDecimal:2cde270,'0.2033E2',18(27)>
rate = BigDecimal.new(50)
=> #<BigDecimal:27849a0,'0.5E2',9(18)>
commission = ((amount * rate) / (BigDecimal.new(‘100’))).round(2)
=> #<BigDecimal:2e6f698,'0.1017E2',18(27)>
payable = amount - commission
=> #<BigDecimal:2e49a88,'0.1016E2',18(27)>
payable.round(2) + commission.round(2)
=> #<BigDecimal:2cf0f10,'0.2033E2',18(27)>
```
Hence, the thumb rule here is
1. **ALWAYS ROUND OFF THE NUMBERS BEFORE ANY CALCULATION**
2. **ALWAYS ROUND OFF THE NUMBERS AFTER MULTIPLICATION AND DIVISION.**