Currency Calculations in Ruby
Tags: ruby
Published: 2020-10-06 20:55:40

*This article was originally written by [Julio Sampaio][1] on the [Honeybadger Developer Blog][2].*  

Money, regardless of the currency it is in, seems like a floating-point number. But it's a mistake to use floats for currency.

[Float](https://ruby-doc.org/core-2.5.0/Float.html) numbers (hence, float objects) are, by definition, inexact real numbers that make use of the double-precision floating-point representation characteristic of the native architecture. Inexact numbers make accountants unhappy.

In this article, you’ll be guided through some quick examples that will help you to address the available options for dealing with money data in Ruby and Rails.

## What's a Float?

As we said, `float` objects have different arithmetic. This is the main reason they are inexact numbers, especially because Ruby (like most languages) uses a fixed number of binary digits to represent floats. In other words, Ruby converts floating numbers from decimal to binary and vice versa.

When you dive deep into the binary representation of real numbers (aka decimal numbers), some of them can't be exactly represented, so the only remaining option is for the system to round them off.

If you think ahead and consider common math structures, such as periodic tithes, you may understand that they can't be entirely represented within a fixed number since the _pi_ number, for example, is infinite. Floats can usually hold up to 32 or 64 bits of precision, which means the number will be cut off when it reaches the limit.

Let’s analyze a classic example:

```ruby
1200 * (14.0/100)
```

This is a straightforward way to calculate the percentage of a number. Fourteen percent of 1200 should be 168; however, the result of this execution within Ruby will be

```ruby
1200 * (14.0/100)
=> 168.00000000000003
```

However, if you add just 0.1% to the formula, you get something different:

```ruby
1200 * (14.1/100)
=> 169.2
```

Alternatively, you could [`round`](https://apidock.com/ruby/Float/round) the value to the nearest possible one, defining how many decimal places are desired:

```ruby
(my_calculation).round(2)
```

Indeed, it is not guaranteed when it comes to more complex calculations, especially if you perform comparisons of these values.

If you're interested in understanding the real science behind it, I highly recommend reading the Oracle's appendix: [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). It explains, in detail, the whys behind the inaccurate nature of float numbers.

## The Trustworthy BigDecimal

Consider the following code snippet:

```ruby
require "bigdecimal"
BigDecimal("45.99")
```

This code can easily represent a real logic embracing an eCommerce cart’s amount. In the end, the real value being manipulated will always be 45.99 instead of 45.9989 or 45.99000009, for example.

This is the precise nature of `BigDecimal`. For usual arithmetic calculations, `float` will perform the same way; however, it is unpredictable, which is the danger of using it.

When it's run with `BigDecimal`, the same percentage calculation we did in the previous section results in

```ruby
require "bigdecimal"
(BigDecimal(1200) * (BigDecimal(14)/BigDecimal(100))).to_s("F")
=> 168.0
```

This is just a short version to allow rapid execution in an _irb_ console.

Originally, when you print the direct `BigDecimal` object, you’ll get its scientific notation, which is not what we want here. The [`to_s`](https://ruby-doc.org/stdlib-2.5.1/libdoc/bigdecimal/rdoc/BigDecimal.html#method-i-to_s) method receives the given argument due to formatting settings and displays the equivalent floating value of the BigDecimal. For further details on this topic, refer to [Ruby docs](https://ruby-doc.org/stdlib-2.5.1/libdoc/bigdecimal/rdoc/BigDecimal.html).

In case you need to determine a limit for decimal places, it has the `truncate` method, which will do the job for you:

```ruby
(BigDecimal(1200) * (BigDecimal("14.12")/BigDecimal(100))).truncate(2).to_s("F")
=> 169.44
```

## The RubyMoney Project

[RubyMoney](https://github.com/RubyMoney) was created after thinking about these problems. It is an open-source community of Ruby developers aiming to facilitate developers' lives by providing great libraries to manipulate money data in the Ruby ecosystem.

The project is composed of seven libraries, three of which stand out in importance:

- [Money](https://github.com/RubyMoney/money): A Ruby library for dealing with money and currency conversion. It provides several object-oriented options to handle money in robust and modern applications, regardless of whether they are for the web.
- [Money-rails](https://github.com/RubyMoney/money-rails): An integration of RubyMoney for Ruby on Rails, mixing all the `money`'s library power with Rails flexibility.
- [Monetize](https://github.com/RubyMoney/monetize): A library for converting various objects into `money` objects. It works more like an auxiliary library for applications that deal with a lot of String parsing, for example.

The project has four other interesting libraries:

- [EU_central_bank](https://github.com/RubyMoney/eu_central_bank): A library that helps calculate exchange rates by making use of published rates from the European Central Bank.
- [Google_currency](https://github.com/RubyMoney/google_currency): An interesting library for currency conversion using Google Currency rates as a reference.
- [Money-collection](https://github.com/RubyMoney/money-collection): An auxiliary library for accurately calculating the sum/min/max of `money` objects.
- [Money-heuristics](https://github.com/RubyMoney/money-heuristics): A module for heuristic analyses of string input for the money gem.

## The “Money” Gem

Let’s start with the most famous one: the [money](https://github.com/RubyMoney/money) gem. Among its main features are the following:

- A `money` class that holds relevant monetary information, such as the value, currency, and decimal marks.
- Another class called `Money::Currency` that wraps information regarding the monetary unit being used by the developer.
- By default, it works with integers rather than floating-point numbers to avoid the aforementioned errors.
- The ability to exchange money from one currency to another, which is super cool.

Other than that, we also get the high flexibility offered by consistent and object-oriented structures to manipulate money data, just like any other model within your projects.

Its usage is pretty simple, just install the proper gem:

```shell
gem install money
```

A quick example involving a fixed amount of money would be

```ruby
my_money = Money.new(1200, "USD")
my_money.cents #=> 1200
my_money.currency #=> Currency.new("USD")
```

As you can see, money is represented based on cents. Twelve hundred cents is equivalent to 12 dollars.

Just like you did with BigDecimal, you can also play around and do some basic math with these objects. For example,

```ruby
cart_amount = Money.new(10000, "USD") #=> 100 USD
discount = Money.new(1000, "USD") #=> 10 USD

cart_amount - discount == Money.new(9000, "USD") #=> 90 USD
```

Interesting, isn’t it? That’s the nature of the objects we mentioned. When coding, it really feels like you’re manipulating monetary values rather than inexpressive and ordinary numbers.

### Currency Conversions

If you’ve got your own exchange rate system, you can perform currency conversions through an exchange bank object. Consider the following:

```ruby
Money.add_rate("USD", "BRL", 5.23995)
Money.add_rate("BRL", "USD", 0.19111)
```

Whenever you need to exchange values between them, you may run the following code:

```ruby
Money.us_dollar(100).exchange_to("BRL") #=> Money.new(523, "BRL")
```

The same applies to any arithmetic and comparison evaluations you may want to perform.

Make sure to refer to the [docs](https://github.com/RubyMoney/money#currency) for more of the provided currency attributes, such as `iso_code` (which returns the international three-digit code of that currency) and `decimal_mark` (the char between the value and the fraction of the money data), among others.

Oh, I almost forgot; once you’ve installed the money gem, you can access a `BigDecimal` method called [`to_money`](https://www.rubydoc.info/gems/money/3.5.1/Numeric:to_money) that automatically performs the conversion for you.

## The “monetize” gem

It is important to understand the role each library plays within the RubyMoney project. Whenever you need to convert a different Ruby object (a `String`, for example) into `Money`, then [`monetize`](https://github.com/RubyMoney/monetize) is what you’re looking for.

First, make sure to install the gem dependency or add it to your _Gemfile_:

```shell
gem install monetize
```

Obviously, `money` also needs to be installed.

The `parse` method is also very useful when you receive money data in a different format; for example,

```ruby
Money.parse("£100") == Money.new(100, "GBP") #=> true
```

Although the scenarios in which you’d use this parsing method are restricted, it can be very useful when you receive a value formatted alongside its currency code from an HTTP request. On the web, everything is text, so converting from string to `money` can be very useful.

However, be careful with how your system manipulates the values and if they can be hacked somehow. Financial systems are always covered by multiple security layers to ensure that the value you’re receiving is the real value of that transaction.

## The “monetize-rails” gem

This is the [library](https://github.com/RubyMoney/money-rails) that deals with the same money manipulation operations, but within a Rails app.

Why do we need a second library just to make it work alongside Rails? Well, you can certainly make use of the `money` gem alone within Rails projects for ordinary math operations. However, it won’t work properly when your Rails structures need to communicate with `money`’s features.

Consider the following example:

```ruby
class Cart < ActiveRecord::Base
  monetize :amount_cents
end
```

This is a real, functional Rails model object. You can use it along with databases (even including aliases when you want a different model attribute name), Mongoid, REST web services, etc.

All the features we’ve been in contact with so far also apply to this gem. Usually, only additional settings are necessary to make it run, which should be placed into the _config/initializers/money.rb_ file:

```ruby
MoneyRails.configure do |config|

  # set the default currency
  config.default_currency = :usd

end
```

This will set the default currency to the one you provide. However, during development, the chances are that you may need to perform exchange conversions or handle more than one currency throughout the models.

If so, `money-rails` allows us to configure a model-level currency definition:

```ruby
class Cart < ActiveRecord::Base

  # Use GPB as model level currency
  register_currency :eur

  monetize :amount_cents, as "amount"
  monetize :discount_cents, with_currency: :eur

  monetize :converted_value, with_currency: :usd

end
```

Note that once everything is set up, it is really easy to make use of money types alongside your projects.

## Wrapping Up

In this blog post, we’ve explored some available options to deal with money values within the Ruby and Rails ecosystems. Some important points are summarized below:

- If you’re dealing with the calculation of float numbers, especially if they represent money data, go for `BigDecimal` or `Money` instances.
- Try to stick to one system only to avoid further inconsistencies alongside your development.
- The `money` library is the core of the whole RubyMoney system, and it is very robust and straightforward. `Money-rails` is the equivalent version for Rails applications, and `monetize` is necessary whenever you need to parse from any value to `Money` objects.
- Avoid using `Float`. Even if your app doesn’t need to calculate anything now, the chances are that an unadvised dev will do it in the future. You might not be there to stop it.

Remember, the official docs should always be a must-to. [BigDecimal](https://ruby-doc.org/stdlib-2.5.1/libdoc/bigdecimal/rdoc/BigDecimal.html) is filled with great explanations and examples of its usage, and the same is true of [RubyMoney](https://github.com/RubyMoney) gem projects.

[1]: https://www.honeybadger.io/blog/ruby-currency/#authorDetails
[2]: https://www.honeybadger.io/blog/ruby-currency/