PHPのintval()にfloatを渡したときの話【その後】 #php #clang
PHPとはいってもネイティブの浮動小数点数型をそのまま使ってるのは間違いないのでC言語で同じ事やってみた。C言語らしくバイト列のHEXダンプを追加した、以下のようなコードを実行致します。
#include <vcl.h> #pragma hdrstop #include <tchar.h> #include <stdio.h> #include <stdlib.h> void test(double v) { unsigned long long *p = (unsigned long long *)&v; printf("%f\r\n", v); printf("%llx\r\n", *p); printf("%d\r\n", (int)v); } int _tmain(int argc, _TCHAR* argv[]) { puts("----"); test(0.201 * 1000); puts("----"); test(2.01 * 100); puts("----"); test(20.1 * 10); puts("----"); test(201); getchar(); return 0; }
以下のような結果になるわけです。
---- 201.000000 4069200000000000 201 ---- 201.000000 40691fffffffffff 200 ---- 201.000000 4069200000000000 201 ---- 201.000000 4069200000000000 201
やはり「0.201 * 1000」と「2.01 * 100」の結果は、内部的には異なる値になってるようだ。怖いねぇ。怖いから俺寝る。。。では何の解決にもならんので追試してみた。ついでにfloatの場合も試してみることにする。
#include <vcl.h> #pragma hdrstop #include <tchar.h> #include <stdio.h> #include <stdlib.h> int _tmain(int argc, _TCHAR* argv[]) { puts("----"); double x = 0.201 * 1000; double y = 2.01 * 100; unsigned long long *xp = (unsigned long long *)&x; unsigned long long *yp = (unsigned long long *)&y; printf("x=%f\r\n", x); printf("x=%llx\r\n", *xp); printf("y=%f\r\n", y); printf("y=%llx\r\n", *yp); if(x == y) { puts("x == y"); } else { puts("x != y"); } puts("----"); float x2 = 0.201 * 1000; float y2 = 2.01 * 100; unsigned long *xp2 = (unsigned long *)&x2; unsigned long *yp2 = (unsigned long *)&y2; printf("x2=%f\r\n", x2); printf("x2=%x\r\n", *xp2); printf("y2=%f\r\n", y2); printf("y2=%x\r\n", *yp2); if(x2 == y2) { puts("x2 == y2"); } else { puts("x2 != y2"); } getchar(); return 0; }
---- x=201.000000 x=4069200000000000 y=201.000000 y=40691fffffffffff x != y ---- x2=201.000000 x2=43490000 y2=201.000000 y2=43490000 x2 == y2
やはり「0.201 * 1000 != 2.01 * 100」なのだった。10進でデバッグプリントしてる限り何が悪いのか気づけないのである。浮動小数点数怖い!!なおfloatの場合は人間的な動作となったが、もちろん数値によってはdoubleの201と同じことが起こると考えられる。
浮動小数点数演算で誤差が出るのは当たり前だが、演算結果が「見た感じ同じである」という点で、ちょっとしたワナだよなー思った次第。これが、良くあるパターンで200.999999とかになっててくれれば話は早いのだが。やっぱ寝るか(ぉい