Panicked at 'attempt to subtract with overflow' when cycling backwards though a list
Asked Answered
S

3

42

I am writing a cycle method for a list that moves an index either forwards or backwards. The following code is used to cycle backwards:

(i-1)%list_length

In this case, i is of the type usize, meaning it is unsigned. If i is equal to 0, this leads to an 'attempt to subtract with overflow' error. I tried to use the correct casting methods to work around this problem:

((i as isize)-1)%(list_length as isize)) as usize

This results in an integer overflow.

I understand why the errors happen, and at the moment I've solved the problem by checking if the index is equal to 0, but I was wondering if there was some way to solve it by casting the variables to the correct types.

Saintsimonianism answered 23/12, 2016 at 12:24 Comment(2)
As an aside: I don't think you want to do that at all. (-1 % 10) is -1, not 9. -1isize as usize is 18446744073709551615 (on 64-bit architectures).Gneiss
Ok, I didn't know. I thought it worked like described in this post, but I see now that it is implemented like described in this post. That clears it up!Saintsimonianism
B
29

As DK. points out, you don't want wrapping semantics at the integer level:

fn main() {
    let idx: usize = 0;
    let len = 10;

    let next_idx = idx.wrapping_sub(1) % len;
    println!("{}", next_idx) // Prints 5!!!
}

Instead, you want to use modulo logic to wrap around:

let next_idx = (idx + len - 1) % len;

This only works if len + idx is less than the max of the type — this is much easier to see with a u8 instead of usize; just set idx to 200 and len to 250.

If you can't guarantee that the sum of the two values will always be less than the maximum value, I'd probably use the "checked" family of operations. This does the same level of conditional checking you mentioned you already have, but is neatly tied into a single line:

let next_idx = idx.checked_sub(1).unwrap_or(len - 1);
Brigham answered 23/12, 2016 at 14:17 Comment(0)
W
10

If your code can have overflowing operations, I would suggest using Wrapping. You don't need to worry about casting or overflow panics when you allow it:

use std::num::Wrapping;

let zero = Wrapping(0u32);
let one = Wrapping(1u32);

assert_eq!(std::u32::MAX, (zero - one).0);
Weinreb answered 23/12, 2016 at 12:31 Comment(1)
There's also inherent methods on each type for wrapping arithmetic.Brigham
I
-1

Had a similar issue, the equivalent of

(i+list_length-1)%list_length

solved it for me

Ina answered 12/4 at 21:15 Comment(1)
This doesn't add anything to Shepmaster's answer.Longitudinal

© 2022 - 2024 — McMap. All rights reserved.