uint(1) - uint(2) の評価 または型付けなし定数について
Twitter の TL には脊髄反射で書いてしまったが,この件は Go およびそのコンパイラの特徴をよく表していると思うので Zenn 記事としてちょろんと書いておく。
起点となる記事はこれ:
いわゆる符号なし整数の “
package main
import "fmt"
func main() {
fmt.Println(uint(1) - uint(2))
}
と書いたコードを実行しようとすると
./prog.go:6:22: constant -1 overflows uint
などとコンパイルエラーになる。何故か。
実は Go には「型付けなし定数(untyped constant)」と呼ばれる仕様がある。
Constants may be typed or untyped. Literal constants, true, false, iota, and certain constant expressions containing only untyped constant operands are untyped.
A constant may be given a type explicitly by a constant declaration or conversion, or implicitly when used in a variable declaration or an assignment or as an operand in an expression. It is an error if the constant value cannot be represented as a value of the respective type.
この仕様によりリテラル値はいったん型付けなし定数として評価され型が決まった時点で再評価される。たとえば,符号なし整数は負値を取らないので,リテラル値の -1
を uint
にキャストしても
x := uint(-1) // constant -1 overflows uint
とコンパイルエラーになる。
uint(1) - uint(2)
は符号なし整数同士の引き算ぢゃないか! と思われるかもしれないが,実際のコンパイラの挙動としては,リテラル値の部分を 1 - 2
と型付けなし定数として評価してから uint
型にキャストされるようだ。最適化というやつだ。なので
x := uint(1) - uint(2) // constant -1 overflows uint
もコンパイルエラーになるのだ。もちろん,それぞれのリテラル値をいったん(型付きの)変数に落とし込んでやれば
package main
import "fmt"
func main() {
var a, b uint = 1, 2
fmt.Println(a - b) // 18446744073709551615
}
意図通り符号なし整数同士の引き算として機能する。ちなみに
fmt.Println(18446744073709551615) // constant 18446744073709551615 overflows int
もコンパイルエラーとなる[1]。理由は分かるね(笑)
型付けなし定数といっても無限サイズの数値を扱えるわけではない。どこまでのサイズを扱えるかは基本的にはコンパイラ依存となるのだが,仕様としては
Implementation restriction: Although numeric constants have arbitrary precision in the language, a compiler may implement them using an internal representation with limited precision. That said, every implementation must:
- Represent integer constants with at least 256 bits.
- Represent floating-point constants, including the parts of a complex constant, with a mantissa of at least 256 bits and a signed binary exponent of at least 16 bits.
- Give an error if unable to represent an integer constant precisely.
- Give an error if unable to represent a floating-point or complex constant due to overflow.
- Round to the nearest representable constant if unable to represent a floating-point or complex constant due to limits on precision.
といったあたりまでは保証されているようだ。
参考
以下の書籍の3.6.2章で「型付けなし定数」について詳しく解説されている。ご参照あれ!
【2022-03-13 追記】おまけのクイズ
Twitter の TL で見かけたクイズを紹介する。以下のコードを実行するとどうなるでしょう。
fmt.Println(1 % 2.0)
fmt.Println(int(1) % 2.0)
この記事を読んだならもう分かるよね(笑) 答え合わせはこちら。
Discussion
冪乗ですね
ホンマじゃ! 間違いのご指摘ありがとうございます。修正しました。
qiitaの記事を書いた人間です、役に立つになって、嬉しいです。