How to divide integers and get the remainder
Short answer: / divides and % gives the remainder. Both work on every integer type, division truncates toward zero, and the remainder keeps the dividend’s sign — -7 % 2 is -1, not 1. That sign rule is the surprise most people hit; div_euclid and rem_euclid are the standard library’s answer for when the other convention is wanted.
From a fresh project — cargo new div-demo, then replace src/main.rs with each block below.
The two operators
Section titled “The two operators”fn main() { let slices = 17; let people = 5; println!("{} slices each, {} left over", slices / people, slices % people);}3 slices each, 2 left over/ between two integers is integer division: 17 / 5 is 3, not 3.4. There is no silent promotion to a float — float division is a different operation on different types. % is the remainder of that same division, and the two answers always add back up: (17 / 5) * 5 + 17 % 5 == 17.
The negative number that surprises you
Section titled “The negative number that surprises you”fn main() { println!("{} {}", -7 / 2, -7 % 2); println!("{} {}", 7 / -2, 7 % -2); println!("{} {}", -7 / -2, -7 % -2);}-3 -1-3 13 -1-7 / 2 is -3, not -4: division rounds toward zero, chopping off the fraction. Python’s // floors instead — -7 // 2 is -4 there — and that is the habit most people arrive with.
The remainder keeps the dividend’s sign, because % has to satisfy the same add-back equation: -7 / 2 is -3, and -3 * 2 + (-1) is -7, so -7 % 2 is -1. The middle row shows the mirror case: the dividend is positive, so the remainder is 1 even though the divisor is negative. A negative index slipping out of a % and crashing a slice or a lookup is this rule, not a bug.
When the other convention is wanted: div_euclid and rem_euclid
Section titled “When the other convention is wanted: div_euclid and rem_euclid”Euclidean division asks for a non-negative remainder — one in 0..|divisor| — no matter what the signs are:
fn main() { println!("{} {}", (-7i32).div_euclid(2), (-7i32).rem_euclid(2)); println!("{} {}", 7i32.div_euclid(-2), 7i32.rem_euclid(-2));}-4 1-3 1rem_euclid never answers below zero, which makes it the right tool for wrapping a number into a fixed range — an index into a buffer, an hour onto a clock face — where a negative result would be nonsense. The i32 suffix is not decoration: (-7) on its own has an ambiguous integer type, and the compiler refuses to pick one for a method call.
The two ways this panics
Section titled “The two ways this panics”Division has a failure mode the arithmetic around it lacks — a zero divisor — plus the one overflow only the extreme case can produce.
When the divisor is a constant, the compiler refuses to build at all:
fn main() { println!("{}", 10 / 0);}error: this operation will panic at runtime --> src/main.rs:2:20 |2 | println!("{}", 10 / 0); | ^^^^^^ attempt to divide `10_i32` by zero | = note: `#[deny(unconditional_panic)]` on by default
error: could not compile `div-demo` (bin "div-demo") due to 1 previous errorA divisor that arrives from data at run time gets past that check, and the program panics:
fn main() { let people: i32 = std::env::args().nth(1).unwrap().parse().unwrap(); println!("{}", 17 / people);}thread 'main' (365269) panicked at src/main.rs:3:20:attempt to divide by zeronote: run with `RUST_BACKTRACE=1` environment variable to display a backtraceThe number in parentheses is the thread id, and it differs on every run. The panic itself does not.
The other panic is i32::MIN / -1. The quotient would be 2147483648, which does not fit in an i32 — the one division that overflows:
fn main() { let n: i32 = std::env::args().nth(1).unwrap().parse().unwrap(); println!("{}", n / -1);}thread 'main' (365348) panicked at src/main.rs:3:20:attempt to divide with overflownote: run with `RUST_BACKTRACE=1` environment variable to display a backtraceUnlike + and *, which wrap in a release build (How to stop an integer from overflowing), this one panics in release too — same message, same line. Division never silently wraps.
If a zero divisor is a possibility the program should survive, checked_div hands back an Option instead of panicking — None both for the zero divisor and for the MIN / -1 overflow. checked_rem does the same for %, and its None cases are the same two: the remainder is computed through the division, so i32::MIN.checked_rem(-1) is None as well, not Some(0):
fn main() { println!("{:?}", 17i32.checked_div(5)); println!("{:?}", i32::MIN.checked_div(-1)); println!("{:?}", i32::MIN.checked_rem(-1));}Some(3)NoneSome(0)- How to format a number with thousands separators — the previous article in this chapter.
- Working with numbers — the rest of the chapter.