Skip to content

7 / 2 is 3: integer division floors

Level: 101 · a Python programmer's first week in Ruby

One line: Ruby's / on two Integers is Python's // — it floors and stays an Integer — so 7 / 2 is 3 and -7 / 2 is -4; a Float answer needs fdiv or a Float operand, and only an Integer divided by zero raises.

Ruby has one division operator and lets the operands pick its meaning: two Integers give the floored Integer quotient, and a Float on either side gives a Float. Python 3 split the two meanings into two operators — / always answers in float and // floors — so a Python programmer who types total / count in Ruby gets a floored count where they expected an average. The rest of the family follows the same rule in both languages: % and divmod floor and take the divisor's sign, remainder truncates and takes the dividend's sign like C, and div floors to an Integer even from a Float. Where the two languages part is zero: a Float divided by zero is Infinity or NaN in Ruby, while in Python every division by zero raises.

Verified output of integer_division_floors_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

 1. 7 / 2              -> 3                                Integer / Integer stays an Integer
 2. -7 / 2             -> -4                               it floors: towards minus infinity, not towards zero
 3. 7.fdiv(2)          -> 3.5                              fdiv asks for a Float; 7.quo(2) is (7/2), a Rational
 4. 7 / 2.0            -> 3.5                              one Float operand makes the answer a Float
 5. 7.divmod(2)        -> [3, 1]                           quotient and modulo as a pair
 6. -7.divmod(2)       -> [-4, 1]                          the pair floors too: -4 * 2 + 1 == -7
 7. -7 % 2             -> 1                                % takes the sign of the divisor
 8. 7 % -2             -> -1                               so a negative divisor gives a negative result
 9. -7.remainder(2)    -> -1                               remainder takes the sign of the dividend, like C's %
10. 7.0.div(2)         -> 3                                div floors and returns an Integer, even for Floats
11. 7.ceildiv(2)       -> 4                                the quotient rounded up (Ruby 3.2+)
12. 7.0 / 0            -> Infinity                         Float division by zero is Infinity, not an error
13. 0.0 / 0            -> NaN                              NaN; (0.0 / 0).nan? is true
14. 7 / 0              -> ZeroDivisionError: divided by 0  Integer division by zero raises
15. 7.0 % 0            -> ZeroDivisionError: divided by 0  so does modulo, even on a Float
16. (7.0 / 0).to_i     -> FloatDomainError: Infinity       Infinity has no Integer value

Reading the output

Rows 1–4 are the operator choosing its meaning from its operands. 7 / 2 is 3, and -7 / 2 is -4, not -3: the quotient is floored towards minus infinity, which is what Python's // does and what C's / does not. To get 3.5 you either call fdiv (row 3) or put a Float on one side (row 4); quo gives the exact answer, the Rational (7/2).

Rows 5–9 are the remainders. divmod returns quotient and modulo as a pair, and -7.divmod(2) is [-4, 1] because -4 * 2 + 1 is -7. % takes the sign of its divisor (rows 7 and 8), so -7 % 2 is 1 and 7 % -2 is -1; remainder takes the sign of the dividend (row 9), which is what % does in C and Java, and it pairs with the truncated quotient rather than the floored one — the kata below checks both identities. Rows 10 and 11 are the two named divisions: div floors and always returns an Integer, even from a Float receiver, and ceildiv rounds the quotient up.

Rows 12–16 are zero. A Float divided by zero follows IEEE 754 — Infinity, or NaN for 0.0 / 0 — and nothing raises. An Integer divided by zero raises ZeroDivisionError (row 14), and so does % by zero even with a Float receiver (row 15). Row 16 is the trap on the way back: to_i on Infinity or NaN raises FloatDomainError, so a Float that went through a zero cannot quietly become an Integer.

Compared with Python

