I'm trying to understand what is the best way to deal with 'overflow' and 'underflow' when using usize/u32 variables. For instance, I was trying to implement Binary Search in Rust and came up with a situation where one of the variables may underflow.
Below implementation of my implementation of Binary Search in Rust. I wanted to ask the community if there is a better way to write it. Most implementations I see online avoid the underflow situation by declaring mid to be i32. If I wanted to stick with usize for mid, is my use of checked_sub() necessary?
// Return -1 if target not found
fn binary_search(nums: Vec<i32>, target: i32) -> i32 {
let mut left = 0;
let mut right = nums.len() - 1;
let mut underflow = false;
while left <= right && !underflow {
let mid = left + (right - left) / 2;
if nums[mid] == target {
return mid as i32;
} else if target > nums[mid] {
left = mid + 1;
} else {
right = mid.checked_sub(1).unwrap_or_else(|| {
underflow = true;
0
});
}
}
-1
}
If I don't use checked_sub() I get a undeflow and mid wraps around leading to logic errors with the algorithm
EDIT: This part of a leetcode question so I’m unfortunately not allowed to change the function signature :( See here: https://leetcode.com/problems/binary-search/description/rust
The language is telling you something useful here, your index variable might underflow and wrapping which would be the default in most other languages isn't really what you want here.
The most idiomatic solution would be to return
Option<usize>an index that might not have been found, then you can use the short circuit try operator?to immediately returnNone(instead of a sentinel like -1 you'd have to use in other languages) to signal a failure to find the target:Note: Switching from taking
Vec<i32>to&[i32]still let's you pass inlet a: Vec<i32> = …viabinary_search(&a, value)but it also accepts arrays and other things that deref to a slice, it also has the added benefit of not consuming the first parameter which really isn't necessary for binary search.In general, if your variable can over-/underflow use
carrying_,checked_,wrapping_,...add,sub, ... as applicable, their use compiles to one or two instructions (i.e. no practical overhead) and they convey what behaviour you want exactly in the event of overflow.If for some reason the method signature is required to be
fn(Vec<i32>, i32) -> i32I recommend you write a wrapper:where
binary_search_implementationis the plainbinary_searchfrom above.Of course in the real world I'd use the provided
slice::binary_search: