オーバーフロー
Goには整数型が10個ある。
- 符号付き整数型 : int, int8, int16, int32, int64
- 符号なし整数型 : uint, uint8, uint16, uint32, uint64
32bitシステムの場合は32bitのint32, 64bitシステムの場合は64bitのint64を使う。
ここで、int32の最大値(math.MaxInt32
)に1を足して、出力させてみます。
func main() {
var counter int32 = math.MaxInt32
fmt.Printf("counter = %d\n", counter)
counter++
fmt.Printf("counter = %d\n", counter)
}
このコードは、コンパイルエラーやパニックは起きませんが、オーバーフローが発生します。実行すると、以下のようになります。
counter = 2147483647
counter = -2147483648
int8での最大最小を考えてみます。
10進数 | 2進数 |
---|---|
0 | 00000000 |
127 | 01111111 |
-128 | 10000000 |
❌ | 11111111 |
実際に、コードで出してみると以下のようになります。
func main() {
maxInt8 := math.MaxInt8
minInt8 := math.MinInt8
maxUint8 := math.MaxUint8
minUint8 := uint8(0)
fmt.Printf("int32(10進数) : %d 〜 %d\n", minInt8, maxInt8)
fmt.Printf("int32(2進数) : %b 〜 %b\n", minInt8, maxInt8)
fmt.Printf("uint32(10進数): %d 〜 %d\n", minUint8, maxUint8)
fmt.Printf("uint32(2進数) : %b 〜 %b\n", minUint8, maxUint8)
}
int32(10進数) : -128 〜 127
int32(2進数) : -10000000 〜 1111111
uint32(10進数): 0 〜 255
uint32(2進数) : 0 〜 11111111
11111111の場合は2の補数となる(ビット反転して、+1)ので、オーバーフローとなります。
ちなみに、Goではコンパイル時にオーバーフローが検出できれば、コンパイルエラーとなります。
func main() {
var counter int32 = math.MaxInt32 + 1 // コンパイルエラー
}
しかし、検知できな場合は、先ほどの例のようにオーバーフローやアンダーフローはエラーにもパニックにもならないので注意しなければならない。これらは分かりにくいバグを引き起こす。
1996年のアリアン5の打ち上げ失敗はオーバーフローが原因らしいです。
クラスターミッション
以下の場合は要注意。
- メモリに制約があるプロジェクトで小さい整数型を使用するとき
- 大きな数値を扱ったり変換したりするとき
オーバーフローの検知
オーバーフローを検知するためのアルゴリズムを紹介していきます。
1を加算するときの検出アルゴリズム
int8 や int16に1を加算する場合にオーバーフローを検出する場合は以下のようにする。
func Inc32(counter int32) int32 {
if counter == math.MaxInt32 {
panic("int32 overflow")
}
return counter + 1
}
Go1.17以前はMaxInt32などがなかったので、自分で定数を作成する必要がありました。
intやunitも同じようにできます。
func IncInt(counter int) int {
if counter == math.MaxInt {
panic("int overflow")
}
return counter + 1
}
func IncUint(counter uint) uint {
if counter == math.MaxUint {
panic("uint overflow")
}
return counter + 1
}
加算時の整数オーバーフローの検出アルゴリズム
2つの整数の加算によるオーバーフローはmath.MaxInt
とmath.MinInt
を用いて検出する。
func AddInt(a, b int) int {
if (b > 0 && a > math.MaxInt-b) || (b < 0 && a < math.MinInt-b {
panic("int overflow")
}
return a + b
}
乗算時の整数のオーバーフローの検出アルゴリズム
func MultiplyInt(a, b int) int {
if a == 0 || b == 0 {
return 0
}
result := a * b
if a == 1 || b == 1 {
return result
}
if a == math.MinInt || b == math.MinInt {
panic("integer overflow")
}
if result/b != a {
panic("integer overflow")
}
return result
}
終わりに
Goでは大きな数を扱うパッケージとして、math/bigがある。また、適切なサイズの型を使用するのも大事で、Goのstrconv.ParseInt
やstrconv.ParseUint
を使って、適切なbitSizeを指定することもできます。