Skip to content

Instantly share code, notes, and snippets.

@abhinavmsra
Last active November 12, 2023 13:00
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save abhinavmsra/08a9da0fe377a9124978 to your computer and use it in GitHub Desktop.
Save abhinavmsra/08a9da0fe377a9124978 to your computer and use it in GitHub Desktop.
Tips for Money Related Arithmetic in Ruby

Tips for Money Related Arithmetic in Ruby

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

  1. 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.**
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment