Go言語:数値型の幅
フィボナッチなサンプルを写経して動かしていた時にやっと理解した。
Go言語の数値型(の整数)
- intはジェネリックであり、実行環境に依存したサイズ(値の扱える範囲)を持つ。
- 範囲を超えた場合は、オーバーフローを起こす。
- 数値演算を行った結果、扱える範囲の最小または最大を超える。
- オーバーフローが発生しても例外は発生しない。
- ただし、リテラルの場合は、例外が発生する。
- わかりきったオーバーフローのためと推測。
- オーバーフローしたら破棄され、ラップアラウンドが発生する。
- 扱える範囲の最大値に達した後に、最小値に戻る。
- 扱える範囲の最小値に達した後に、最大値に戻る。
ラップアラウンドが発生するため、
x < x + 1
が常に成り立つとは限らない。ラップアラウンドを発生させる使用であるため、暗黙的な型変換は発生しない。
なぜGoには暗黙的な数値変換がないのですか?
C言語における数値型での自動型変換の利便性は過剰であり、混乱を招く原因となっています。式が符号無しなのはいつか? 値の大きさはどの位? オーバーフローする? 型変換の結果はポータブルで実行されるマシンに非依存? これはコンパイラも複雑にします。「通常の算術変換」の実装は簡単ではなく、アーキテクチャ間で一貫性を保つことも簡単ではありません。私たちは移植性の理由から、コード中で明示的に型変換をする労力を払うことによって、明確で解り易くする決断をしました。それでも、Go言語での定数の定義(符号およびサイズのアノテーションの要らない任意精度の値)は問題をかなり改善しています。
これと関連しますが、C言語とは違い、仮にintが64ビットだったとしても、intとint64は明確に別の型となります。int型はジェネリックです。整数が何ビットあるのかを気にしたい場合、Go言語では明示的に指定することが推奨されます。
試してみる
実行環境は64bitマシン
実行結果
overflowIntWithLiteral 9223372036854775807 + 0 => 9223372036854775807 overflowInt 9223372036854775807 + 0 => 9223372036854775807 9223372036854775807 + 1 => 9223372036854775806 overflowUint8Max 255 + 0 => 255 255 + 1 => 254 overflowUint8Min 0 - 0 => 0 0 - 1 => 255
コード中のコメントアウトを解いて実行すると、
# command-line-arguments ./overflow.go:21: constant 9223372036854775808 overflows int
Rubyで慣れきってて、めちゃくちゃに大きい数値を扱うことが少ないと、下手にラップアラウンドを罠として踏みそう。
大事なのは、明示的にプログラミングすることと、そのための想定。