2012-05-04 26 views
6

Я внедряю Pythagorean means в PHP, арифметические и геометрические средства - это кусок торта, но у меня очень трудное время придумать надежную реализацию 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 - это то же самое, что и п-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/н ясерии 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 мой DEV машина (32-бит) говорит, что -1.0808639105689E+17, еще нет ничего, как 0 или INF я ожидал. Я даже пытался позвонить is_infinite() по возвращаемому значению, но он вернулся как false, как и ожидалось.

Я также обнаружил функцию stats_harmonic_mean(), который является частью расширения stats PECL, но, к моему удивлению, я получил тот же результат ошибочный: -1.0808639105689E+17, если какой-либо из аргументов 0, 0 возвращается, но никаких проверок не делается на сумма серии, as you can see on line 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 
) 

Можно ли обойти эту проблему, не обращаясь к расширениям /bcmath?

ответ

4

Вы правы. Числа, которые вы находите, являются артефактом особенностей арифметики с плавающей запятой.

Дополнительная точность не поможет. Все, что вы делаете, это перемещение постов цели.

Суть в том, что расчеты выполнены с конечной точностью. Это означает, что в какой-то момент промежуточный результат будет округлен. Тогда этот промежуточный результат больше не точен. Ошибка распространяется через вычисления и в конечном итоге превращает ее в конечный результат. Когда точный результат равен нулю, вы обычно получаете числовой результат около 1е-16 с номерами с двойной точностью.

Это происходит каждый раз, когда ваш расчет включает фракцию с знаменателем, который не является степенью 2.

Единственным способом вокруг него, чтобы выразить расчеты в терминах целых или рациональных чисел (если можно) , и для выполнения вычислений используйте произвольный массив целых чисел. Это то, что делает Вольфрам Альфа.

Обратите внимание, что вычисление геометрического среднего также не является тривиальным. Попробуйте последовательность из 20 раз 1e20. Поскольку числа все одинаковы, результат должен быть 1e20. Но вы обнаружите, что результат бесконечен. Причина в том, что продукт из этих 20 номеров (10e400) выходит за пределы чисел с плавающей запятой с двойной точностью, и поэтому он установлен в бесконечность. 20-й корень бесконечности все еще бесконечен.

И, наконец, мета-наблюдение: питогарское средство действительно имеет смысл только для положительных чисел. Каково среднее геометрическое 3 и -3? Это мнимое? Цепочка неравенств на странице Википедии, на которую вы ссылаетесь, действительна только в том случае, если все значения положительны.

+0

Очень хороший ответ и наблюдения Джеффри, используя произвольную библиотеку точности, выполняет трюк, также округляя до максимальной точности ('round (array_sum ($ arguments), ini_get ('precision'))) возвращает' -0', что также может быть хорошим способом избежать зависимости 'gmp' или' bcmath'. Что касается вашего мета-наблюдения, вы правы. Должен ли я просто фильтровать отрицательные значения или использовать их абсолютное значение? –

+0

@AlixAxel Округление движется по воротам. Он может работать для значений, равных нулю, но в какой-то момент даст неправильный результат для значений, очень близких к 0. Возьмем, например, H (999999, -999998, -999997,999996) '. Результат равен ~ 1e + 18', но округляется до макс. двойная точность даст 0. –

+0

@AlixAxel Как вы обрабатываете отрицательные входы, зависит от ваших требований. Если это чисто для информационных целей, я бы просто предупредил. –

3

Да, это проблема с точностью с плавающей запятой. -1/2 могут быть представлены точно, но 1/3 и 1/6 не могут. Таким образом, когда вы добавляете их, вы не получаете нуль.

Вы можете использовать упомянутый вами подход общего назначения-знаменателя (формулы H2 и H3, которые вы опубликовали), но это просто немного ударяет по тропе вниз, вы все равно получите неточные результаты, как только сумма - срок действия продуктов начинает округлять.

Почему вы принимаете гармоническое среднее чисел, которое может быть отрицательным, во всяком случае? Это по своей сути неустойчивый расчет (H (-2,3,6 + epsilon) широко варьируется для очень небольшого эпсилона).

+0

Спасибо Keith, что касается отрицательных чисел, я был нацелен только на полноту, но я считаю, что это не имеет большого смысла.Должен ли я фильтровать отрицательные числа или просто использовать их абсолютное значение? –

+1

@AlixAxel: Я бы выбрал исключение, если вы можете сделать это на PHP. Если нет, верните код ошибки. Бесшумное игнорирование плохого ввода - плохая идея. –

Смежные вопросы