6

次のコードで、乗算アプローチでは丸め誤差が発生しないのに、累積加算アプローチでは丸め誤差が発生するのはなぜですか?

function get_value() { return 26.82; }

function a($quantity) {
    $value_excluding_vat = get_value();
    $value_including_vat = round($value_excluding_vat * (1 + (20 / 100)),2);
    $total_nett = 0;
    $total_gross = 0;
    for($i=0; $i<$quantity; $i++) {
        $total_nett += $value_excluding_vat;
        $total_gross += $value_including_vat;
    }
    return array(
        $total_nett,
        $total_gross
    );
}

function b($quantity) {
    $value_excluding_vat = get_value();
    $value_including_vat = round($value_excluding_vat * (1 + (20 / 100)),2);
    return array(
        $quantity * $value_excluding_vat,
        $quantity * $value_including_vat
    );
}

$totals = a(1000);
print_r($totals);
echo $totals[1] - $totals[0];
echo "\n\n";
$totals = b(1000);
print_r($totals);
echo $totals[1] - $totals[0];

ここに私の出力があります:

Array
(
    [0] => 26820
    [1] => 32180
)
5360.0000000005

Array
(
    [0] => 26820
    [1] => 32180
)
5360
4

3 に答える 3

6

まず、基数 10 では有理数であるが、2 進浮動小数点表現では有理数ではない数が多数あることを考慮してください。たとえば、26.82 の浮動小数点値は実際には26.8200000000000002842170943040400743484497

当然、これを自分自身に追加し続けると、いくつかのエラーが忍び込みますが、有効数字が 15 桁までは問題ないはずです。これを 1000 回追加すると、実際の合計は次のようになります。26819.9999999997671693563461303710937500000000

しかし、興味深い質問は、26.82 を 1000.0 で掛けると、26820.0000000000000000000000000000000000000000どうやってそれを行うのかということです。

答えは単純に、26820.0 に正確なバイナリ表現があり、乗算演算はそれを見つけるのに十分スマートです。1001.0 を掛けて 26.82 を引いても、正確な答えが得られます。

ここにいくつかの興味深いリンクがあります

于 2013-08-12T10:54:42.417 に答える
3

float 値のマシン表現に問題がある可能性があります(警告ブロックを参照)

たとえば、21470.73 は 21470.729999..9994561 または 21470.73000...0001231 のようになりますが、計算方法によって異なります。

計算する前にgross_total_so_farとのような一時的な値を丸めるようにしてくださいnett_total_so_far$total

于 2013-08-09T14:50:21.520 に答える