How to subtract an isize from a usize?
Asked Answered
F

3

12

I've got a usize that does hit very large values. I also apply a delta to it that I receive in the form of an isize. What's the best way to apply the delta without losing any precision?

fn main() {
    let mut big_indexer: usize = 4295032832; // 2^32 + 2^16
    let delta: isize = -65792; // 2^16 + 2^8

    let big_indexer = (big_indexer as isize) + delta // Can't do this b/c overflow
    let big_indexer = big_indexer + (delta as usize) // Can't do this b/c lose negative number

    // This is ugly
    if delta < 0 {
        let big_indexer -= delta.abs() as usize;
    } else {
        let big_indexer += delta.abs() as usize;
    }
}
Follmer answered 6/12, 2019 at 7:2 Comment(1)
delta.abs() may panic when delta is isize::MIN.Cruces
M
6

There are two ways:

  • either you keep all your value in range of isize (the choice of rust std for example)
  • or you only work with usize and handle sign yourself (clearly my preferred choice).

But the implementation is up to you; for example, you could have a bool that tells you if the offset is a difference or an addition, or use an enum:

fn foo(n: usize, offset: usize, sub: bool) -> Option<usize> {
    (if sub {
        usize::checked_sub
    } else {
        usize::checked_add
    })(n, offset)
}

enum OffSet {
    Neg(usize),
    Pos(usize),
}

fn bar(n: usize, offset: OffSet) -> Option<usize> {
    match offset {
        OffSet::Pos(offset) => n.checked_add(offset),
        OffSet::Neg(offset) => n.checked_sub(offset),
    }
}

fn main() {
    let n = 4295032832; // 2^32 + 2^16
    let offset = 65792; // 2^16 + 2^8
    let sub = true;
    assert_eq!(Some(n - offset), foo(n, offset, sub));
    assert_eq!(Some(n - offset), bar(n, OffSet::Neg(offset)));
}

This is not ugly at all; you just have to use some trait to hide the logic and then you just have to use it.

Mycenaean answered 6/12, 2019 at 8:39 Comment(5)
You could create an Offset from an isizeClotilda
@FrenchBoiethios the point is to get rid of isize, also use isize lead to overflow problem when value is isize::min_value, I have a strong opinion on that, life is better without isizeMycenaean
play.integer32.com/…Clotilda
@FrenchBoiethios yes but you run into another problem what to do when your offset is bigger or less then isize limit ?Mycenaean
What do you mean? i was talking about isize -> Offset conversion.Clotilda
F
6

Rust 1.66 (stable release on December 15, 2022)

As of Rust 1.66 the standard library now has checked_add_signed, overflowing_add_signed, saturating_add_signed, and wrapping_add_signed.

Rust 1.13 (September 10, 2020)*

The easiest way is to create your own add_signed function or trait. Here is what that function would look like. This implementation should give optimal performance in release mode while also properly handling overflow checks (if enabled). When compiled by rustc in release mode (overflow checks disabled) this function produced the same assembly as a.wrapping_add(b as usize).

#[track_caller]
#[inline(always)]
pub const fn add_signed(a: usize, b: isize) -> usize {
    match b {
        x if x < 0 => a - x.wrapping_abs() as usize,
        x => a + x as usize
    }
}

* To become fully compatible with Rust 1.13, you must remove #[track_caller] and convert it to a non-const function.

Fluted answered 29/12, 2022 at 3:18 Comment(0)
C
4

Stargateur's answer is fine advice in general, but let's suppose you can't just rewrite the API to eliminate isizes, or limit the range of your usizes. In such a situation you may cast delta to usize and explicitly use wrapping arithmetic:

// DON'T COPY THIS LINE unless you read the caveat below first
big_indexer = big_indexer.wrapping_add(delta as usize);

This works for the example in the question, but it has a "big" caveat: if both delta and big_indexer are positive and their sum would overflow, it wraps instead. If the numbers you're using are guaranteed to be in range, that's fine. If you need to add an isize to a usize and detect overflow, you're back to an "ugly" if expression.

fn add_offset(big_indexer: usize, delta: isize) -> Option<usize> {
    if delta < 0 {
        big_indexer.checked_sub(delta.wrapping_abs() as usize)
    } else {
        big_indexer.checked_add(delta as usize)
    }
}

If you use wrapping arithmetic a lot, you may want to use the std::num::Wrapping struct to make it more convenient.

Cruces answered 6/12, 2019 at 18:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.