29
浮動小数点数の話 2013年度版 hnw 第70回PHP勉強会(2013/07/22) 発表資料 13723日火曜日

浮動小数点数の話 2013年度版

Embed Size (px)

DESCRIPTION

第70回PHP勉強会の発表資料です。

Citation preview

Page 1: 浮動小数点数の話 2013年度版

浮動小数点数の話2013年度版

hnw第70回PHP勉強会(2013/07/22) 発表資料

13年7月23日火曜日

Page 2: 浮動小数点数の話 2013年度版

自己紹介

@hnw / id:hnw

勤務先:KLab株式会社

カレーとバグが大好物

最近の興味:Zend OPcache

改造しがいがある、最近イチオシのオモチャ

13年7月23日火曜日

Page 3: 浮動小数点数の話 2013年度版

今日おはなしすること

浮動小数点数の怖い話

round関数の近況

MySQLでの小数の扱い

13年7月23日火曜日

Page 4: 浮動小数点数の話 2013年度版

浮動小数点数の怖い話

round関数の近況

MySQLでの小数の扱い

13年7月23日火曜日

Page 5: 浮動小数点数の話 2013年度版

浮動小数点数の怖い話(1)

例:ゲーム内のアイテムの売買機能

プレイヤーはアイテムを店で定価で買える

定価の10%増しで、NPCが買い取ってくれる

端数が出た場合、小数点以下切り上げ(5.5→6)

13年7月23日火曜日

Page 6: 浮動小数点数の話 2013年度版

浮動小数点数の怖い話(1)

例:ゲーム内のアイテムの売買機能

プレイヤーはアイテムを店で定価で買える

定価の10%増しで、NPCが買い取ってくれる

端数が出た場合、小数点以下切り上げ(5.5→6)

罠に気づきましたか?

13年7月23日火曜日

Page 7: 浮動小数点数の話 2013年度版

浮動小数点数の怖い話(2)定価100コインのアイテムの売価が111コインになる!

100の10%増しなら110 のはずでは…?

実験してみる(1.1倍して切り上げ)

13年7月23日火曜日

Page 8: 浮動小数点数の話 2013年度版

何が問題だったか?1.1倍したのが問題

1.0や0.5はピッタリ表現できるが、0.1だと誤差が入る

11.0倍して10.0で割っていれば問題なかった

13年7月23日火曜日

Page 9: 浮動小数点数の話 2013年度版

補足(1)

浮動小数点数の性質からくる問題

浮動小数点数は2進数なので、1/5は循環小数になる

有限桁(53bit)で打ち切られるので、誤差が入る

PHPに限らず、多くの言語で起こる現象

10進小数クラスや有理数クラスを持つ言語もある

13年7月23日火曜日

Page 10: 浮動小数点数の話 2013年度版

補足(2)

浮動小数点数演算で誤差を避ける方法は?

浮動小数点数で「ピッタリ」表せる数を使うこと

誤差が入りうる計算を後回しにすること

整数演算だけで済む仕様に誘導するのも手

13年7月23日火曜日

Page 11: 浮動小数点数の話 2013年度版

補足(3)

浮動小数点数で無理ならgmp関数を使ってもよい

BC Mathは素性が不明だわ遅いわでイマイチな印象

「その処理をPHPでやるのが正解か?」も要検討

13年7月23日火曜日

Page 12: 浮動小数点数の話 2013年度版

浮動小数点数の怖い話

round関数の近況

MySQLでの小数の扱い

13年7月23日火曜日

Page 13: 浮動小数点数の話 2013年度版

こんなことがありましたPHP 5.2.xのround関数の実装がイマイチというネタで炎上

http://d.hatena.ne.jp/hnw/20070515

日記書き始めて10エントリ目くらいだった

まつもとゆきひろさんの日記で言及→DIS大会

13年7月23日火曜日

Page 14: 浮動小数点数の話 2013年度版

応戦してた

