rs.nkmk.me

Rustで整数のオーバーフローに対応(checked, wrappingなど)

Posted: | Tags: Rust, 数値

Rustで、整数型(i32u32など)のオーバーフローに対応するには、checked_*wrapping_*などのメソッドを使う。

整数型が取り得る範囲(最大値・最小値)

整数型が取り得る範囲、すなわち、最大値と最小値は、MAXMINで取得できる。

assert_eq!(i8::MAX, 127);
assert_eq!(i8::MIN, -128);

assert_eq!(u8::MAX, 255);
assert_eq!(u8::MIN, 0);

この範囲を超えるとオーバーフローとなる。

デバッグビルドとリリースビルドの違い

コンパイル時にオーバーフローが検知されればコンパイルエラーとなる。

let i: i8 = 100;
// println!("{}", i + 28);
// error: this arithmetic operation will overflow

コンパイル時に検知できない場合、デバッグビルド(cargo buildcargo runのデフォルト)では実行時にパニックが発生する。

let s = "100";
let i: i8 = s.parse().unwrap();

// debug build
// println!("{}", i + 28);
// thread 'main' panicked at 'attempt to add with overflow'

リリースビルド(cargo build -rcargo run -r)ではラップアラウンドされた値となる。後述のwrapping_*の結果と同じ。以下の例では便宜上コメントアウトしているがパニックなしで実行される。

// release build
// println!("{}", i + 28);
// -128

オーバーフローを明示的に処理するため、整数型にはchecked_*wrapping_*などのメソッドが用意されている。*には四則演算(add, sub, mul, div)や絶対値abs、べき乗powなどが入る。

checked_*: Optionを返す

checked_*Optionを返す。オーバーフローしたときはNoneとなる。

以下のリンクは公式リファレンスにおけるi32に対する検索結果だが、他の整数型にも同じメソッドがある。以降も同様。

let i: i8 = 100;
assert_eq!(i.checked_add(27), Some(127));
assert_eq!(i.checked_add(28), None);

なお、checked_divはゼロ除算に対してもNoneを返す。

wrapping_*: ラップアラウンド

wrapping_*はラップアラウンド(wrap around)した値を返す。

ラップアラウンドは溢れた桁を切り捨てる処理。最大値を超えると最小値に戻り、最小値を超えると最大値に戻る。

let u: u8 = 128;
assert_eq!(u.wrapping_add(127), 255);
assert_eq!(u.wrapping_add(128), 0);
assert_eq!(u.wrapping_add(138), 10);

assert_eq!(u.wrapping_sub(128), 0);
assert_eq!(u.wrapping_sub(129), 255);
assert_eq!(u.wrapping_sub(139), 245);

let i: i8 = 100;
assert_eq!(i.wrapping_add(27), 127);
assert_eq!(i.wrapping_add(28), -128);
assert_eq!(i.wrapping_add(38), -118);

let i: i8 = -100;
assert_eq!(i.wrapping_sub(28), -128);
assert_eq!(i.wrapping_sub(29), 127);
assert_eq!(i.wrapping_sub(39), 117);

上述のように、リリースビルドでは通常の+-演算子の結果がこの値となる。

overflowing_*: (wrapping, overflowed)のタプルを返す

overflowing_*(wrapping, overflowed)のタプルを返す。ここで、wrappingwrapping_*の結果、overflowedはオーバーフローしたかどうかを示すbool

let i: i8 = 100;
assert_eq!(i.overflowing_add(27), (127, false));
assert_eq!(i.overflowing_add(28), (-128, true));
assert_eq!(i.overflowing_add(38), (-118, true));

let i: i8 = -100;
assert_eq!(i.overflowing_sub(28), (-128, false));
assert_eq!(i.overflowing_sub(29), (127, true));
assert_eq!(i.overflowing_sub(39), (117, true));

saturating_*: 最大値・最小値でクランプ

saturating_*はオーバーフローした場合にその型の最大値または最小値でクランプ(クリップ)した値を返す。

let i: i8 = 100;
assert_eq!(i.saturating_add(27), 127);
assert_eq!(i.saturating_add(28), 127);
assert_eq!(i.saturating_add(100), 127);

let i: i8 = -100;
assert_eq!(i.saturating_sub(28), -128);
assert_eq!(i.saturating_sub(29), -128);
assert_eq!(i.saturating_sub(100), -128);

Wrapping構造体

std::num::Wrapping構造体を使う方法もある。

AddトレイトやSubトレイトなどが実装されており、演算子オーバーロードで+-などの演算子がラップアラウンド処理になる。

use std::num::Wrapping;

assert_eq!(Wrapping(100_i8) + Wrapping(27), Wrapping(127));
assert_eq!(Wrapping(100_i8) + Wrapping(28), Wrapping(-128));

assert_eq!(Wrapping(32_i8) * Wrapping(2), Wrapping(64));
assert_eq!(Wrapping(32_i8) * Wrapping(4), Wrapping(-128));

中身の整数型の値は.0で取り出す。

assert_eq!(Wrapping(100_i8).0, 100);
assert_eq!((Wrapping(100_i8) + Wrapping(28)).0, -128);

3つ以上の数で演算する場合は、wrapping_*メソッドよりWrapping構造体のほうが分かりやすく書けるかもしれない。

let a: i8 = 64;
let b: i8 = 32;
let c: i8 = 2;

assert_eq!(a.wrapping_add(b.wrapping_mul(c)), -128);
assert_eq!((Wrapping(a) + Wrapping(b) * Wrapping(c)).0, -128);

なお、Rust1.67時点ではnightly-onlyだが、saturating_*に対応するSaturating構造体もある。

関連カテゴリー

関連記事