TL;DR
-
const n = int(0.5)
はconstant x.x truncated to integerというコンパイルエラーになってしまう - 型付き定数の値は、常に定数型の値で正確に表現可能でなければならない
- https://golang.org/ref/spec#Constant_expressions
- int(0.5)は切り捨てが発生する値なので正確に表現できる値ではない
- 型変換の言語仕様にも「A constant value x can be converted to type T if x is representable by a value of T」と定数における適用ルールが明記してある
※ 2021.3.4 @DQNEO さんのコメントにて定数の型変換仕様の明記についてご教示いただき訂正しました。
背景
golang.tokyo #28のDevquizにて、
package main
import "fmt"
const n = int(0.5)
func main() {
m := n
fmt.Println(10 / m)
}
がどのような結果になるかという問題が出題された。
これは結論として、constant 0.5 truncated to integer
というエラーが出るのだが、これはint(m)
といった形で一度変数に入れた場合はコンパイルエラーにはならない。これがなぜなのかという点について言語仕様を深堀りしてみたい。
類似のIssueから学ぶ
cmd/gc
についてのIssue https://github.com/golang/go/issues/6900 にて、同様の疑問が投げかけられている。
When I run the code below,
package main
import "fmt"
func main() {
a := int(float32(23.4543534))
fmt.Println(a)
}
it reports
# command-line-arguments
./float_int.go:6: constant 23.4544 truncated to integer
このIssue内で次のように解説されている。
This is how the language is defined, though I do sometimes wonder whether this is a
mistake. A type conversion of a constant fails if the constant can not be represented
in the new type.
https://github.com/golang/go/issues/6900#issuecomment-66088962
この説明から読み取れることは、 定数を新しい型で表現(represent)できない場合は、定数の型変換に失敗する という仕様であるという点である。
非定数(non constant)の場合
型変換(type conversion)という言葉が出てきたので、変換(Conversion)の言語仕様を見てみる。
おさらい的に引用すると、型の変換は、
A conversion changes the type of an expression to the type specified by the conversion.
とある通り、式の型を変換で指定された型に変更する操作を表す。
この変換の仕様の中で Conversions between numeric types という説明がある。数字(numeric)間での変換においてGoを書く際に馴染みのあるルールが記載されている。その中の一つが、
When converting a floating-point number to an integer, the fraction is discarded (truncation towards zero).
という、 浮動小数点(float)を数値(int)に変換する際に、端数を切り捨てる という仕様。この仕様によって、我々はfloat32
やfloat64
といった浮動小数点の値をint
に変換することが出来る。
package main
import "fmt"
const n = 0.5
func main() {
m := n
toI := int(m)
fmt.Println(toI) // 0になる
}
このルールは、non-constant numeric values
の変換においてであるという点が当該ドキュメントにて明示されている。
For the conversion of non-constant numeric values, the following rules apply:
故に、定数(Constant)をそのままint()
に入れた場合の同様の動作になるかというと、そうではないということになる。
定数の場合
定数式(Constant expression)についての仕様が、 https://golang.org/ref/spec#Constant_expressions にて説明されている。
この定数式の説明において次のような記述がされている。
The values of typed constants must always be accurately representable by values of the constant type.
型付き定数の値は、常に定数型の値で正確に表現可能でなければなりません という仕様がここでわかる。例示されているコードを引用すると、次のようなケースが挙げられている。
uint(-1) // -1 cannot be represented as a uint
int(3.14) // 3.14 cannot be represented as an int
int64(Huge) // 1267650600228229401496703205376 cannot be represented as an int64
Four * 300 // operand 300 cannot be represented as an int8 (type of Four)
Four * 100 // product 400 cannot be represented as an int8 (type of Four)
例えば、uint(-1)
は、-1
がuint
型として表現可能ではない。これは仕様上エラーとなる。
このことは、Google group https://groups.google.com/forum/#!topic/golang-nuts/A-UALeyerB0 でも説明されていた。
結論
冒頭に上げたこのコードがコンパイルエラーになる理由を以上の仕様から整理すると、
package main
import "fmt"
const n = int(0.5)
func main() {
m := n
fmt.Println(10 / m)
}
const n = int(0.5)
は定数式の仕様を準拠する必要があるが、0.5
はint
型で表現可能ではないため、コンパイルエラーになり、
constant 0.5 truncated to integer
というエラーが得られる結果となる。
一方で、一度変数に入れて、それをint()
にした場合には、定数以外のType Conversionの仕様により、端数を切り捨てて変換を行う。
package main
import "fmt"
const n = 0.5
func main() {
m := n
fmt.Println(int(m))
}
よって、このコードはコンパイルエラーにはならない。
さらに、このように変数を介さない場合は、最初の例と同様にコンパイルエラーになる。
package main
import "fmt"
const n = 0.5
func main() {
fmt.Println(int(n))
}
これは、同じくn = 0.5
の値がint型で表現可能ではないので、同様の理由でconstant 0.5 truncated to integer
というコンパイルエラーになる。
また、定数であるため、non constantであれば当てはまる型変換のルールにはもちろん合致しない。
ちなみに
「int(1.0)
も浮動小数点が書かれているがいけるんだっけ?」ってふと思った。これはint型として表現可能である点が、公式ドキュメントにて例示されていた。
var m int = 1.0<<s // 1.0 has type int; m == 0 if ints are 32bits in size
そのため、このように記述してもこれはint型として表現可能であるため、問題ないことになる
package main
import "fmt"
const n = int(1.0)
func main() {
fmt.Println(n) // 1
}