Why % works differently between rust and python

in python

-1 % 200  ===> 199 

but in rust

-1 % 200  ===> -1 

???

and how can i get the same result with python? :slight_smile:

It's possible to use rem_euclid method of integer types (which computes least nonnegative remainder) to have the same behavior as in Python, for example:

(-1i32).rem_euclid(200)

Will provide 199 as an answer.

Python does provide math.remainder which computes truncated remainder like % operator in Rust does, however this particular function takes floating point numbers, and so won't work well with large integers.

5 Likes

I cannot answer to “why” cuz I’m not some kinda Python expert, but I can tell you “how”. You can use PyO3 with maturin to build something like this:

use pyo3::prelude::*;

#[pyfunction]
fn rem(x: i32, y: i32) -> PyResult<i32> {
    Ok(x % y)
}

#[pymodule]
fn remainder(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(rem, m)?)?;
    Ok(())
}

then install your package / extract a pyd and use it like this

from remainder import rem

print(f'{rem(-1, 200)}')

This probably is not the best way and maybe there are some mistakes in my examples, but I hope you got the point :wink:

1 Like

thanks ! :slight_smile:

I did some research. Python’s % is actually a modulo operator, while Rust’s % is just a remainder. So, as @xfix said, you need rem_euclid method.

There are three common variants of this:

  1. Truncating division.
    a / b computes round_towards_0(a / b).
    a % b has the same sign as a.
    This is what Rust and C use.

  2. Flooring division.
    a / b computes floor(a / b).
    a % b has the same sign as b.
    This is what Python uses.

  3. Euclidean division.
    a / b = round_down(a) / b
    a % b is always nonnegative.
    This is what Rust uses with a.div_euclid(b), a.rem_euclid(b).

#3 has the nicest mathematical properties, #2 second-nicest, #1 is more-less unusable for negative numbers if you care about the remainder.

I think Rust just copied the #1 behavior from C. And C copied it from the hardware division instruction on many CPUs (idiv). I think this is a design mistake, because:

  • idiv is slow and fixing its output doesn't cost many cycles
  • often idiv doesn't have to be used

If you divide by 200, both % and rem_euclid use imul twice rather than idiv, with rem_euclid using a few additional instructions. godbolt

If you divide by 16, both use and, with rem_euclid using a few fewer instructions. The latter improved in Rust 1.65, probably thanks to this LLVM improvement.

6 Likes