# Behind The Scenes: How Numbers Work in Ruby

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

And 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
Fixnum Normal numbers that fit into the OS integer type 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

You may have noticed that in the description for the `Float` class it says “imprecise”, what’s the meaning of that?

Let me show you with 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
```

And that’s what I mean by imprecision! The reason this happens is because of the way that a float is stored. If you need decimal numbers that are always accurate you can use the `BigDecimal` class.

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

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

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.

## Summary

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

You learned that floats are imprecise, and that you can use `BigDecimal` if accuracy is a lot more important than performance. And after that you 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 last year

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

Jesus Castello says last year

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
```