Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
PHPで任意精度演算を行って 「正しい」金額計算をする方法 2023年3月24日 / PHPerKaigi 2023 • Day 1 合同会社テンマド 山岡広幸
Slide 2
Slide 2 text
さて、突然ですが
Slide 3
Slide 3 text
0.1 + 0.2 = ?? php > var_dump(0.1 + 0.2);
Slide 4
Slide 4 text
0.1 + 0.2 ≠ 0.3 php > var_dump(0.1 + 0.2); float(0.30000000000000004) 🤔
Slide 5
Slide 5 text
自己紹介 - 山岡広幸 / Twitter: @hiro_y - Webアプリケーションエンジニア - PHPは3から。最近はNode.js多め - 合同会社テンマド 代表社員
Slide 6
Slide 6 text
- 受託制作・コンサルティング - 各種アドバイザー、各種制作(デザイン・開発) - 例: 株式会社イノベーター・ジャパン 社外CTO - 自社サービスの運営 - iruca、mimemoなど 👨💻👨💻👩💻👩💻
Slide 7
Slide 7 text
0.1 + 0.2 が 0.3 にならない世界
Slide 8
Slide 8 text
我々は PHP 8 の 時代を生きているので
Slide 9
Slide 9 text
型で考えてみる
Slide 10
Slide 10 text
0.1の型? php > var_dump(0.1); float(0.1) php > var_dump(gettype(0.1)); string(6) "double" 🧐
Slide 11
Slide 11 text
マニュアルを見る https://www.php.net/manual/ja/language.types. fl oat.php
Slide 12
Slide 12 text
fl oat / double - PHPで、整数は int(integer)という型 - 10進数、16進数、8進数、2進数で指定可能 - 「整数以外」を表現するのに浮動小数点数を使う - fl oat(ただし倍精度)、double - intの上限(PHP_INT_MAX)を超える整数も対象
Slide 13
Slide 13 text
浮動小数点数 もう少しくわしく
Slide 14
Slide 14 text
2進数の世界 - コンピューターは2進数で動いている - 0 or 1(OFF or ON)のバイナリの世界 - 10進数の小数は2進数の世界で表現可能か? - 0.1(2進数)は2-1(10進数の0.5) 0.01(2進数)は2-2(10進数の0.25)
Slide 15
Slide 15 text
翻って、我々の世界 - 10進数で物事を考えている - 部分的に他の基数も使うが、10進数が基本 - 2進数をそのまま扱うのは不便すぎる - 10進数の0.1は2進数で正確に表現できない
Slide 16
Slide 16 text
浮動小数点数 - 10進数の実数を有限桁の2進数の近似値で表現 - 浮動小数点( fl oating-point) - -1 x 0.101010101010101 x 2-1 と -1 x 1.01010101010101 x 2-2 は同じ 符号、仮数、指数 - 複数フォーマットがある(そのうちの1つがIEEE 754)
Slide 17
Slide 17 text
不便すぎるので - 必要な桁数の精度を得るための演算処理を実装 - ただし、コストがかかるので非常に遅くなる… - そのうちの1つの方式が「任意精度演算」 - 多くの環境で実装されている
Slide 18
Slide 18 text
ここで体験談
Slide 19
Slide 19 text
新卒のころの話 - 新卒研修はCOBOL(その後Java) - 金融系のプロジェクトに配属 - 投資信託/ファンドのパフォーマンス評価 - たくさん小数点計算が必要だった
Slide 20
Slide 20 text
計算を何気なく実装 result = value * rate; 😡
Slide 21
Slide 21 text
Javaで十進演算するには - java.lang.BigDecimalを使う必要がある - プリミティブな型の fl oatを使ってはいけない - COBOLは10進数を言語でサポート(二進化十進数) - C#ならdecimal、RubyならBigDecimal
Slide 22
Slide 22 text
我らがPHPの場合
Slide 23
Slide 23 text
言語として 任意精度計算の 仕組みがない 😵
Slide 24
Slide 24 text
だけど大丈夫 https://www.php.net/manual/ja/language.types. fl oat.php
Slide 25
Slide 25 text
BC Math 関数 - GNU bc(Basic Calculator)からのfork - https://www.gnu.org/software/bc/ - PHPを「--enable-bcmath」を付けて構築 - 関数: bcadd、bccomp、bcdiv、bcmod、bcmul、 bcpow、bcpowmod、bcscale、bcsqrt、bcsub
Slide 26
Slide 26 text
例えば足し算 php > var_dump(bcadd('0.1', '0.2', 1)); string(3) "0.3" php > var_dump(bcadd('0.1', '0.2', 20)); string(22) "0.30000000000000000000"
Slide 27
Slide 27 text
GMP 関数 - GNU Multiple Precision Arithmetic Library - https://gmplib.org/ - PHPを「--with-gmp」を付けて構築 - とてもたくさんの数学関数群
Slide 28
Slide 28 text
雑なベンチマーク
Slide 29
Slide 29 text
高コストな結果 # php test.php float: 0.040148973464966ms bcadd: 0.86464095115662ms 🥺
Slide 30
Slide 30 text
注意: 引数は文字列で - BC Math関数に渡す引数は「文字列」で - fl oat表記で渡すとstringにキャストされる - 例: 0.00001 は 1.0E-5 になってしまう - 数値を表す文字列ではないのでエラーになる可能性
Slide 31
Slide 31 text
一応、もう一つのやり方 - PHPで整数の計算には誤差が発生しない - つまり、小数も整数に変換した上で計算、 小数に戻せば誤差は発生しない - 複雑になってしまうし、 絶対に間違えるのでオススメできない
Slide 32
Slide 32 text
【結論】 BC Math関数を使おう
Slide 33
Slide 33 text
ただし - BC Math関数に用意されているのは 基本的な算術関数のみであることに注意 - 例えば、 fl oorやceil、roundのような関数はない - 雑に fl oor関数等に渡すと fl oatに変換されるので 計算誤差が発生する可能性がある
Slide 34
Slide 34 text
便利ライブラリ紹介 - Brick\Math - https://github.com/brick/math - GMP関数、BC Math関数、手計算の各実装 - 四捨五入、切り捨て/切り上げが実装されている
Slide 35
Slide 35 text
Brick\Math例 $result = BigDecimal::of('2.5') ->multipliedBy(BigInteger::of('2')) ->divideBy('0.3', 3, RoundingMode::HALF_UP);
Slide 36
Slide 36 text
さて、余談 // Google Chrome DevTools Console > 0.1 + 0.2 0.30000000000000004
Slide 37
Slide 37 text
JavaScriptも同様 - 浮動小数点数、IEEE 754に準ずる - 解決方法: ライブラリを使う - https://github.com/MikeMcl/decimal.js - BigDecimalのプロポーザルあり - https://github.com/tc39/proposal-decimal
Slide 38
Slide 38 text
まとめ - 浮動小数点数の扱いには注意が必要 - BC Math関数を使えばOK - 必要に応じてライブラリを利用して楽をしよう - フロントエンド側も配慮が必要
Slide 39
Slide 39 text
実は身近な小数点計算 - 消費税、金利、住宅ローン…
Slide 40
Slide 40 text
正しい小数点計算を していくために
Slide 41
Slide 41 text
任意精度演算を 使っていこう (フロントもね!)
Slide 42
Slide 42 text
ありがとう ございました!