DEV Community

Jane for Mastering Backend

Posted on • Originally published at masteringbackend.com on

Python Devs Beware: The % Operator Lies in Rust.

title

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
Enter fullscreen mode Exit fullscreen mode

Rust code snippet

fn main() { 
    println!("{}", 5 % 3); // 2 
    println!("{}", -5 % 3); // -2 
    println!("{}", 5 % -3); // 2 
    println!("{}", -5 % -3); // -2 
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 
}
Enter fullscreen mode Exit fullscreen mode

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) 
 }
Enter fullscreen mode Exit fullscreen mode

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 Cheat sheet

  • 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!!

Have a great one!!!

Author: by Ugochukwu Chizaram


Thank you for being a part of the community

Before you go:

Whenever you’re ready

There are 4 ways we can help you become a great backend engineer:

  • The MB Platform: Join thousands of backend engineers learning backend engineering. Build real-world backend projects, learn from expert-vetted courses and roadmaps, track your learnings and set schedules, and solve backend engineering tasks, exercises, and challenges.
  • The MB Academy: The “MB Academy” is a 6-month intensive Advanced Backend Engineering Boot Camp to produce great backend engineers.
  • Join Backend Weekly: If you like posts like this, you will absolutely enjoy our exclusive weekly newsletter, sharing exclusive backend engineering resources to help you become a great Backend Engineer.
  • Get Backend Jobs: Find over 2,000+ Tailored International Remote Backend Jobs or Reach 50,000+ backend engineers on the #1 Backend Engineering Job Board.

Originally published at https://masteringbackend.com on September 2, 2025.


Top comments (0)