Python 3 gave the two meanings two operators: / always answers in float, even for 4 / 2, and // floors. So Ruby's 7 / 2 is Python's 7 // 2, and Ruby's 7.fdiv(2) is Python's plain 7 / 2. From row 5 on the two languages agree on the floored family — divmod, and % with the divisor's sign — and Python's truncating counterpart to remainder is math.fmod, which answers in float. // on a float floors but keeps the float type (7.0 // 2 is 3.0), where Ruby's div returns an Integer. The real divergence is zero: Python raises ZeroDivisionError for every division by zero, float or int, and infinity and NaN exist only as math.inf and math.nan, never as the result of an arithmetic operator. The twin prints the exception's type only, because CPython reworded these messages between 3.12 and 3.14 — integer division or modulo by zero became division by zero. (Not machine-checked here.)

Verified output of integer_division_floors_py.py — regenerated by tools/run_examples.py, never hand-typed.

 1. 7 / 2              -> 3.5                              / always makes a float, even 4 / 2 -> 2.0; 7 // 2 is 3
 2. -7 // 2            -> -4                               // floors, like Ruby's /
 3. 7 / 2              -> 3.5                              the float answer needs no fdiv; Fraction(7, 2) is Fraction(7, 2)
 4. 7 // 2.0           -> 3.0                              // on a float floors but stays a float; 7 / 2.0 is 3.5
 5. divmod(7, 2)       -> (3, 1)                           quotient and modulo as a tuple
 6. divmod(-7, 2)      -> (-4, 1)                          the pair floors too: -4 * 2 + 1 == -7
 7. -7 % 2             -> 1                                % takes the sign of the divisor
 8. 7 % -2             -> -1                               so a negative divisor gives a negative result
 9. math.fmod(-7, 2)   -> -1.0                             fmod takes the sign of the dividend, and answers in float
10. 7.0 // 2           -> 3.0                              floors but stays a float; math.floor(7.0 / 2) is 3
11. -(-7 // 2)         -> 4                                the ceiling-division idiom; math.ceil(7 / 2) is 4
12. 7.0 / 0            -> ZeroDivisionError                float division by zero raises too; math.inf is inf
13. 0.0 / 0            -> ZeroDivisionError                no NaN from division; math.nan == math.nan is False
14. 7 // 0             -> ZeroDivisionError                integer division by zero raises
15. 7.0 % 0            -> ZeroDivisionError                so does modulo
16. int(math.inf)      -> OverflowError                    infinity has no int value; int(math.nan) is ValueError

The Python library's Comparing an int with a float ↗ is where / answering in float starts to cost something: the moment the integer is too big for a double.

Try it

  1. For every pair (a, b) in [7, -7] × [2, -2], print the floored pair (a / b, a % b) and the truncated pair (a.fdiv(b).truncate, a.remainder(b)), and check that each pair rebuilds a as q * b + r. Which of the four remainders change sign when only b does?
  2. Write a c_div(a, b) that divides the way C does — truncating towards zero — without calling fdiv or truncate: start from a / b and correct it by one when the signs differ and a % b is not zero. Check it against a.fdiv(b).truncate on the same four pairs, then compare it with Integer#ceildiv.
Solution to 1

integer_division_floors_kata_rb.rb in full — pasted here by tools/run_examples.py from the file CI runs.

# Kata: for every (a, b) in [7, -7] x [2, -2], print the floored pair
# (/ and %) and the truncated pair (fdiv.truncate and remainder), and check
# the identity each pair satisfies: a == q * b + r.

puts format("%-3s %-3s | %-6s %-6s | %-6s %-9s | %s",
            "a", "b", "a / b", "a % b", "trunc", "remainder", "both identities hold?")
[7, -7].product([2, -2]).each do |a, b|
  floored   = a / b
  modulo    = a % b
  truncated = a.fdiv(b).truncate
  remainder = a.remainder(b)
  floor_ok  = (a == floored * b + modulo)
  trunc_ok  = (a == truncated * b + remainder)
  puts format("%-3d %-3d | %-6d %-6d | %-6d %-9d | %s",
              a, b, floored, modulo, truncated, remainder, floor_ok && trunc_ok)
end

Verified output of integer_division_floors_kata_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

a   b   | a / b  a % b  | trunc  remainder | both identities hold?
7   2   | 3      1      | 3      1         | true
7   -2  | -4     -1     | -3     1         | true
-7  2   | -4     1      | -3     -1        | true
-7  -2  | 3      -1     | 3      -1        | true

See also