2.3 * 100,000 のナゾ2008年03月27日 04時01分12秒

ちと意味わかんないんすけど

今日(あけてるので昨日か)の昼間、「PHPで 2.3を10万倍して int に cast したら誤差発生」という現象に見舞われた。最初はまたPHPのクサレ素敵仕様かバグかと思ったが、試してみたところ少なくともJavaScriptとC#(1.0)でまったく同様の結果になった。なので他の言語でもある話なのかも。

ちょっと理屈がわかんないんだよなー。

まずはすなおに出力

まずはこれ。

C:\Documents and Settings\dara-j>php -r "var_dump(2.3 * 100000);"
float(230000)

C:\Documents and Settings\dara-j>

まぁ、普通です。

キャストしてみる

んで、これをintにcastしてみる。

C:\Documents and Settings\dara-j>php -r "var_dump((int)(2.3 * 100000));"
int(229999)

C:\Documents and Settings\dara-j>

...えーっと...

もっともマニュアルの「浮動小数点」の説明を見ると、

こうなる理由のひとつとして、「有限小数に変換できない分数がある」 という事実があります。たとえば 1/3 を小数で表そうとすると 0.3333333. . . となります。

よって、小数の最後の桁を信用してはいけませんし、 小数が等しいという比較を行ってはいけません。より高い精度が必要な場合には、 任意精度数学関数または gmp 関数を代わりに使用してください。
とかあるので、まぁ精度の問題かしら、と思えなくもないのだが。

ちなみにJSでも

alert( Math.floor( 2.3 * 100000 ) );
で「229999」になる。

これは納得いかないんだが

こんどは1万倍と100万倍をやってみると、なんだか納得いかない。

C:\Documents and Settings\dara-j>php -r "var_dump((int)(2.3 * 10000));"
int(23000)

C:\Documents and Settings\dara-j>php -r "var_dump((int)(2.3 * 1000000));"
int(2300000)

C:\Documents and Settings\dara-j>
...なぜに意図どおりの結果が??いや、これで正しいんだけど。ってことは100,000だけ仲間はずれっすか。

さらに納得いかないんだが

よし、じゃあ10万倍が特別なのはよしとしましょう。でも、これは?

C:\Documents and Settings\dara-j>php -r "var_dump((int)(2.3 * 10 * 10000));"
int(230000)

...えええっと... えええっと...

内部での実行順序の違いってのがあるのかも知らんけど、* 100000と違う結果になるのはちとひどくないか?

この現象って、冒頭でも書いたけど別にPHP固有の現象ではなく、少なくともJavaScriptとC#ではまったく同じ結果になる。ちなみにここまでのコード例ではcastしてるけど、floor()使っても同じ結果になる。

10進型が使えりゃ気にしないのだが

実際、2.3 * 100000 で誤差がでると困るのでなんとか対応せにゃならなかったんだけど、さすがに * 10 してから * 10000 って、なんだかバッドノウハウくさいので、結局いったんstringを経由させてからintにキャストすることにしたんだけども。

こんな感じ。

function hoge($v) {
	// 元はこんな感じ
	// return (int)( $v * 100000 );
	
	// それをこんな感じに
	$v = $v * 100000;
	return (int)( "$v" );
	
	// なんというか、なんでこんなことを...
}

...なんだかなぁ。10進型(decimal)が使えればこんなことしなくてすむんだけどね。

あ、もちろんBCMath任意精度数学関数ってのがあるのは知ってるし、このケースで bcmul() なら問題ないことも確認してるんだけど、今回は実行環境にlibbcmathがなかったので。

オマケ

さらにさらに、これどうよ?

C:\Documents and Settings\dara-j>php -r "var_dump((int)(2.3 * 100 * 1000));"
int(229999)

C:\Documents and Settings\dara-j>php -r "var_dump((int)(2.3 * 1000 * 100));"
int(230000)

...ここまでくると、もはやどうでもいいや

コメント

コメントをどうぞ

※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。

※なお、送られたコメントはブログの管理者が確認するまで公開されません。

名前:
メールアドレス:
URL:
コメント:

トラックバック

このエントリのトラックバックURL: http://dara-j.asablo.jp/blog/2008/03/27/2853397/tb

※なお、送られたトラックバックはブログの管理者が確認するまで公開されません。