Skip to content

15 — Numbers

One line: Ruby's numbers are Python's numbers with different defaults — / floors, round goes away from zero, 1.0 is not the Hash key 1, a Float divided by zero is Infinity — plus Rational and BigDecimal literals and a coerce protocol for classes of your own.

The types line up one to one: Integer is int (unbounded in both), Float is float (the same 64-bit double), Rational is Fraction, BigDecimal is Decimal, Complex is complex, and Random is random.Random — both Mersenne Twisters. A Python programmer's model of what a number is carries over completely, and every page here prints the same rows from both languages to show it: 0.1 + 0.2 misses 0.3 by the same 5.55e-17, 2 ** 53 + 1 fails the same exact comparison with its own Float, and pow(3, 200, 1000) is 1 in both.

What does not carry over is the defaults. Ruby's one / floors on Integers where Python needed a second operator; 2.5.round is 3 and round(2.5) is 2; format("%.2f", 2.675) is 2.68 and format(2.675, ".2f") is 2.67; 7.0 / 0 is Infinity and so is 2.0 ** 1024, where Python raises for both; {1 => :a}[1.0] is nil because 1.eql?(1.0) is false, where a Python dict finds the key; 2 ** -1 is a Rational, not 0.5; and Python's str(10 ** 5000) raises under a 4300-digit guard that Ruby does not have. Each of those is one row in one lesson below, and each lesson ends by putting the two outputs side by side.

Lesson Level The one thing
7 / 2 is 3: integer division floors 101 Ruby's / is Python's // on Integers; % follows the divisor and remainder the dividend; a Float over zero is Infinity, not an error
Integers never overflow: 2 ** 200 is just an Integer 101 one Integer class of any size, with bit_length, digits, Integer.sqrt and modular pow; only a Float loses digits, and only Python caps str(int)
0.1 + 0.2 != 0.3, and 2.5.round is 3 201 the same double as Python's, but a half rounds away from zero, overflow is Infinity, and max with a NaN raises
1/3r and BigDecimal("0.1") are exact where Float is not 201 Rational and BigDecimal make 0.1 + 0.2 == 0.3 true; Fraction and Decimal are their twins, except that Python refuses to mix a Decimal with a float
1 == 1.0 is true, but {1 => :a}[1.0] is nil 201 == crosses the Integer–Float line exactly; eql? and hash do not, so a Hash, uniq and a Set see two keys where Python sees one
1 + 1.5 works because Integer asks Float to coerce 201 coerce is Ruby's __radd__ and NotImplemented; to_str and to_int are the implicit conversions that arithmetic never uses
Random.new(42) gives the same numbers every run 201 a seeded generator is repeatable on every machine, shuffle and sample take it as random:, and SecureRandom is the one you cannot seed
Numbers become text with format, to_s(16) and % 201 format has .. two's-complement negatives and no thousands flag; Integer() is strict where to_i never fails; Float#to_s is the shortest round trip

Read more