2.3 * 100,000 のナゾ
2008-03-27


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

今日(あけてるので昨日か)の昼間、「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 関数を代わりに使用してください。
PHP: 浮動小数点数 - Manual
とかあるので、まぁ精度の問題かしら、と思えなくもないのだが。

ちなみに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


続きを読む

[JavaScript]
[.NET]
[PHP]

コメント(全0件)
コメントをする


記事を書く
powered by ASAHIネット