31

PHPでの浮動小数点問題の回避策をいくつか見つけました。

php.ini設定precision = 14

342349.23 - 341765.07 = 584.15999999992 // floating point problem

php.iniの設定、たとえばprecision = 8

342349.23 - 341765.07 = 584.16 // voila!

デモ:http ://codepad.org/r7o086sS

それはどれくらい悪いですか?

1.正確な2桁の計算(お金)が必要な場合、このソリューションを信頼できますか?

2.そうでない場合、このソリューションが失敗したときの明確な例を教えてください。

編集:3。どのphp.ini.precision値が最適な2桁の金額、金額の計算に適しているか


  • 整数計算(float * 100 =セント)は使用できないことに注意してください。それには遅すぎます。
  • 10^6を超える数値には取り組みません
  • 数字を比較する必要はありません

アップデート

@Babaの答えは良いですが、彼は彼のテストで使用precision=20precision=6ました...それでも私はそれがうまくいくかどうかわかりません。

次のことを考慮してください。

たとえばprecision = 8、私がしているのは足し算+と引き算だけです。-

A + B = C

A - B = C

質問1: AとBが小数点以下の桁数である0..999999.99の間の数値では、精度の回避策は失敗しますか?もしそうなら、私に例を教えてください。

簡単なテストでうまくいきます。

// if it fails what if I use 9,10,11 ???
// **how to find when it fails??? **
ini_set('precision', 8); 
for($a=0;$a<999999.99;$a+=0.01) {
  for($b=0;$b<999999.99;$b+=0.01) {
     // mind I don't need to test comparision (round($a-$b,2) == ($a-$b))
     echo ($a + $b).','.($a - $b)." vs ";
     echo round($a + $b, 2).','.round($a - $b, 2)."\n";
  }
}

しかし、明らかに99999999 * 2仕事が大きすぎるので、このテストを実行できません

質問2:精度の回避策が失敗した場合の見積もり/計算方法は?そのようなクレイジーなテストなしで?数学的な*、それに対する正解はありますか?計算方法は失敗するかどうか?

*浮動小数点の計算が機能することを知る必要はありませんが、精度とAとBの範囲を知っていれば回避策が失敗した場合


セントとbcmathが最善の解決策であることを私は本当に知っていることを覚えておいてください。しかし、それでも、減算と加算の回避策が失敗するかどうかはわかりません

4

5 に答える 5

49

序章

浮動小数点演算は、多くの人にとって難解な主題と見なされています。浮動小数点はコンピュータシステムに遍在しているため、これはかなり驚くべきことです。ほとんどの小数は2進数として正確に表現されていないため、丸めが行われています。良いスタートは、すべてのコンピューター科学者が浮動小数点演算について知っておくべきことです

質問

質問1

正確な2桁の計算(お金)が必要な場合、このソリューションに頼ることはできますか?

回答1

正確な2桁が必要な場合 、答えは「いいえ 」です。phpの精度設定を使用して、たとえそうである場合でも、常に2桁のnot going to work on numbers higher than 10^6小数を確認することはできません。

計算中に、長さが8未満の場合、精度の長さが増加する可能性があります。

質問2

そうでない場合は、このソリューションが失敗したときの明確な例を教えてください。

回答2

ini_set('precision', 8); // your precision
$a =  5.88 ; // cost of 1kg
$q = 2.49 ;// User buys 2.49 kg
$b = $a * 0.01 ; // 10% Discount only on first kg ;
echo ($a * $q) - $b;

出力

14.5824 <---- not precise 2 digits calculations even if precision is 8

質問3

どのphp.ini.precision値が、2桁の金額の計算に最適ですか?

回答3

精度とお金の計算は2つの異なるものです...財務計算または浮動小数点の長さのベースとしてPHPの精度を使用することはお勧めできません

簡単なテスト

bcmathを使用していくつかの例を一緒に実行しないnumber_formatでください。minus

Base

$a = 342349.23;
$b = 341765.07;

Example A

ini_set('precision', 20); // set to 20 
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

出力

584.15999999997438863
584.15999999999996817    <----- Round having a party 
584.16
584.15  <-------- here is 15 because precision value is 20

Example B

ini_set('precision', 14); // change to  14 
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

出力

584.15999999997
584.16
584.16
584.16  <-------- at 14 it changed to 16

Example C

ini_set('precision', 6); // change to  6 
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

出力

584.16
584.16
584.16
584.00  <--- at 6 it changed to 00 

Example D

ini_set('precision', 3); // change to 3
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

出力 

584
584
584.16   <-------------------------------- They only consistent value 
0.00  <--- at 3 .. everything is gone 

結論

浮動小数点を忘れてcents、後で計算し、それ100が遅すぎる場合は単に使用するだけnumber_formatで、私には一貫しているように見えます。

アップデート

質問1:AとBが小数点以下の桁数である0..999999.99の間の数値では、精度の回避策は失敗しますか?もしそうなら、私に例を教えてください

の増分でのフォーム0は、ループの組み合わせの可能性についてです。 私は、誰もあなたのためにそのようなテストを実行したいとは思わないでしょう。999999.990.0199,999,9999,999,999,800,000,000

浮動小数点は有限の精度の2進数であるため、設定しようとするとprecision、精度を確保するための効果が制限されます。簡単なテストを次に示します。

