Вычисление среднего гармонического и точность с плавающей запятой

Я реализую пифагорейские средние в PHP, арифметические и геометрические средние — это кусок пирога, но мне очень трудно придумать надежныйгармоническое среднеереализация.

Это определение WolframAlpha:

Harmonic Mean Definition from WolframAlpha


И это эквивалентная реализация в PHP:

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

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

    return func_num_args() / $result;
}

Теперь, если какой-либо из аргументов равен 0, это вызовет предупреждение о делении на 0, но так как 1 / nтакое же, какn-1и 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 / niравна 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(), которая является частью расширения statsPECL, но, к моему удивлению, я получил точно такой же ошибочный результат:-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?

6
задан Alix Axel 4 May 2012 в 04:04
поделиться