LoginSignup
1
0

More than 1 year has passed since last update.

Goクイズアドベントカレンダー 14日目

Last updated at Posted at 2020-12-16

問題

Q. 以下のコードを実行するとどうなるでしょう?

package main

import (
    "fmt"
)

func main() {
    type A float64
    type B = float64

    // 2の128乗より少し小さいくらい
    const a = 50000000000000000000000000000000000000000000000000000000000000000000000000000
    var b B = B(a)
    var c A = A(a)

    fmt.Printf("%T %T %T", a, b, c)
}

  1. int, float64, main.A と表示される
  2. float64, main.B, main.A と表示される
  3. untyped int, float64, main.A と表示される
  4. コンパイルエラー

以下解答&解説















解答

正解は 4. コンパイルエラー でした。 → 答え合わせ

$ go run prog.go
./prog.go:17:13: constant 50000000000000000000000000000000000000000000000000000000000000000000000000000 overflows int

というエラーメッセージが表示されます。

解説

このコードがコンパイルエラーになることを理解するためには、次の知識が必要です。

  • 型無し定数
  • 型無し定数のデフォルト型
  • 代入できる値とできない値の区別

それでは、順を追って解説していきます。

型無し定数とは

Goには「型無し定数(untyped constants)」というものが存在します。
仕様書では Constantsの章 で触れられています。

Constants may be typed or untyped. Literal constants, true, false, iota, and certain constant expressions containing only untyped constant operands are untyped.
(直訳)
定数は型付きでも型なしでもかまいません。リテラル定数、true、false、iota、および型指定されていない定数オペランドのみを含む特定の定数式は型指定されていません。

今回のコードで定数 a として定義している 50000000000000000000000000000000000000000000000000000000000000000000000000000 はリテラル定数なので
untyped integer constant となります。

また、定数 a はもっとも大きい整数型であるuint64に収まらないほど大きな数ですが、仕様書にも次のように書かれているとおり、
大きな定数を定義すること自体はコンパイルエラーになりません。

Rune, integer, floating-point, and complex constants are collectively called numeric constants.
〜中略〜
Numeric constants represent exact values of arbitrary precision and do not overflow.
(直訳)
Rune、整数、浮動小数点数、複素数の定数は数値定数と総称されます。
数値定数は任意精度の正確な値を表し、オーバーフローしません。

ただし、無限大の精度の数値を扱えるのはあくまで仕様上の話であり、実際の計算機でそのような処理系を作ることは不可能なので、仕様書には次のようにも書かれています。

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.
(直訳)
実装の制限:数値定数は言語で任意の精度を持っていますが、コンパイラーは制限された精度の内部表現を使用してそれらを実装する場合があります。とはいえ、すべての実装は次のことを行う必要があります。
- 256ビット以上の整数定数を表します。
- 少なくとも256ビットの仮数と少なくとも16ビットの符号付き2進指数を使用して、複素定数の一部を含む浮動小数点定数を表します。
- 整数定数を正確に表すことができない場合はエラーを出します。
- オーバーフローのために浮動小数点または複素定数を表すことができない場合は、エラーを出します。
- 精度の制限により浮動小数点または複素定数を表現できない場合は、最も近い表現可能な定数に丸めます。

定数 a は64bitには収まらなくても、256bitには収まるため、全ての処理系で正確に表せる整数定数であることが保証されています。

型無し定数のデフォルト型

型無し定数には型がありませんが、型無し定数を使うときには型が確定します。

例えば、次の例では、12345 は型無し定数ですが、hogeに代入するときに明示されているuint16型に確定します。

var hoge uint16 = 12345

しかし、型の宣言を省いた場合、型を確定させられないように見えます。

hoge := 12345

Goではこのようなケースに対応するために、それぞれの型無し定数にはデフォルト型が定められています。
仕様書では Constantsの章 で触れられています。

The default type of an untyped constant is bool, rune, int, float64, complex128 or string respectively, depending on whether it is a boolean, rune, integer, floating-point, complex, or string constant.
(直訳)
型なし定数のデフォルトの型は、ブール定数、ルーン定数、整数、浮動小数点定数、複素定数、または文字列定数のいずれであるかに応じて、それぞれbool、rune、int、float64、complex128、またはstringです。

つまり、hoge の型は型無し整数定数である 12345 のデフォルト型 = int 型 になります。

今回の問題でコンパイルエラーになっている行を読むと、型無し整数定数である ainterface{} に渡そうとしています。

    fmt.Printf("%T, %T, %T, %T", a, b, c, d)

interface{} の動的型を決定する必要がありますが、明示的な型指定をしていないため、定数 a のデフォルト型が適用されて int 型となることが分かります。

代入できる値とできない値の区別

値の代入可能性については Assignabilityの章 で触れられています。
この章では、ずばり代入できる条件が列挙されています。

A value x is assignable to a variable of type T ("x is assignable to T") if one of the following conditions applies:
- x's type is identical to T.
- x's type V and T have identical underlying types and at least one of V or T is not a defined type.
- T is an interface type and x implements T.
- x is a bidirectional channel value, T is a channel type, x's type V and T have identical element types, and at least one of V or T is not a defined type.
- x is the predeclared identifier nil and T is a pointer, function, slice, map, channel, or interface type.
- x is an untyped constant representable by a value of type T.
(直訳)
次の条件のいずれかが当てはまる場合、値xは型Tの変数に割り当てることができます(「xはTに割り当てることができます」)。
- xのタイプはTと同じである。
- xの型VとTは同一の基になる型を持ち、VまたはTの少なくとも1つは定義された型ではない。
- Tはインターフェイスタイプであり、xはTを実装している。
- xは双方向チャネル値、Tはチャネルタイプ、xのタイプVとTは同一の要素タイプを持ち、VまたはTの少なくとも1つは定義されたタイプではありません。
- xは事前に宣言された識別子nilであり、Tはポインタ、関数、スライス、マップ、チャネル、またはインターフェイスタイプである。
- xは、型Tの値で表すことができる型無し定数である。

今回は型無し整数定数を int 型として代入しようとしているため、「定数 aint 型の値で表すことができるかどうか」が争点になります。
型無し定数が型Tの値で表せるかどうかの条件は、 Representabilityの章に書かれています。

A constant x is representable by a value of type T if one of the following conditions applies:
- x is in the set of values determined by T.
- T is a floating-point type and x can be rounded to T's precision without overflow. Rounding uses IEEE 754 round-to-even rules but with an IEEE negative zero further simplified to an unsigned zero. Note that constant values never result in an IEEE negative zero, NaN, or infinity.
- T is a complex type, and x's components real(x) and imag(x) are representable by values of T's component type (float32 or float64).
(直訳)
次の条件のいずれかが当てはまる場合、定数xはタイプTの値で表すことができます。
- xは、Tによって決定される値の集合に含まれる。
- Tは浮動小数点型であり、xはオーバーフローすることなくTの精度に丸めることができる。丸めにはIEEE754の丸め規則が使用されますが、IEEEの負のゼロはさらに単純化されて符号なしゼロになります。定数値がIEEEの負のゼロ、NaN、または無限大になることは決してないことに注意してください。
- Tは複素数型であり、xの成分real(x)およびimag(x)は、Tの成分型(float32またはfloat64)の値で表すことができる。

定数 a は整数であり、浮動小数点数でも複素数でもないため、1つ目の条件が適用され、 int 型の範囲に収まっていれば、無事代入可能であることが分かります。

int 型の精度は処理系によって異なりますが、Numeric Typesの章には次のように書かれています。

uint either 32 or 64 bits
int same size as uint
(直訳)
uint 32もしくは64ビット
int uintと同じ

つまり int 型の精度は int32int64 と同じです。
しかし定数 a は非常に大きく、 2の127乗 < a < 2の128乗 程度の値のため int32 はおろか int64 にも到底収まりません。

まとめ

よって、今回の問題のコードは、

fmt.Printf()interface{} 型の引数に型無し整数定数 a を与えようとする
→ 型を指定していないためデフォルト型のint 型として代入しようとする
int 型に収まらないくらい大きい値のため、 定数 aint 型として表すことができない。
→ 定数 aint

というステップを経てコンパイルエラーになっているのでした。

おまけ

それぞれの選択肢がどういったシナリオを想定していたのかも書いておきます。

  1. int, float64, main.A を選んだ人
    • 型無し整数定数のデフォルト型が int 型であることを知っていたが、 int 型に収まらない大きさの場合にどうなるか知らなかった
    • 型エイリアスを%Tで表示しても元の型が表示されることを知っていた
    • ちなみに、定数 aint 型に収まる大きさだった場合は、この選択肢が正解です。
  2. float64, main.B, main.A を選んだ人
    • 定数 aint 型には収まらないが、 float64 には収まるため変換されると勘違いしていた
    • type B = float64 という型定義は、float64のエイリアスを作っているだけなので、%Tで表示しても float64 と表示されることを知らなかった
  3. untyped int, float64, main.A を選んだ人
    • 定数 a が型無し整数定数であることを見抜いていたが、引数に渡されるときに型が確定してしまうことを忘れていた
    • 型エイリアスを%Tで表示しても元の型が表示されることを知っていた
    • ちょっと詳しい人を引っ掛けたくてこの選択肢を用意しました
1
0
0

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
1
0