如何在Rust中左移<<时惯用地测试溢出?

问题描述

对于大多数可能会溢出的操作员,Rust提供了已检查的版本。例如,要测试添加项是否溢出,可以使用checked_add

match 255u8.checked_add(1) {
    Some(_) => println!("no overflow"),None => println!("overflow!"),}

这将打印"overflow!"。还有一个checked_shl,但根据the documentation,它仅检查移位是否大于或等于self中的位数。这就是说:

match 255u8.checked_shl(8) {
    Some(val) => println!("{}",val),}

被捕获并打印"overflow!",这是

match 255u8.checked_shl(7) {
    Some(val) => println!("{}",}

仅打印128,显然没有发现溢出。 左移时检查任何溢出的正确方法是什么?

解决方法

我不知道有什么惯用的方法,但是可以实现自己的特质:Playground

该算法基本上是检查数字中的前导零是否少于移位大小

#![feature(bool_to_option)]

trait LossCheckedShift {
    fn loss_checked_shl(self,rhs: u32) -> Option<Self> 
        where Self: std::marker::Sized;
}

impl LossCheckedShift for u8 {
    fn loss_checked_shl(self,rhs: u32) -> Option<Self> {
        (rhs <= self.leading_zeros()).then_some(self << rhs)
        // in stable Rust
        // if rhs <= self.leading_zeros() { Some(self << rhs) }
        // else { None }
    }
}

fn main() {
    match 255u8.loss_checked_shl(7) {
        Some(val) => println!("{}",val),None => println!("overflow!"),// <--
    } 
    
    match 127u8.loss_checked_shl(1) {
        Some(val) => println!("{}",// <--
        None => println!("overflow!"),}
    match 127u8.loss_checked_shl(2) {
        Some(val) => println!("{}",// <--
    }
}
,

您可以进行补充的右移(右移8-request_number_of_bits个),然后检查是否还剩下0。如果是这样,则表示左移不会丢失任何位:

fn safe_shl(n: u8,shift_for: u8) -> Option<u8> {
    if n >> (8 - shift_for) != 0 {
        return None; // would lose some data
    }
    Some(n << shift_for)
}

还可以编写一个通用版本,该版本接受任何数字类型,包括bigints(应用于u8的代码将生成与上述代码完全相同的代码):

use std::mem::size_of;
use std::ops::{Shl,Shr};

fn safe_shl<T>(n: T,shift_for: u32) -> Option<T>
where
    T: Default + Eq,for<'a> &'a T: Shl<u32,Output = T> + Shr<u32,Output = T>,{
    let bits_in_t = size_of::<T>() as u32 * 8;
    let zero = T::default();
    if &n >> (bits_in_t - shift_for) != zero {
        return None; // would lose some data
    }
    Some(&n << shift_for)
}

Playground