みなさん、こんにちは。
今回は、Go言語でリリースしているコース「【Go言語】基礎文法からweb開発まで攻略コース」でいただいた質問で私にとっても勉強できたので紹介します。
コースの紹介は、こちら
Contents
質問内容
Udemyで受講者の方から下記の質問をいただきました。
MaxInt uint64 = 1<<64 - 1
と簡単に書いてありますが、1<<64
はuint64
の範囲を超えてしまっているのに、正しくMaxInt
の値が計算できているのはなぜですか?
MaxInt uint64 = 1<<64
とすると、constant 18446744073709551616 overflows uint64
とエラーメッセージが表示されます。プログラムで扱える整数の範囲と、Goコンパイラがコンパイル時に扱える「数」の範囲は別物ということでしょうか?
オーバーフローとは?
データには型がある
プログラミング言語では、データを扱うために型と呼ばれるものがあります。
Go言語では、整数であればint型や文字列であればstring型など様々です。
特に数値系の型では、扱える値の大小の限界値が存在します。
それは、型を使うときに利用する2進数のビット数によって決まります。
例えば、ご自宅の中で本を1冊保管する時に、図書館で利用するような本棚を用意はしないですよね。適切な大きさの棚か本立てくらいで良いはずです。
いつも最大限使えるようにすればいいじゃん!と思いますが、パソコンの中で記憶して扱えるデータ量には、限度があります。そのため型によって扱える数の限度を設けて必要な時には幅広い数を利用できる型にしてプログラムをします。
型の扱える値を超えた場合
この場合がオーバーフローとなります。エラーとして表示されてしまいます。
実際にA Tour Of GoというGoの入門サイトで行うと下記のようにエラーが表示されます。
1 2 3 4 5 6 7 8 |
コード func main() { var Maxint uint64 = 18446744073709551616 } 出力 constant 18446744073709551616 overflows uint64 |
しかし、下記のようにするとエラーはなくなり表示されます。
1 2 3 4 5 6 7 8 |
コード func main() { var Maxint uint64 = 18446744073709551616 -1 fmt.Printf("Type: %T Value: %v\n", Maxint, Maxint) } 出力 Type: uint64 Value: 18446744073709551615 |
値自体が範囲外のはずなのに計算はできているのが謎でした。
コンパイル時にどこまで計算できるか調べる
型に値を入れる際にオーバーフローでエラーになりますが、計算できるということはコンパイラ自体は計算できる値の幅に入っていることになります。
そこで、コンパイラが計算できない範囲はどこまでか調べました。
Githubで調べる
GoのソースコードはGithubに挙げられており、そこから調べていきました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
func (a *Mpint) Lsh(b *Mpint) { if a.Ovf || b.Ovf { if nsavederrors+nerrors == 0 { Fatalf("ovf in Mpint Lsh") } a.SetOverflow() return } s := b.Int64() if s < 0 || s >= Mpprec { //ここの部分 msg := "shift count too large" if s < 0 { msg = "invalid negative shift count" } yyerror("%s: %d", msg, s) a.SetInt64(0) return } if a.checkOverflow(int(s)) { yyerror("constant shift overflow") return } a.Val.Lsh(&a.Val, uint(s)) } |
この部分でMpprecという定数と比較しながら限界値を判断していました。そこで、Mpprecを検索するとGithub Golang mpfloatにMpprec=512となっていました。つまり512ビット計算までできます。2の512乗は1.340781e+154のため、これがコンパイラの計算限界値と判断できました。
試してみる
A Tour Of Goで下記のようにコードを使ってみました。
1 2 3 4 5 6 7 8 9 10 11 12 |
コード func main() { var a uint64 = 1<<512 var b uint64 = 1 <<511 fmt.Printf("Type: %T Value: %v\n", a, a) fmt.Printf("Type: %T Value: %v\n", b, b) } 出力 prog.go:18:18: shift count too large: 512 prog.go:19:17: constant 6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048 overflows uint64 |
1<<512はビット計算で左に512シフトした値にします。
このビットシフトの値で512の部分が境界値ということが確認できました。
まとめ
今回は、Go言語のコンパイラで扱える値の限界について調べてみました。
最初は、OSの中身で調べるようにしていましたがGoのコンパイラの中身のコードを見るまでに時間がかかってしまいました。
検索して他の方の情報を調べるのも良いですが、根本的なソースコードがあった場合は、面倒くさくても地道にみていくべきですね。勉強になりました。