Published
Ruby uses a handful of classes to represent numbers. I had trouble remembering
the exact class hierarchy1, so I opened up irb
to inspect the ancestors
of each class. The hierarchy turned out to be simpler than I expected and
actually not that hard to remember once laid out:
BasicObject
Object
Numeric
Float
Rational
Complex
BigDecimal
Integer
Fixnum
Bignum
Update: Fixnum
and Bignum
have been unified into Integer
in Ruby 2.4 and
are planned to be deprecated in the future. See Unify Fixnum and Bignum into
Integer.
Everything starts with the Numeric
superclass, which
also includes the Comparable
module.
There are four basic types: Integer
, Float
, Rational
, and Complex
.
Each can be constructed in at least the following ways: with literals,
conversion methods defined on Object
and capitalized conversion
methods defined on Kernel
.
Floating point representation is only an approximation of real
numbers. In order to represent very large or very accurate
floating point numbers in Ruby, we have to resort to BigDecimal
.2 From
the documentation:
Decimal arithmetic is also useful for general calculation, because it provides the correct answers people expect–whereas normal binary floating point arithmetic often introduces subtle errors because of the conversion between base 10 and base 2.
Type | Literal | Object# | Kernel# |
---|---|---|---|
Integer | 73 |
to_i |
Integer() |
Rational | 1/3r |
to_r |
Rational() |
Complex | 2i |
to_c |
Complex() |
Float | 1.1 |
to_f |
Float() |
BigDecimal | — | — |
BigDecimal() 3
|
Fixnum
and Bignum
?Update: This chapter only applies to Ruby versions before 2.4. (#12005)
Ruby uses instances of these two classes to represent integers. The
interesting part is that we never actually create Integer
instances:
Behind the scenes, Ruby juggles between Fixnum
and Bignum
, depending on
the size of the number we want to reference.
You can verify this in irb:
The automatic conversion is also mentioned in the documentation for Fixnum
:
[Fixnum] holds Integer values that can be represented in a native machine word (minus 1 bit). If any operation on a Fixnum exceeds this range, the value is automatically converted to a Bignum. Fixnum objects have immediate value. This means that when they are assigned or passed as parameters, the actual object is passed, rather than a reference to that object. Assignment does not alias Fixnum objects. There is effectively only one Fixnum object instance for any given integer value, so, for example, you cannot add a singleton method to a Fixnum. Any attempt to add a singleton method to a Fixnum object will raise a TypeError.
Unsurprisingly, the documentation for Bignum
is similar:
Bignum objects hold integers outside the range of Fixnum. Bignum objects are created automatically when integer calculations would otherwise overflow a Fixnum. When a calculation involving Bignum objects returns a result that will fit in a Fixnum, the result is automatically converted. For the purposes of the bitwise operations and [], a Bignum is treated as if it were an infinite-length bitstring with 2’s complement representation. While Fixnum values are immediate, Bignum objects are not—assignment and parameter passing work with references to objects, not the objects themselves.
Wait, what was that last bit? Fixnum
and Bignum
seem to behave differently when it comes to memory allocation. Let’s try to confirm this:
The object ids differ, showing that the two variables point to different objects. Fixnum instances show the opposite behaviour: