6

私はPHPでピタゴラス平均を実装しています。算術平均と幾何平均は簡単ですが、信頼できる調和平均の実装を思い付くのに非常に苦労しています。

これはWolframAlphaの定義です:

WolframAlphaからの調和平均の定義


そして、これはPHPでの同等の実装です。

function harmonicMeanV1()
{
    $result = 0;
    $arguments = func_get_args();

    foreach ($arguments as $argument)
    {
        $result += 1 / $argument;
    }

    return func_num_args() / $result;
}

さて、引数のいずれかがこれである場合、0これは0による除算の警告をスローしますが、はn -11 / n同じであり、エラーをスローせずに定数を正常に返すため、次のように書き直すことができます(次の場合でもエラーがスローされます)引数はありませんが、今は無視しましょう):pow(0, -1)INF

function harmonicMeanV2()
{
    $arguments = func_get_args();
    $arguments = array_map('pow', $arguments, array_fill(0, count($arguments), -1));

    return count($arguments) / array_sum($arguments);
}

どちらの実装もほとんどの場合(例v1v2WolframAlpha )は正常に機能しますが、 1 / n iシリーズの合計が0の場合、 見事に失敗します。0による別の除算警告を取得する必要がありますが、...

次のセットを考えてみましょう: -2, 3, 6( WolframAlphaはそれが複雑な無限だと言っています):

  1 / -2    // -0.5
+ 1 / 3     // 0.33333333333333333333333333333333
+ 1 / 6     // 0.16666666666666666666666666666667

= 0

ただし、両方の実装は、ではなく-2.7755575615629E-17合計v1v2)として返され0ます。

CodePadで返される結果は-108086391056890000、私の開発マシン(32ビット)がそうだと言っていますが-1.0808639105689E+17、それでも、0またはINF私が期待していたものとは異なります。戻り値を呼び出してみましたが、期待通りis_infinite()に戻ってきました。false

stats_harmonic_mean()PECL拡張機能の一部である関数も見つかりましたstatsが、驚いたことに、まったく同じバグのある結果が得られまし-1.0808639105689E+17た。引数のいずれかがである場合、0返されますが、シリーズの合計0に対するチェックは行われません。 3585行

3557    /* {{{ proto float stats_harmonic_mean(array a)
3558       Returns the harmonic mean of an array of values */
3559    PHP_FUNCTION(stats_harmonic_mean)
3560    {
3561        zval *arr;
3562        double sum = 0.0;
3563        zval **entry;
3564        HashPosition pos;
3565        int elements_num;
3566    
3567        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a",  &arr) == FAILURE) {
3568            return;
3569        }
3570        if ((elements_num = zend_hash_num_elements(Z_ARRVAL_P(arr))) == 0) {
3571            php_error_docref(NULL TSRMLS_CC, E_WARNING, "The array has zero elements");
3572            RETURN_FALSE;
3573        }
3574    
3575        zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos);
3576        while (zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&entry, &pos) == SUCCESS) {
3577            convert_to_double_ex(entry);
3578            if (Z_DVAL_PP(entry) == 0) {
3579                RETURN_LONG(0);
3580            }
3581            sum += 1 / Z_DVAL_PP(entry);
3582            zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos);   
3583        }
3584    
3585        RETURN_DOUBLE(elements_num / sum);
3586    }
3587    /* }}} */

これは典型的な浮動精度のバグのように見えますが、個々の計算が非常に正確であるため、その理由を本当に理解することはできません。

Array
(
    [0] => -0.5
    [1] => 0.33333333333333
    [2] => 0.16666666666667
)

gmp/bcmath拡張機能に戻らずにこの問題を回避することは可能ですか?

4

2 に答える 2

4

あなたは正しいです。あなたが見つけている数は、浮動小数点演算の特性のアーティファクトです。

精度を上げても役に立ちません。あなたがしているのは、ゴールポストを動かすことだけです。

肝心なのは、計算は有限の精度で行われるということです。つまり、ある時点で、中間結果が丸められます。その中間結果は、もはや正確ではありません。エラーは計算を通じて伝播し、最終的には最終結果になります。正確な結果がゼロの場合、通常、倍精度の数値で約1e-16の数値結果が得られます。

これは、計算に2の累乗ではない分母を持つ分数が含まれるたびに発生します。

それを回避する唯一の方法は、整数または有理数(可能な場合)で計算を表現し、任意精度の整数パッケージを使用して計算を行うことです。これはWolfram|Alphaが行うことです。

幾何平均の計算も簡単ではないことに注意してください。1e20を20回繰り返してみてください。数値はすべて同じであるため、結果は1e20になります。しかし、あなたが見つけることは、結果が無限であるということです。その理由は、これらの20個の数値(10e400)の積が倍精度浮動小数点数の範囲外であるため、無限大に設定されているためです。無限大の20番目のルートはまだ無限大です。

最後に、メタ観察:Pythogarianは、実際には正の数に対してのみ意味があることを意味します。3と-3の幾何平均は何ですか?架空ですか?リンク先のウィキペディアページの不等式の連鎖は、すべての値が正の場合にのみ有効です。

于 2012-05-04T05:30:37.083 に答える
3

はい、これは浮動小数点の精度の問題です。-1/2は正確に表現できますが、1/3と1/6は表現できません。したがって、それらを合計すると、完全にゼロになるわけではありません。

あなたが言及した最小公分母の使用アプローチ(あなたが投稿したH2とH3の式)を行うことができますが、それは缶を少し先に進めるだけです、あなたはまだ製品の合計が不正確な結果を得るでしょう期間は四捨五入を開始します。

とにかく、なぜ負の数の調和平均を取るのですか?これは本質的に不安定な計算です(H(-2,3,6 +イプシロン)は非常に小さいイプシロンでは大きく異なります)。

于 2012-05-04T04:22:18.140 に答える