21
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

GoAdvent Calendar 2019

Day 7

[Go] constant x.x truncated to integerはなぜおきるのか

Last updated at Posted at 2019-12-07

TL;DR

  • const n = int(0.5) はconstant x.x truncated to integerというコンパイルエラーになってしまう
  • 型付き定数の値は、常に定数型の値で正確に表現可能でなければならない
  • 型変換の言語仕様にも「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)に変換する際に、端数を切り捨てる という仕様。この仕様によって、我々はfloat32float64といった浮動小数点の値を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)は、-1uint型として表現可能ではない。これは仕様上エラーとなる。

このことは、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.5int型で表現可能ではないため、コンパイルエラーになり、

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
}
21
5
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?