Skip to content
Select theme

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.

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.

-3 -1
-3 1
3 -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:

-4 1
-3 1

rem_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.

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:

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 error

A divisor that arrives from data at run time gets past that check, and the program panics:

src/main.rs
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 zero
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The 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:

src/main.rs
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 overflow
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Unlike + 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):

Some(3)
None
Some(0)