Rustで整数のオーバーフローに対応(checked, wrappingなど)
Rustで、整数型(i32
やu32
など)のオーバーフローに対応するには、checked_*
やwrapping_*
などのメソッドを使う。
整数型が取り得る範囲(最大値・最小値)
整数型が取り得る範囲、すなわち、最大値と最小値は、MAX
とMIN
で取得できる。
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 build
やcargo 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 -r
やcargo 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)
のタプルを返す。ここで、wrapping
はwrapping_*
の結果、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
構造体もある。