Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust signed modulo unsigned -> unsigned

In (stable) Rust, is there a relatively straightforward method of implementing the following function?

fn mod_euclid(val: i128, modulo: u128) -> u128;

Note the types! That is, 'standard' euclidean modulus (result is always in the range of [0, mod)), avoiding spurious overflow/underflow in the intermediate calculation. Some test cases:

// don't-care, just no panic or UB.
// Mild preference for treating this as though it was mod=1<<128 instead of 0.
assert_dc!(mod_euclid(i128::MAX,         0)); 
assert_dc!(mod_euclid(        0,         0)); 
assert_dc!(mod_euclid(i128::MIN,         0)); 

assert_eq!(mod_euclid(        1,        10),                  1);
assert_eq!(mod_euclid(       -1,        10),                  9);
assert_eq!(mod_euclid(       11,        10),                  1);
assert_eq!(mod_euclid(      -11,        10),                  9);
assert_eq!(mod_euclid(i128::MAX,         1),                  0);
assert_eq!(mod_euclid(        0,         1),                  0);
assert_eq!(mod_euclid(i128::MIN,         1),                  0);
assert_eq!(mod_euclid(i128::MAX, u128::MAX),  i128::MAX as u128);
assert_eq!(mod_euclid(        0, u128::MAX),                  0);
assert_eq!(mod_euclid(i128::MIN, u128::MAX),  i128::MAX as u128);

For signed%signed->signed, or unsigned%unsigned->unsigned, this is relatively straightforward. However, I can't find a good way of calculating signed % unsigned -> unsigned without converting one of the arguments - and as the last example illustrates, this may overflow or underflow no matter which direction you choose.

like image 843
TLW Avatar asked Sep 15 '26 17:09

TLW


2 Answers

As far as I can tell, there is no such function in the standard library, but it's not very difficult to write one yourself:

fn mod_euclid(a: i128, b: u128) -> u128 {
    if a >= 0 {
        (a as u128) % b
    } else {
        let r = (!a as u128) % b;
        b - r - 1
    }
}

Playground link

How it works:

  • If a is non-negative then it's straightforward - just use the unsigned remainder operator.
  • Otherwise, the bitwise complement !a is non-negative (because the sign bit is flipped), and numerically equal to -a - 1. This means r is equivalent to b - a - 1 modulo b, and hence b - r - 1 is equivalent to a modulo b. Conveniently, b - r - 1 is in the expected range 0..b.
like image 171
kaya3 Avatar answered Sep 19 '26 21:09

kaya3


Maybe a little bit more straight forward, use rem_euclid where possible and else return the positive value equivalent to a:

pub fn mod_euclid(a: i128, b: u128) -> u128 {
    const UPPER: u128 = i128::MAX as u128;
    match b {
        1..=UPPER => a.rem_euclid(b as i128) as u128,
        _ if a >= 0 => a as u128,
        // turn a from two's complement negative into it's
        // equivalent positive value by adding u128::MAX
        // essentialy calculating u128::MAX - |a|
        _ => u128::MAX.wrapping_add_signed(a),
        //_ => a as u128 - (a < 0) as u128,
    }
}

(The parser didn't like my casting in the match hence UPPER)

Playground

Results in a little fewer instructions & jumps on x86_64 as well.

like image 33
cafce25 Avatar answered Sep 19 '26 19:09

cafce25



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!