PHPのintval()とfloor()の戻り値が低すぎますか?
-
03-07-2019 - |
質問
PHPのfloatデータ型は不正確であり、MySQLのFLOATはINTよりも多くのスペースを使用するため(そして不正確です)、常にINTとして価格を格納し、格納する前に100倍して10進数がちょうど2になるようにします精度の場所。しかし、PHPは誤動作していると思います。コード例:
echo "<pre>";
$price = "1.15";
echo "Price = ";
var_dump($price);
$price_corrected = $price*100;
echo "Corrected price = ";
var_dump($price_corrected);
$price_int = intval(floor($price_corrected));
echo "Integer price = ";
var_dump($price_int);
echo "</pre>";
生成された出力:
Price = string(4) "1.15"
Corrected price = float(115)
Integer price = int(114)
驚いた。最終結果が予想より1低い場合、テストの出力は次のようになると予想していました。
Price = string(4) "1.15"
Corrected price = float(114.999999999)
Integer price = int(114)
これは、float型の不正確さを示します。しかし、なぜfloor(115)は114を返すのですか?
解決
簡単な修正としてこれを試してください:
$price_int = intval(floor($price_corrected + 0.5));
発生している問題はPHPのせいではなく、浮動小数点演算で実数を使用するすべてのプログラミング言語には同様の問題があります。
通貨計算の一般的な経験則は、フロートを使用しないことです(データベースでもスクリプトでも)。常にドルではなくセントを格納することで、あらゆる種類の問題を回避できます。セントは整数であり、自由に加算して、他の整数を掛けることができます。数字を表示するときは、必ず最後の2桁の前にドットを挿入してください。
115ではなく114を取得する理由は、 floor
が最も近い整数に切り捨てられるためです。したがって、floor(114.999999999)は114になります。より興味深い質問は、なぜ1.15 * 100が114.999999999その理由は、1.15が正確に115/100ではなく、非常に少ないため、100を掛けると、115より小さいビット数が得られるためです。
echo 1.15 * 100;
の機能の詳細は次のとおりです。
- 1.15を解析して2進浮動小数点数にします。これには丸めが含まれ、1.15に最も近い2進浮動小数点数を取得するために少し切り捨てられます。 (丸め誤差なしで)正確な数値を取得できない理由は、1.15の基数2に無限の数字があるためです。
- 100を解析して2進浮動小数点数にします。これには丸めが含まれますが、100は小さな整数であるため、丸め誤差はゼロです。
- 前の2つの数値の積を計算します。これには、最も近い2進浮動小数点数を見つけるための小さな丸めも含まれます。この操作では、丸め誤差がゼロになります。
- 2進浮動小数点数をドット付きの10進数の10進数に変換し、この表現を出力します。これには少し丸めることも含まれます。
PHPが驚くべき Corrected price = float(115)
(114.999 ...ではなく)を出力する理由は、 var_dump
が正確な数( !)、ただし、数値は n-2
(または n-1
)桁に丸められます。ここで、n桁は計算の精度です。これは簡単に確認できます:
echo 1.15 * 100; # this prints 115
printf("%.30f", 1.15 * 100); # you 114.999....
echo 1.15 * 100 == 115.0 ? "same" : "different"; # this prints `different'
echo 1.15 * 100 < 115.0 ? "less" : "not-less"; # this prints `less'
フロートを印刷する場合、フロートを印刷するときにすべての数字が表示されるとは限らないことを忘れないでください。
PHPフロートドキュメントの冒頭にある大きな警告も参照してください。
他のヒント
他の答えは、原因と問題の良い回避策をカバーしていると思います。
別の角度から問題を解決することを目指します:
MySQLで価格の値を保存するには、 10進数型。正確な値を小数点以下の桁で保存できます。
PHPは有効数字に基づいて丸めを行っています。不正確さを隠しています(2行目)。もちろん、床が来るとき、それはそれ以上何も知らず、ずっと下にそれを落とします。
たぶん、この「問題」の別の解決策かもしれません:
intval(number_format($problematic_float, 0, '', ''));
前述のように、これはPHP自体の問題ではありません。有限の浮動小数点値として表現できない分数の処理の問題であり、切り上げの際に文字が失われることになります。
解決策は、浮動小数点値で作業しているときに精度を維持する必要があることを確認することです。gmp関数またはBC数学関数を使用します-bcpow、bcmulなど。問題は簡単に解決されます。
E.gの代わりに $ price_corrected = $ price * 100;
use $ price_corrected = bcmul($ price、100);