帰ってきた「しっぽのさきっちょ」

1を1億回足して1億にならない場合 — プログラミング言語 Go

(この記事は Qiita に投稿した記事の転載です)

今回は軽めのネタで。

まぁ浮動小数点数型の仕様を知れば当たり前の話なのだが,面白そうなので「1を1億回足す」ってのを Go 言語でも書いてみる。

package main

import "fmt"

func main() {
	var d float32 = 0.0
	for i := 0; i < 100000000; i++ {
		d += 1.0
	}
	fmt.Println(d)
}

実行結果は予想通り

$ go run loop1.go
1.6777216e+07

となる1。 念のため float64 でも試してみよう。

package main

import "fmt"

func main() {
	var d float64 = 0.0
	for i := 0; i < 100000000; i++ {
		d += 1.0
	}
	fmt.Println(d)
}

結果は

$ go run loop2.go
1e+08

で,ちゃんと1億になる。 Go 言語では基本型のサイズが厳密に決まってるので(int, uint, uintptr は除く),浮動小数点数型の計算誤差についてもきちんと見積もれるはずである。

ちなみに

package main

import "fmt"

func main() {
	for d := 0.0; d < 1.0; d += 0.1 {
		fmt.Println(d)
	}
}

とすると2

$ go run loop3.go
0
0.1
0.2
0.30000000000000004
0.4
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999

ってなことになる3 ので浮動小数点数型の変数をループカウンタにするのは止めましょうね。 約束だよ!

ブックマーク

参考図書

photo
プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
Alan A.A. Donovan Brian W. Kernighan 柴田 芳樹
丸善出版 2016-06-20
評価

スターティングGo言語 (CodeZine BOOKS) Go言語によるWebアプリケーション開発 Kotlinスタートブック -新しいAndroidプログラミング Docker実戦活用ガイド グッド・マス ギークのための数・論理・計算機科学

著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。

reviewed by Spiegel on 2016-07-13 (powered by G-Tools)


  1. float32 は32ビットサイズの浮動小数点数型で,符号部1ビット,指数部8ビット,仮数部23ビット,という内訳になっている(仮数部は仮数の小数点以下を表す)。つまり有効桁数が24ビット(10進数で約7桁)しかない。したがって今回のような「1づつ加算する動作を繰り返す」処理では16,777,216(=0xffffff+1)以降は「情報落ち」が発生する。ちなみに float64 は64ビットサイズで仮数部は52ビットあり,10進数にして約15桁の有効桁数になる。 [return]
  2. d := 0.0” と記述した場合,変数 dfloat64 として宣言・初期化される。厳密には定数 “0.0” は,いったん「型付けなし」の浮動小数点数として評価された後,変数宣言時に float64 に暗黙的に変換される。 Go 言語におけるこの定数の機能は何かと便利なので覚えておくとよいだろう。 [return]
  3. このような結果になるのは float32/float64 の浮動小数点数型の内部表現が2進数になっているため。たとえば 0.1 を2進数で表すと「0.000110011…」と循環しキリのいい値にならない。このため 0.1 を加算していくと「丸め誤差」が蓄積していくのである。 [return]