私はPHPでピタゴラス平均を実装しています。算術平均と幾何平均は簡単ですが、信頼できる調和平均の実装を思い付くのに非常に苦労しています。
これは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 -1と1 / 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);
}
どちらの実装もほとんどの場合(例v1、v2、WolframAlpha )は正常に機能しますが、 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
合計(v1、v2)として返され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
拡張機能に戻らずにこの問題を回避することは可能ですか?