## Numeric types in Ruby

14 June 2022

I recently hit an issue with an existing application which was experiencing bugs
comparing two numbers—one stored as a `Float`

, the other as a `BigDecimal`

. I
had some existing idea this was bug-prone and not good practice, but the details
evaded me so I thought I’d spend some time looking into the representation of
numeric types in Ruby.

## Why worry?

Why do we even need to worry about how our numbers are being stored? Ruby isn’t a strongly typed language, can’t we just let it do its thing and forget about it? In many cases, yes, but in some—as I discovered—no.

If we just present a number to Ruby, it will attempt a best guess at what class to use to represent it. Often this is appropriate:

But other times, not so much:

## What are these issues?

This isn’t the place to get into the intricacies of how floating point numbers are represented internally, suffice to say they are performant but inaccurate; sometimes this matters, sometimes it doesn’t. If you want to understand what’s going on, this is a great resource.

`Numeric`

and its subclasses

All numbers in Ruby are represented using one of the subclasses of Numeric. The
most important of these are `Integer`

, `Float`

, `Complex`

, `Rational`

and
`BigDecimal`

, and below I take a look at each of them. It’s worth noting you
cannot instantiate any of these. They are implemented as **immediates**, so are
immutable. There cannot be more than one instance of 5, for example.

There are a wide number of methods and checks they all support. Some of the most
useful (and self-apparent) are `finite?`

, `integer?`

, `negative?`

, `nonzero?`

,
`positive?`

, `real?`

and `zero?`

.

### Integer (docs)

Probably the simplest of the subclasses, `Integer`

s represent whole numbers.
Historically, the two subclasses of `Integer`

(`Fixnum`

and `Bignum`

), were used
depending on the size of the integer—the former if it could be represented in a
native machine word, otherwise the latter. This is no longer the
case.

### Float (docs)

As we saw above, `Float`

s are Ruby’s default for representing numbers with a
fractional part. They’re fast and often adequate but have their shortcomings:
primarily their use can lead to inaccuracies and unexpected results.

### Complex (docs)

If you’re unaware of the mathematical concept of complex numbers, it probably means you’re unlikely to need to use them and don’t need to worry too much about how they’re implemented in Ruby. Basically, a complex number has two parts: one of them “imaginary”. We can deal with them as follows:

Be aware of the possibly counter-intuitive nature of the `real?`

method when
applied to `Complex`

objects however—it will return `false`

even if the number
has no imaginary part.

### Rational (docs)

Rational numbers are those represented by a numerator and denominator, and Ruby is happy to handle them:

Rational numbers are exact, but be cautious when comparing them to inexact data types.

### BigDecimal (docs)

Finally, `BigDecimal`

provides support for very large or very accurate
representation of numbers with a fractional part. They are the recommended way
of representing financial data.

`BigDecimal`

comes from the standard library, so you will need to ```
require
'bigdecimal'
```

in your project.

A few things to be aware of when working with the class:

- It will return infinity in some cases, such as division by zero.
- In others, it will return
`NaN`

(Not a Number), such as 0/0.`NaN`

is never equal to anything,**including itself**.

- Numbers too small to be represented using the currently defined precision will return zero.

The precision `BigDecimal`

uses is defined when you create the object: the
second argument to `new`

is the number of significant digits. There’s a good
description of how this works over on Stack
Overflow.