【Go言語】オーバーフローの仕組みと検出

# オーバーフロー

Goには整数型が10個ある。

* 符号付き整数型 : int, int8, int16, int32, int64
* 符号なし整数型 : uint, uint8, uint16, uint32, uint64

32bitシステムの場合は32bitのint32, 64bitシステムの場合は64bitのint64を使う。
ここで、int32の最大値(`math.MaxInt32`)に1を足して、出力させてみます。

```go
func main() {
	var counter int32 = math.MaxInt32
	fmt.Printf("counter　=　%d\n", counter)
	counter++
	fmt.Printf("counter　=　%d\n", counter)
}
```

このコードは、コンパイルエラーやパニックは起きませんが、オーバーフローが発生します。実行すると、以下のようになります。

```txt:実行結果
counter　=　2147483647
counter　=　-2147483648
```

int8での最大最小を考えてみます。

| 10進数 |  2進数  |
|:-:|:-:|
| 0 | 00000000  |
| 127 | 01111111  |
| -128 | 10000000  |
| ❌ | 11111111  |

実際に、コードで出してみると以下のようになります。

[Go Playground](https://go.dev/play/p/V0Ul--vMuhY)

```go
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)
}
```

```txt:実行結果
int32(10進数) : -128 〜 127
int32(2進数)  : -10000000 〜 1111111
uint32(10進数): 0 〜 255
uint32(2進数) : 0 〜 11111111
```

11111111の場合は2の補数となる(ビット反転して、+1)ので、オーバーフローとなります。

ちなみに、Goではコンパイル時にオーバーフローが検出できれば、コンパイルエラーとなります。

```go
func main() {
	var counter int32 = math.MaxInt32 + 1 // コンパイルエラー
}
```

しかし、検知できな場合は、先ほどの例のようにオーバーフローやアンダーフローはエラーにもパニックにもならないので注意しなければならない。これらは分かりにくいバグを引き起こす。

:::note info
1996年のアリアン5の打ち上げ失敗はオーバーフローが原因らしいです。
[クラスターミッション](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%BF%E3%83%BC%E3%83%9F%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3)
:::

以下の場合は要注意。

* メモリに制約があるプロジェクトで小さい整数型を使用するとき
* 大きな数値を扱ったり変換したりするとき

# オーバーフローの検知

オーバーフローを検知するためのアルゴリズムを紹介していきます。

## 1を加算するときの検出アルゴリズム

int8 や　int16に1を加算する場合にオーバーフローを検出する場合は以下のようにする。

```go
func Inc32(counter int32) int32 {
	if counter == math.MaxInt32 {
		panic("int32 overflow")
	}
	return counter + 1
}
```

:::note info
Go1.17以前はMaxInt32などがなかったので、自分で定数を作成する必要がありました。
:::

intやunitも同じようにできます。

```go
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`を用いて検出する。

```go
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
}
```

## 乗算時の整数のオーバーフローの検出アルゴリズム

```go
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](https://pkg.go.dev/math/big)がある。また、適切なサイズの型を使用するのも大事で、Goの`strconv.ParseInt`や`strconv.ParseUint`を使って、適切なbitSizeを指定することもできます。

# 参考文献

* [Go言語でのInteger Overflow対策にはstrconv.ParseIntを利用する - みずりゅの自由帳](https://mzryuka.hatenablog.jp/entry/2020/10/20/233300)
* [Japanese Version - 100 Go Mistakes and How to Avoid Them](https://100go.co/ja/)