浮動小数点数の話 2013年度版 hnw 第70回PHP勉強会(2013/07/22) 発表資料 13年7月23日火曜日
浮動小数点数の話2013年度版
hnw第70回PHP勉強会(2013/07/22) 発表資料
13年7月23日火曜日
自己紹介
@hnw / id:hnw
勤務先:KLab株式会社
カレーとバグが大好物
最近の興味:Zend OPcache
改造しがいがある、最近イチオシのオモチャ
13年7月23日火曜日
今日おはなしすること
浮動小数点数の怖い話
round関数の近況
MySQLでの小数の扱い
13年7月23日火曜日
浮動小数点数の怖い話
round関数の近況
MySQLでの小数の扱い
13年7月23日火曜日
浮動小数点数の怖い話(1)
例:ゲーム内のアイテムの売買機能
プレイヤーはアイテムを店で定価で買える
定価の10%増しで、NPCが買い取ってくれる
端数が出た場合、小数点以下切り上げ(5.5→6)
13年7月23日火曜日
浮動小数点数の怖い話(1)
例:ゲーム内のアイテムの売買機能
プレイヤーはアイテムを店で定価で買える
定価の10%増しで、NPCが買い取ってくれる
端数が出た場合、小数点以下切り上げ(5.5→6)
罠に気づきましたか?
13年7月23日火曜日
浮動小数点数の怖い話(2)定価100コインのアイテムの売価が111コインになる!
100の10%増しなら110 のはずでは…?
実験してみる(1.1倍して切り上げ)
13年7月23日火曜日
何が問題だったか?1.1倍したのが問題
1.0や0.5はピッタリ表現できるが、0.1だと誤差が入る
11.0倍して10.0で割っていれば問題なかった
13年7月23日火曜日
補足(1)
浮動小数点数の性質からくる問題
浮動小数点数は2進数なので、1/5は循環小数になる
有限桁(53bit)で打ち切られるので、誤差が入る
PHPに限らず、多くの言語で起こる現象
10進小数クラスや有理数クラスを持つ言語もある
13年7月23日火曜日
補足(2)
浮動小数点数演算で誤差を避ける方法は?
浮動小数点数で「ピッタリ」表せる数を使うこと
誤差が入りうる計算を後回しにすること
整数演算だけで済む仕様に誘導するのも手
13年7月23日火曜日
補足(3)
浮動小数点数で無理ならgmp関数を使ってもよい
BC Mathは素性が不明だわ遅いわでイマイチな印象
「その処理をPHPでやるのが正解か?」も要検討
13年7月23日火曜日
浮動小数点数の怖い話
round関数の近況
MySQLでの小数の扱い
13年7月23日火曜日
こんなことがありましたPHP 5.2.xのround関数の実装がイマイチというネタで炎上
http://d.hatena.ne.jp/hnw/20070515
日記書き始めて10エントリ目くらいだった
まつもとゆきひろさんの日記で言及→DIS大会
13年7月23日火曜日
応戦してた
どう見てもヤバい人です。
13年7月23日火曜日
当時のround関数の概要
round(x)の実装が普通と違った
普通の実装:x > 0 なら floor(x+0.5)
PHPの実装:x > 0 なら floor(x+0.50000000001)
13年7月23日火曜日
当時のround関数の概要
round(x)の実装が普通と違った
普通の実装:x > 0 なら floor(x+0.5)
PHPの実装:x > 0 なら floor(x+0.50000000001)↑ 意味わからん
13年7月23日火曜日
議論の概要
PHP以外の人「これだからPHPは」
僕「指摘が浮動小数点数的に間違ってるけど?」 「RubyやPythonやPerlのコレもバグじゃね?」
浮動小数点数まわりのバグが結構見つかった
PHPがひどい点については基本スルー
13年7月23日火曜日
今どうなってるか(1)
PHP 5.3以降、round関数の仕様と実装が変わりました
詳細:https://wiki.php.net/rfc/rounding
x > 0 なら floor(x+0.5) と、他の言語と同様の実装
大きい数にだけ起こる、直しにくいバグが残っている
http://d.hatena.ne.jp/hnw/20110407
13年7月23日火曜日
今どうなってるか(2)
僕が当時指摘したバグはRuby 1.8.7とPython 2.7.3ですべて修正されている
すげえ!
なおPerl 5.14.4では修正されていない模様
13年7月23日火曜日
浮動小数点数の怖い話
round関数の近況
MySQLでの小数の扱い
13年7月23日火曜日
MySQLでの小数
小数リテラルは固定小数点数になる
固定小数点数=桁数と小数点の位置が決まっている表現
MySQLではDECIMAL型が対応する
DECIMAL(10, 2) : 全10桁、小数以下2桁
13年7月23日火曜日
固定小数点数の世界誤差の問題から解放される
これで平和が訪れた!
= true
13年7月23日火曜日
固定小数点数の世界誤差の問題から解放される
これで平和が訪れた!
と思ったでしょ?残念!
= true
13年7月23日火曜日
浮動小数点数の恐怖再び文字列リテラルに対して加減算→自動キャスト
文字列を数値にキャスト→浮動小数点数になる
= false
13年7月23日火曜日
イマイチなPDOの仕様
PDOはプレースホルダの展開時にPHP変数の型が文字列なら必ずクォーティングする
PDO::PARAM_INTの指定はbool型のみ影響する
参照:http://stackoverflow.com/questions/833510/php-pdobindparam-data-types-how-does-it-work
数値なのに文字列型になっている場合は要注意
13年7月23日火曜日
小数を避ければ平気か「DB上で小数なんて使わないから自分には関係ない」とか思ってませんか
小数点が無くても浮動小数点数を経由します
= true
13年7月23日火曜日
何が起きたのか?文字列をキャストして浮動小数点数になった
2^53以上の整数は浮動小数点数で正しく表現できない
前のページの例では、1を足したはずが2増えた
10進で16桁以上の整数を扱う場合は要注意
BIGINT型など
13年7月23日火曜日
まとめ
浮動小数点数は罠がたくさん
罠を避けるには知識が必要
PHPのround関数は昔よりマトモになった
MySQLにも浮動小数点数の罠がある
PHPだとPDOがイマイチなせいで罠を踏みやすい
13年7月23日火曜日
ご静聴ありがとうございました
13年7月23日火曜日