谐波平均计算与浮点精度

时间:2012-05-04 03:59:00

标签: php math floating-point floating-accuracy ieee-754

我正在PHP中实现Pythagorean means,算术和几何手段是小菜一碟,但我真的很难想出一个可靠的harmonic mean实现。

这是WolframAlpha definition

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);
}

对于大多数情况(例如v1v2WolframAlpha),这两种实现都可以正常工作,但如果 的总和,它们会非常失败 1 / n i 系列是0 ,我应该得到另一个除0警告,但我不...

请考虑以下设置:-2, 3, 6WolframAlpha表示这是一个复杂的无限):

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

= 0

但是,我的两个实现都将 -2.7755575615629E-17作为总和v1v2)而不是0

虽然CodePad上的返回结果是-108086391056890000我的开发机器(32位)说它是-1.0808639105689E+17,但它仍然与我期待的0INF完全不同。我甚至尝试在返回值上调用is_infinite(),但它按预期返回false

我还发现stats_harmonic_mean()函数是stats PECL扩展的一部分,但令我惊讶的是我得到了完全相同的错误结果:-1.0808639105689E+17,如果有任何参数是{ {1}},0已退回,但未对该系列的总和进行检查,as you can see on line 3585

0

这看起来像一个典型的浮动精度错误,但我不能真正理解为什么因为个别计算非常精确:

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扩展名的情况下解决此问题?

2 个答案:

答案 0 :(得分:4)

你是对的。您找到的数字是浮点运算特性的人为因素。

添加更多精度对您没有帮助。你所做的只是移动目标职位。

底线是计算以有限精度完成。这意味着在某些时候,中间结果将被舍入。那中间结果不再准确。错误在计算中传播,最终使其成为最终结果。当精确结果为零时,通常会得到大约1e-16的数字结果,并带有双精度数。

每当您的计算涉及分母的分数不是2的幂时,就会发生这种情况。

唯一的方法是用整数或有理数表示计算(如果可以的话),并使用任意精度整数包进行计算。这就是Wolfram | Alpha所做的。

请注意,几何平均数的计算也不是微不足道的。尝试20次1e20的序列。由于数字都相同,因此结果应为1e20。但你会发现结果是无限的。原因是这20个数字(10e400)的乘积超出了双精度浮点数的范围,所以它被设置为无穷大。无限的第20根仍然是无限的。

最后,一个元观察:Pythogarian意味着真正只对正数有意义。 3和-3的几何平均数是多少?这是想象中的吗?您链接到的维基百科页面上的不等式链仅在所有值均为正值时才有效。

答案 1 :(得分:3)

是的,这是浮点精度的问题。 -1/2可以精确表示,但1/3和1/6不能。因此,当你把它们加起来时,你就不会得到零。

你可以使用你提到的公用分母方法(你公布的H2和H3公式),但是这只会让你的产品稍微开始,一旦总和,你仍然会得到不准确的结果产品术语开始四舍五入。

为什么你采用可能为负的数字的调和均值呢?这是一个固有的不稳定计算(H(-2,3,6 + epsilon)对于非常小的epsilon变化很大。)