We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ground Rules: Values, Types, Control Flow step 7 of 24
Integer division and remainder: the signs will surprise you
A twelve-hour clock face, numbered 0 through 11. The hand starts at
start, and each step moves it that many hours — forwards for positive,
backwards for negative. Report the position after each step.
pub fn clock_positions(start: i32, steps: &[i32]) -> Vec<i32>
Every value you return must be in 0..=11, including the very first one:
start may itself be out of range or negative.
(0, [-1]) gives [11]. (11, [1]) gives [0]. (3, [-15]) gives [0].
The starter compiles, passes clippy, and is wrong on every negative input.
% is a remainder, not a modulus
Ask Python for -7 % 2 and you get 1. Ask Rust and you get -1. Neither is
buggy; they have made different choices about what % means, and Rust’s choice
is the one C, C++, Java, Go and C# also make.
The rule Rust follows is truncated division: / rounds toward zero, and %
takes whatever sign the left operand had, so that the identity
(a / b) * b + (a % b) == a holds.
Verified on 1.95:
7 / 2 == 3 7 % 2 == 1
-7 / 2 == -3 -7 % 2 == -1
7 / -2 == -3 7 % -2 == 1
-7 / -2 == 3 -7 % -2 == -1
Python instead uses floored division, which is why its % follows the sign of
the right operand. If you arrived from Python, this is a real trap: your
wrap-around code, your circular buffers, your hash bucketing and your
“every N-th item” logic will all be silently wrong for negative inputs, and
your tests will pass because nobody tested a negative.
rem_euclid is the fix almost nobody finds
The standard library has the operation you actually wanted:
(-7i32).rem_euclid(2) == 1
rem_euclid implements Euclidean division, whose defining property is that the
remainder is never negative — it is always in 0..b.abs(). That is exactly
the contract “give me a position on a clock face”. Its partner div_euclid
gives the matching quotient.
It is a plain inherent method on every integer type, it costs about the same as
%, and it has been stable since Rust 1.38. There is no lint that will point
you at it, which is precisely why it is worth learning deliberately.
The other surprise: division can still panic
Two integer operations panic even at -O, because there is no bit pattern
that could be the answer:
-
x / 0andx % 0— division by zero; -
i32::MIN / -1andi32::MIN % -1— the true quotient is2147483648, which is one pasti32::MAX.
If either operand comes from outside your function, prefer checked_div and
checked_rem, which return Option and hand the decision back to you. This is
the same lesson as the previous item, applied to the one arithmetic operation
that cannot be made total.
Working it out
Add the step, then reduce into 0..=11. Do the reduction on the starting
position too, so that a wild start is normalised before anything else
happens — (-5, [0]) must give [7].
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.