どう見てもヤバい人です。

13年7月23日火曜日

Page 15: 浮動小数点数の話 2013年度版

当時のround関数の概要

round(x)の実装が普通と違った

普通の実装:x > 0 なら floor(x+0.5)

PHPの実装:x > 0 なら floor(x+0.50000000001)

13年7月23日火曜日

Page 16: 浮動小数点数の話 2013年度版

当時のround関数の概要

round(x)の実装が普通と違った

普通の実装:x > 0 なら floor(x+0.5)

PHPの実装:x > 0 なら floor(x+0.50000000001)↑ 意味わからん

13年7月23日火曜日

Page 17: 浮動小数点数の話 2013年度版

議論の概要

PHP以外の人「これだからPHPは」

僕「指摘が浮動小数点数的に間違ってるけど?」 「RubyやPythonやPerlのコレもバグじゃね?」

浮動小数点数まわりのバグが結構見つかった

PHPがひどい点については基本スルー

13年7月23日火曜日

Page 18: 浮動小数点数の話 2013年度版

今どうなってるか(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日火曜日

Page 19: 浮動小数点数の話 2013年度版

今どうなってるか(2)

僕が当時指摘したバグはRuby 1.8.7とPython 2.7.3ですべて修正されている

すげえ!

なおPerl 5.14.4では修正されていない模様

13年7月23日火曜日

Page 20: 浮動小数点数の話 2013年度版

浮動小数点数の怖い話

round関数の近況

MySQLでの小数の扱い

13年7月23日火曜日

Page 21: 浮動小数点数の話 2013年度版

MySQLでの小数

小数リテラルは固定小数点数になる

固定小数点数=桁数と小数点の位置が決まっている表現

MySQLではDECIMAL型が対応する

DECIMAL(10, 2) : 全10桁、小数以下2桁

13年7月23日火曜日

Page 22: 浮動小数点数の話 2013年度版

固定小数点数の世界誤差の問題から解放される

これで平和が訪れた!

= true

13年7月23日火曜日

Page 23: 浮動小数点数の話 2013年度版

固定小数点数の世界誤差の問題から解放される

これで平和が訪れた!

と思ったでしょ?残念!

= true

13年7月23日火曜日

Page 24: 浮動小数点数の話 2013年度版

浮動小数点数の恐怖再び文字列リテラルに対して加減算→自動キャスト

文字列を数値にキャスト→浮動小数点数になる

= false

13年7月23日火曜日

Page 25: 浮動小数点数の話 2013年度版

イマイチなPDOの仕様

PDOはプレースホルダの展開時にPHP変数の型が文字列なら必ずクォーティングする

PDO::PARAM_INTの指定はbool型のみ影響する

参照:http://stackoverflow.com/questions/833510/php-pdobindparam-data-types-how-does-it-work

数値なのに文字列型になっている場合は要注意

13年7月23日火曜日

Page 26: 浮動小数点数の話 2013年度版

小数を避ければ平気か「DB上で小数なんて使わないから自分には関係ない」とか思ってませんか

小数点が無くても浮動小数点数を経由します

= true

13年7月23日火曜日

Page 27: 浮動小数点数の話 2013年度版

何が起きたのか?文字列をキャストして浮動小数点数になった

2^53以上の整数は浮動小数点数で正しく表現できない

前のページの例では、1を足したはずが2増えた

10進で16桁以上の整数を扱う場合は要注意

BIGINT型など

13年7月23日火曜日

Page 28: 浮動小数点数の話 2013年度版

まとめ

浮動小数点数は罠がたくさん

罠を避けるには知識が必要

PHPのround関数は昔よりマトモになった

MySQLにも浮動小数点数の罠がある

PHPだとPDOがイマイチなせいで罠を踏みやすい

13年7月23日火曜日

Page 29: 浮動小数点数の話 2013年度版

ご静聴ありがとうございました

13年7月23日火曜日