循環(wraparound)する整数型のOverflowを検出する方法
Julia を触っているときに悩んだので備忘録的に残しておきます。
考え方
乗算がオーバーフローしやすいと思うので、その場合で考えていきます。
説明で使う関数などは Julia-like に書きます。わからない人は下記の表を確認してください。
Int64 | 64bitの整数 |
typemax(T) | T型の最大値 ex) typemax(Int64) = 263 - 1 |
typemin(T) | T型の最小値 ex) typemin(Int64) = -263 |
typeof(x) | xの型を返す |
任意の型Tについて、 がオーバーフローすると仮定すると、
が成り立ちます。しかし、ラップアラウンドする整数型に対して、この式は必ずしも成り立つとは言えません。以下に例を挙げてみます。
julia> x = 1000000000000000000 1000000000000000000 julia> typeof(x) Int64 julia> x * 10 -8446744073709551616
このとき、(1) の でラップアラウンドが発生しています。
では、ラップアラウンドが起こらないように (1) を変形してみましょう。
これでラウンドアップを起こさずにオーバーフローを検出できるはずです。 プログラム言語によっては、 の部分で切り捨て除算が起こり正しく判定できないかもしれないので、適宜処理をしてください。
の他の場合分けについても同様に考えることができ、オーバーフローする条件は、
の4パターンです。
実装の例
上記を考慮して「オーバーフロー時に、より範囲の広い整数型で計算する」関数を Julia で作ると以下のようになります。Base.Checked.checked_mul() を使って OverflowError を try catch するより断然早いはずです。
function ovf_mul(x::T, y::T) where T<:Integer (x == zero(T)) && (return zero(T)) (y == zero(T)) && (return zero(T)) if x > zero(T) if y > zero(T) return (typemax(T) / x) < y ? widen(x) * widen(y) : x * y else return (typemin(T) / x) > y ? widen(x) * widen(y) : x * y end else if y > zero(T) return (typemin(T) / x) < y ? widen(x) * widen(y) : x * y else return (typemax(T) / x) > y ? widen(x) * widen(y) : x * y end end end