Looking at these two code samples below, they look the same, right? What could possibly go wrong?
Python code snippet
print(5 % 3) # 2
print(-5 % 3) # 1
print(5 % -3) # -1
print(-5 % -3) # -2
Rust code snippet
fn main() {
println!("{}", 5 % 3); // 2
println!("{}", -5 % 3); // -2
println!("{}", 5 % -3); // 2
println!("{}", -5 % -3); // -2
}
Well, everything. You see, the Rust code is fooling you, because Rust does not handle the %
operator the way Python does. In fact, it doesn’t even calculate the modulo in the sense you might expect.
In simple terms, Python handles %
as a true modulo operator, where the result always follows the divisor’s sign. Rust, however, treats %
as the remainder operator, where the result always follows the dividend’s sign.
In this article, we’ll explore the differences, why Rust does it the way it does, and the correct way to get a true modulo in Rust while preserving signs.
The math behind it
The modulo operator as you know it is just a native implementation of a simple arithmetic theory in code instructions.
Under the hood, in Python the modulo operator could be defined as:
def euclidean_mod(a: int, b: int) -> int:
q = a // b # floor division
r = a - b * q
return r
Whereas in Rust, the %
operator follows a different rule:
fn remainder(a: i32, b: i32) -> i32 {
let q = a / b; // truncation division
let r = a - b * q;
r
}
The core difference is the type of division formula they use. Modulo is always “the remainder,” but the way you pick the quotient changes the remainder’s sign.
In Rust, by default, we use truncated division. That means the quotient is chopped toward zero, and the remainder follows the sign of the dividend.
In Python, the % operator uses Euclidean division. That means the quotient is floored instead of truncated, and this tiny shift ensures the remainder is always non-negative when the divisor is positive.
You see, before we even dive into the numbers, the key is simple: both calculate remainders correctly — but Python goes one step further. It “corrects” the result by flooring the quotient, which guarantees the remainder falls into the clean range:
0≤r<∣b∣0 \leq r < |b|0≤r<∣b∣
It’s a small detail, but it completely changes how %
behaves with negatives.
But why the difference
Well, it is just a conceptual choice. Rust comes from the same world as C, Go, and Java — where % is treated as a remainder operator that simply truncates the division and leaves the remainder “as is.” This makes sense in low-level systems programming because it’s fast, predictable, and lines up with how hardware division usually works.
Python on the other hand tries to stay closer to mathematics. In math, Euclidean division is what makes sense for modular arithmetic — the remainder always lands neatly in the range 0 ≤ r < |b|
. That’s why in Python %
feels more “intuitive” for things like clock math, cycles, and array indexing.
So, neither language is “wrong.” They just made different design decisions. Rust chose performance and tradition. Python chose mathematical neatness.
Fixing this problem in Rust
Now here’s the good news: Rust actually gives you the Python behavior too — you just have to call it by name. It’s called .rem_euclid()
.
Example:
fn main() {
println!("{}", -5 % 3); // -2 (truncated)
println!("{}", -5_i32.rem_euclid(3)); // 1 (Euclidean, same as Python)
}
So what is .rem_euclid()
really? It’s not some magic. It’s simply another method defined in
Rust’s RemEuclid
trait — which is implemented for signed integers (and unsigned too). That’s why you can just call it directly on any integer type (i32
, i64
, etc.).
This lets you pick exactly how you want division to behave: %
for truncated remainder, or .rem_euclid()
for Euclidean.
Summary and Cheatsheet
In Rust,
%
uses truncation. This means remainders can be negative.If you don’t want that, use
.rem_euclid()
instead.For simple cases where your dividend is always positive,
%
is fine.Be aware:
.rem_euclid()
can panic if you pass in a zero divisor (same as%)
.
In Python, %
is always Euclidean — it floors the quotient so that the remainder behaves nicely without you worrying about it.
Note : If you are using
%
in a Rust codebase where negative numbers might appear — please review your code. Leaving it as is can give you very big problems later.
If you learned something new here or have any questions, feel free to message me on my linkedin.
See you next week!!