Morgan H
Morgan H

Reputation: 82

How do I add a signed integer to an unsigned integer in Rust, checking for unsigned overflow?

I want to add an isize to a usize and include bounds checking so that the result does not overflow the bounds of a usize. How can this be done?

Upvotes: 3

Views: 1359

Answers (3)

Zoey Hewll
Zoey Hewll

Reputation: 5385

Rustc 1.66+ supports some mixed integer operations. Note, this was previously unstable under the mixed_integer_ops feature flag.

With this you can do:

fn main() {
    let x: usize = foo();
    let y: isize = bar();
    let (res, flag): (usize, bool) = x.overflowing_add_signed(y); // returns a (usize, bool) pair to indicate if overflow occurred
    println!("{x} + {y} = {res}, overflowed? {flag}");
}

These *_add_signed methods on unsigned integers all accept a signed argument and return an unsigned result. There's a few variants, depending on how specifically you want to handle the overflow case:

  • overflowing_, shown above, returns a pair, with the wrapped result and a boolean flag indicating if overflow occurred.
  • checked_ returns an Option, which is None on overflow.
  • saturating_ prevents overflow by clamping to the max and min values (i.e. MAX + 1 == MAX, MIN - 1 == MIN).
  • wrapping_ doesn't address your use case - it only returns the wrapped result and doesn't indicate if overflow occurred.

There are also analogous methods on signed integers, for additions or subtractions which accept an unsigned argument and return a signed result, named *_add_unsigned and *_sub_unsigned.

(note: adapted from my answer at https://stackoverflow.com/a/73153135/6112457)

Upvotes: 1

vallentin
vallentin

Reputation: 26157

You could use a combination of isize::is_negative(), isize::wrapping_abs(), usize::checked_add() and usize::checked_sub().

const fn add(lhs: usize, rhs: isize) -> Option<usize> {
    if rhs.is_negative() {
        lhs.checked_sub(rhs.wrapping_abs() as usize)
    } else {
        lhs.checked_add(rhs as usize)
    }
}

Why isize::is_negative() vs rhs < 0? In this case, it doesn't change anything, as logical operations on literals count as constant expressions.

However, while it is allowed for literals it is not allowed in general, as traits methods cannot be const. So if you had a wrapping type, e.g. Foo(isize) then it wouldn't be allowed to say foo < Foo(0) in a const context. Though, it would be possible to say foo.is_negative() as Foo could still implement a const fn is_negative().

Yes, you'd still be able to say foo.0 < 0, but that's besides the point I was trying to make.

Upvotes: 5

Morgan H
Morgan H

Reputation: 82

One way to do this would be with a function like the following:

fn signed_unsigned_add(x: usize, y: isize) -> usize {
    let (n, overflow) = x.overflowing_add(y as usize);
    if (y >= 0) ^ overflow {
        n
    } else {
        panic!(
            "signed + unsigned addition overflow: {} + {}",
            x,
            y,
        )
    }
}

This takes advantage of the fact that such an addition, when looked at in terms of unsigned numbers, should only "overflow" (i.e. exhibit two's-complement behaviour) when the unsigned number is negative, and if it doesn't do so when the number is negative, this indicates underflow.

Upvotes: 0

Related Questions