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¶
- 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 rebuildsaasq * b + r. Which of the four remainders change sign when onlybdoes? - Write a
c_div(a, b)that divides the way C does — truncating towards zero — without callingfdivortruncate: start froma / band correct it by one when the signs differ anda % bis not zero. Check it againsta.fdiv(b).truncateon the same four pairs, then compare it withInteger#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.
See also¶
0.1 + 0.2 != 0.3, and2.5.roundis3— what the3.5fromfdivcan and cannot hold, and how Ruby rounds a half1/3randBigDecimal("0.1")are exact where Float is not —quoand1/3r: the exact division that neither/norfdivgives1 + 1.5works because Integer asks Float tocoerce— how7 / 2.0decides to answer in Float- Gotchas for Python programmers —
7 / 2is3sits in the table of traps beside0being truthy - Comparing an
intwith afloat↗ — the Python library on what/answering in float costs once the integer is big - Big integers ↗ — the Rust library's three ways to divide a negative number, in a language whose
/truncates - Common error messages — Twenty-nine errors you will meet in your first month of Ruby, each with the code that raises it, the…
- Twenty-three Python traps for a Ruby programmer — The twenty-three places where a Ruby reflex gives the wrong answer in Python