# How Numbers Work in Ruby: Understanding Integers, Floats & Bigdecimal

Ruby 2.4 merged `Fixnum` & `Bignum` into the same class (`Integer`) so I think this is a good time to review the different number types in Ruby!

That’s what we are going to talk about in this post 🙂

## An Overview of Number Types

Let’s start by taking a look at the class hierarchy of all the number related classes in Ruby:

```Numeric
Integer
Fixnum
Bignum
Float
Complex
Rational
BigDecimal (Standard Library)
```

As you can see, the `Numeric` class is the parent for all the number classes. Remember that you can use the `ancestors` method to discover the parent classes for any class.

Example:

```Fixnum.ancestors - Fixnum.included_modules

[Fixnum, Integer, Numeric, Object, BasicObject]
```

Now let’s see these classes in table form:

Class Description Example
Integer Parent class of `Fixnum` & `Bignum` 1
Fixnum Whole numbers that fit into the OS integer type (32 or 64 bits) 1
Bignum Used for bigger numbers 111111111111
Float Imprecise decimal numbers 5.0
Complex Used for math stuff with imaginary numbers (1+0i)
Rational Used to represent fractions (2/3)
BigDecimal Perfect precision decimal numbers 3.0

## Float Imprecision

The `Float` class in Ruby is described as “imprecise” in the official Ruby documentation.

Why is that?

Let me show you an example:

```0.2 + 0.1 == 0.3
# false
```

Why is this false?

Let’s look at the result of `0.2 + 0.1`:

```0.30000000000000004
```

Exactly! That’s what we mean by imprecision.

This happens because of the way that a float is stored. If you need decimal numbers that are always accurate you can use the `BigDecimal` class.

## Float vs BigDecimal

BigDecimal is a class that gives you arbitrary-precision decimal numbers.

Example:

```require 'bigdecimal'

BigDecimal("0.2") + BigDecimal("0.1") == 0.3
# true
```

Why don’t we always use `BigDecimal` then? Because it’s a lot slower!

Here is a benchmark:

```Calculating -------------------------------------
bigdecimal    21.559k i/100ms
float    79.336k i/100ms
-------------------------------------------------
bigdecimal    311.721k (± 7.4%) i/s -      1.552M
float      3.817M (±11.7%) i/s -     18.803M

Comparison:
float:  3817207.2 i/s
bigdecimal:   311721.2 i/s - 12.25x slower
```

`BigDecimal` is 12 times slower than `Float`, and that’s why it’s not the default 🙂

## Fixnum vs Bignum

In this section, I want to explore the differences between `Fixnum` and `Bignum` before Ruby 2.4.

```1.class
# Fixnum

100000000000.class
# Bignum
```

Ruby creates the correct class for us, and it will automatically promote a `Fixnum` to a `Bignum` when necessary.

Note: You may need a bigger number to get a `Bignum` object if you have a 64-bit Ruby interpreter.

Why do we need different classes? The answer is that to work with bigger numbers you need a different implementation, and working with big numbers is slower, so we end up with a similar situation to `Float` vs `BigDecimal`.

## Special Attributes of Fixnums

The `Fixnum` class also has some special properties. For example, the object id is calculated using a formula.

```1.object_id
# 3
20.object_id
# 41
```

The formula is: `(number * 2) + 1`.

But there is more to this, when you use a `Fixnum` there is no object being created at all. There is no data to store in a `Fixnum`, because the value is derived from the object id itself.

This is just an implementation detail, but I think it’s interesting to know 🙂

MRI (Matz’s Ruby Interpreter) uses these two macros to convert between value & object id:

```INT2FIX(i)  ((VALUE)(((SIGNED_VALUE)(i))<<1 | FIXNUM_FLAG))
FIX2LONG(x) ((long)RSHIFT((SIGNED_VALUE)(x),1))
```

What happens here is called “bit shifting”, which moves all the bits to the left or the right.

Shifting one position to the left is equivalent to multiplying by 2 & that’s why the formula is `(number * 2) + 1`. The +1 comes from the `FIXNUM_FLAG`.

In contrast, `Bignum` works more like a normal class & uses normal object ids:

```111111111111111.object_id
# 23885808
```

All this means is that `Fixnum` objects are closer to symbols in terms of how they work at the interpreter level, while `Bignum` objects are closer to strings.

## Integers In 2.4

Since Ruby 2.4 Fixnum & Bignum are deprecated, but behind the scenes they still work the same way.

Ruby switches from one type to another automatically.

Without changing the class.

This means that small `Integer` numbers still operate in the same way as a `Fixnum`.

## Summary

In this post, you learned about the different number-related classes that exist in Ruby.

You learned that floats are imprecise & that you can use `BigDecimal` if accuracy is a lot more important than performance. You also learned that `Fixnum` objects are special at the interpreter level, but `Bignum`s are just regular objects.

If you found this post interesting don’t forget to sign-up to my newsletter in the form below 🙂

Iain Elder says 4 years ago

Thanks for the intro to numbers in Ruby! How did you produce the benchmark output?

Jesus Castello says 4 years ago

I used the `benchmark-ips` gem:

```require 'benchmark/ips'
require 'bigdecimal'

Benchmark.ips do |x|
x.report("bigdecimal") { BigDecimal("0.2") + BigDecimal("0.1") }
x.report("float") { 0.2 + 0.1 }

x.compare!
end
```