简体   繁体   English

Rust signed modulo unsigned -> unsigned

[英]Rust signed modulo unsigned -> unsigned

In (stable) Rust, is there a relatively straightforward method of implementing the following function?在(稳定的)Rust 中,是否有相对直接的方法来实现以下功能?

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 .注意类型,即“标准”欧氏模数(结果始终在[0, mod)范围内),避免中间计算中的虚假溢出/下溢 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.对于signed%signed->signed,或者unsigned%unsigned->unsigned,这个比较直接。 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.但是,我找不到一种不转换其中一个参数就计算 signed % unsigned -> unsigned 的好方法——正如最后一个示例所示,无论您选择哪个方向,这都可能溢出或下溢。

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.如果a是非负的,那么它很简单——只需使用无符号余数运算符。
  • Otherwise, the bitwise complement !a is non-negative (because the sign bit is flipped), and numerically equal to -a - 1 .否则,按位补码!a是非负的(因为符号位被翻转),并且在数值上等于-a - 1 This means r is equivalent to b - a - 1 modulo b , and hence b - r - 1 is equivalent to a modulo b .这意味着r等价于b - a - 1b ,因此b - r - 1等价于ab Conveniently, b - r - 1 is in the expected range 0..b .方便地, b - r - 1在预期范围内0..b

Maybe a little bit more straight forward, use rem_euclid where possible and return if a >= 0 {a} else {u128::MAX + a} else:也许更直接一点,尽可能使用rem_euclid并返回if a >= 0 {a} else {u128::MAX + a} else:

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,
        _ => a as u128 - (a < 0) as u128,
    }
}

(The parser didn't like my casting in the match hence UPPER ) (解析器不喜欢我在比赛中的铸造,因此UPPER

Playground 操场

Results in a little fewer instructions & jumps on x86_64 as well.在 x86_64 上也会导致更少的指令和跳转

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM