Go クイズ: v == v が false 編

この記事は Goクイズ Advent Calendar 2020 の 18 日目の記事です。

問題

以下の一部欠けたプログラムの出力は false になります。①には任意の式が入り、②にはビルトイン関数名が入ります。

func main() {
	a :=   
	b :=   (a, a)
	c := [2]interface{}{b, b}

	fmt.Println(c == c)
}

[レベル1] 選択肢の中から変数 a の型を選んでください。

選択肢:

[レベル2] ①、②に当てはまるものを答えてください。

※Playground の利用を認めます: https://play.golang.org/p/uKoGpyiHdee

回答

[レベル1] float32

[レベル2]

func main() {
	a := float32(math.NaN())
	b := complex(a, a)
	c := [2]interface{}{b, b}

	fmt.Println(c == c)
}

(①は float32 型の NaN を返す表現なら正解)

Playground

「また NaN 問か〜」とがっかりされた方、ごめんなさい!(かけてはない)

まず仕様上、v == v など同じ変数を比較して false になる値は「NaN か、NaN を含むもの」しか存在しません(Spec)。具体的には

  • float32(NaN)
  • float64(NaN)
  • struct(NaN を含む)
  • array(NaN を含む)
  • complex(NaN を含む)

のみです。

以上の中で選択肢にあるものは float32 のみなので a の型は float32 になり、1 つ目の穴埋めは float32(math.NaN()) になります(math.NaN() の返り値は float64)。

なぜ NaN がそれ自体と一致しないかと言うと、IEEE 754 でそう決まっているからですね。

また、逆に②から考えることもできます。第一引数と第二引数に同じ変数を入れられそうなビルトイン関数は append([]interface{} の場合), delete(前に同様), complex, copy, print, println ですが、このうち①の選択肢のどれかを受け取りそうなものは print 系を除くと complex(float32)だけです。

ここに NaN を入れるとどうなるかと言うと、複素数型は「虚数部と実数部がどちらも一致(==)する」場合に一致する(Spec)ので、虚数部/実数部のどちらかに NaN が含まれているとそれ自身とも一致しなくなります。

またこれを更に配列に入れていますが、配列も同様の性質(すべての要素が一致するなら一致)を持つので同様の結果になります。単に memcmp している訳ではないんですね〜

(もしかしたら NaN の特殊ペイロードを乱数等にして memcmp できるようにしているのかも? と思ったのですが、もうひとつ IEEE 754 の比較におけるイレギュラーな存在である「負のゼロ」のことを思い出してやっぱり無理だなと思いました。負のゼロは正のゼロと異なるビット列を持つにも関わらず、比較演算では同一という結果になります。)

以上です。お疲れ様でした!