web-dev-qa-db-ja.com

PHP減算時の浮動小数点計算エラー

非常に奇妙な問題があります。 1つが数学演算の結果である2つのfloat変数を減算すると、誤った値が得られます。

例:

var_dump($remaining);
var_dump($this->hours_sub['personal']);
echo $remaining-$this->hours_sub['personal'];

これは出力です:

float 5.4
float 1.4
5.3290705182008E-15

5.4-1.4にする必要があります4 2つの値を追加すると、結果は正しいです。

私の間違いはどこですか?丸めの問題にはなりません。

20
RubbelDeCatc

それでも誰かがこのページに到達しても、浮動小数点数の減算によりエラーまたは奇妙な値が発生するという同様の問題が発生します。この問題についてもう少し詳しく説明したいと思います。

PHP=に直接関係するものではなく、バグでもありません。ただし、すべてのプログラマーがこの問題に注意する必要があります。

この問題は20年前にも多くの命を奪いました。

1991年2月25日、MIM-104 Patriotミサイルバッテリーの浮動小数点数計算におけるこの問題により、サウジアラビアのダーランに到着するスカッドミサイルを迎撃できなくなり、米陸軍の第14クォーターマスター分遣隊から28人の兵士が死亡しました。

しかし、なぜそれが起こるのですか?

その理由は、浮動小数点値は限られた精度を表すためです。そのため、値は処理後に同じ文字列表現を持たない場合があります。また、スクリプトに浮動小数点値を書き込んで、数値演算を行わずに直接出力することも含まれます。

簡単な例:

$a = '36';
$b = '-35.99';
echo ($a + $b);

あなたはそれが0.01を印刷することを期待するでしょう?しかし、それは0.009999999999998のような非常に奇妙な答えを出力します

他の数値と同様に、doubleまたはfloatの浮動小数点数は、0と1の文字列としてメモリに格納されます。浮動小数点が整数とどのように異なるかは、0と1をどのように解釈するかという点にあります。保管方法には多くの標準があります。

浮動小数点数は通常、左から右に、符号ビット、指数フィールド、および仮数または仮数としてコンピューターデータにパックされます。

十分なスペースがないため、10進数は2進数では適切に表現されません。つまり、1/30.3333333...とまったく同じように表現することはできません。なぜ私たちは0.01を2進数の浮動小数点数として表現できないのかという理由も同じです。 1/1000.00000010100011110101110000.....で、繰り返し10100011110101110000が含まれます。

0.01がバイナリで01000111101011100001010の簡略化されたシステム切り捨て形式で保持されている場合、10進数に変換すると、システムに応じて0.0099999....のように読み取られます(64ビットコンピューターでは32ビットよりもはるかに正確です)。この場合、オペレーティングシステムは、見た目どおりに印刷するか、より人間が読みやすい方法で印刷するかを決定します。したがって、それをどのように表現するかはマシンに依存します。しかし、さまざまな方法で言語レベルで保護できます。

を使用して結果をフォーマットする場合

echo number_format(0.009999999999998, 2);

0.01が出力されます。

これは、この場合、読み取り方と必要な精度を指示するためです。

Number_format()が唯一の関数ではないことに注意してください。他のいくつかの関数と方法を使用して、プログラミング言語に精度の期待値を伝えることができます。

60
Selay

Number_format()を使用する以外に、正しい結果を取得する方法は3つあります。 1つは、次のように少し計算を行うことです。

<?php

$a = '36';
$b = '-35.99';

$a *= 100;
$b *= 100;

echo (($a + $b)/100),"\n";

参照 デモ

または、単にprintf()を使用することもできます。

<?php

$a = '36';
$b = '-35.99';

printf("\n%.2f",($a+$b));

参照 デモ

精度指定子がない場合、printf()の結果には、次のように小数点以下0が含まれることに注意してください。0.010000

次のように、BC数学関数bcadd()を利用することもできます。

<?php
$a = '36';
$b = '-35.99';
echo "\n",bcadd($a,$b,2);

参照 デモ

3
slevy1

これは私のために働きました:

<?php
    $a = 96.35;
    $b = 96.01;

    $c = ( ( floor($a * 100) - floor($b * 100) ) / 100 );

    echo $c; // should see 0.34 exactly instead of 0.33999999999999
?>

この問題は浮動小数点の減算演算で発生するため、整数演算に変換し、結果を再び浮動小数点にバックアップすることでそれを排除することにしました。

基本的には他の関数で結果をまとめるのではなく、計算のエラーを防ぐので、私はその解決策を非常に好みます。

1
mjaning