ini_set('precision', 8);

$a = 0.19;
$b = 0.16;
$c = 0.01;
$d = 0.01;
$e = 0.01;
$f = 0.01;
$g = 0.01;

$h = $a + $b + $c + $d + $e + $f + $g;

echo "Total: " , $h , PHP_EOL;


$i = $h-$a;
$i = $i-$b;
$i = $i-$c;
$i = $i-$d;
$i = $i-$e;
$i = $i-$f;
$i = $i-$g;

echo $i , PHP_EOL;

出力

Total: 0.4
1.0408341E-17     <--- am sure you would expect 0.00 here ;

試す

echo round($i,2) , PHP_EOL;
echo number_format($i,2) , PHP_EOL;

出力

0
0.00    <------ still confirms number_format is most accurate to maintain 2 digit 

質問2:精度の回避策が失敗した場合の見積もり/計算方法は?そのようなクレイジーなテストなしで?数学的な*、それに対する正解はありますか?計算方法は失敗するかどうか?

浮動小数点には精度の問題がありますが、数学的な解決策については、

浮動小数点計算が機能することを知る必要はありませんが、精度とAとBの範囲を知っていれば、回避策が失敗した場合

ここに画像の説明を入力してください

そのステートメントが何を意味するのかわからない:)

于 2013-02-01T22:43:18.873 に答える
5

ドキュメントによると、precisionディレクティブは、数値を文字列にキャストするときに表示される数字を変更するだけです。

精度 integer
浮動小数点数で表示される有効桁数。

したがって、基本的にはnumber_format()またはmoney_format()の非常に複雑な代替手段ですが、フォーマットオプションが少なく、気付かない可能性のある他の副作用が発生する可能性があります。

<?php

$_POST['amount'] = '1234567.89';

$amount = floatval($_POST['amount']);
var_dump($amount);

ini_set('precision', 5);
$amount = floatval($_POST['amount']);
var_dump($amount);

..。

float(1234567.89)
float(1.2346E+6)

編集:

私は主張します:この設定は、PHPが数値を使って数学計算を行う方法を変更しません。これは、浮動小数点数(整数でさえも!)から文字列に変換するときにフォーマットオプションを変更する魔法の方法です。例:

<?php

ini_set('precision', 2);

$amount = 1000;
$price = 98.76;
$total = $amount*$price;

var_dump($amount, $total);

ini_set('precision', 15);
var_dump($amount, $total);

...プリント:

int(1000)
float(9.9E+4)
int(1000)
float(98760)

これはそれを示しています:

  1. 浮動小数点の計算は影響を受けず、表示のみが変更されます
  2. 整数はすべての場合に影響を受けません
于 2013-01-29T16:24:43.247 に答える
4

この興味深いサイトを問題に引用します。(評判は期待されていません:)しかしそれは言及されるべきです:

この(浮動小数点)問題を回避するにはどうすればよいですか?

それはあなたがしている計算の種類に依存します。

  • 特にお金を扱う場合に、結果を正確に合計する必要がある場合は、特別な10進数のデータ型を使用してください。

  • これらの余分な小数点以下の桁数をすべて表示したくない場合は、結果を表示するときに、結果を固定の小数点以下の桁数に丸めてフォーマットするだけです。

  • 使用可能な10進数のデータ型がない場合は、整数を使用することもできます。たとえば、完全にセントで金額を計算します。しかし、これはより多くの作業であり、いくつかの欠点があります。

このサイトには、 PHPの基本的なヒントもいくつか含まれています

整数を使用するか、そのための特別なDecimal型を作成します。

bcmathを使用する場合:その値をSQLクエリまたは他の外部プログラムに渡す場合は注意してください。彼らが精度を認識していない場合、それは望ましくない副作用につながる可能性があります。(ありそうなこと)

于 2013-01-29T17:15:24.420 に答える
1

思いついた結果を単純に丸めると、サーバー全体の構成を変更しなくても、浮動小数点の問題が解決されると思います。

round(342349.23 - 341765.07, 2) = 584.16
于 2013-01-29T16:37:20.460 に答える
0

精度=8を使用する場合、8桁の数値を使用する場合、8桁目を確認することはできません。これは、9桁目の四捨五入から1ずれている可能性があります。

例えば

12345678.1 -> 12345678
12345678.9 -> 12345679

これはそれほど悪くはないように思われるかもしれませんが、検討してください

   (11111111.2 + 11111111.2) + 11111111.4
-> (11111111)                + 11111111.4
-> 22222222.4
-> 22222222

一方、precision = 9を使用している場合、これはに22222222.8丸められ22222223ます。

加算と減算を行うだけの場合は、これらの種類の計算で丸めを回避するために必要な精度よりも少なくとも2桁以上の精度を使用する必要があります。乗算または除算を行う場合は、さらに必要になる場合があります。最低限必要なものを使用すると、あちこちで数字が失われる可能性があります。

したがって、あなたの質問に答えるために、運が良ければ、phpが計算に高精度を使用し、結果を低精度で保存する場合にのみ、それを回避できる可能性があります(その後、その数値を使用して続行することはありません他の計算)ですが、(少なくとも)計算の最後の桁は完全に信頼できないため、一般的には非常に悪い考えです。

于 2013-02-08T23:35:34.367 に答える