我正在用PHP实现勾股平均数,算术平均数和几何平均数很容易实现,但是我很难想出一个可靠的调和平均数实现。
现在,如果任何一个参数是
然而,我的两种实现都返回
虽然CodePad上的返回结果是
我还发现了
以下是在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()
函数,它是stats
PECL扩展的一部分,但令我惊讶的是,我得到了完全相同的错误结果:-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
扩展程序?
round(array_sum($arguments), ini_get('precision'))
)会返回-0
,这也是避免依赖于gmp
或bcmath
的好方法。关于你的元观察,你是对的。我应该只过滤负值还是使用它们的绝对值? - Alix AxelH(999999,-999998,-999997,999996)
为例。结果约为1e+18
,但将其四舍五入到最大双精度会得到0。 - Jeffrey Sax