Sto implementando lo Pythagorean means in PHP, i metodi aritmetici e geometrici sono un pezzo di torta ma sto avendo davvero difficoltà a trovare un'implementazione affidabile harmonic mean.Calcolo della media armonica e precisione del galleggiante
Questa è la WolframAlpha definition:
E questo è l'implementazione equivalente in PHP:
function harmonicMeanV1()
{
$result = 0;
$arguments = func_get_args();
foreach ($arguments as $argument)
{
$result += 1/$argument;
}
return func_num_args()/$result;
}
Ora, se uno degli argomenti è 0
questo sarà lanciare un divisione per avviso 0, ma poiché 1/n
corrisponde a n-1 e pow(0, -1)
restituisce con garbo la costante INF
senza gettare eventuali errori ho potuto riscrivere che a quanto segue (che sarà ancora gettare errori se non ci sono argomenti, ma lascia ignorare che per ora):
function harmonicMeanV2()
{
$arguments = func_get_args();
$arguments = array_map('pow', $arguments, array_fill(0, count($arguments), -1));
return count($arguments)/array_sum($arguments);
}
Entrambe le implementazioni funzionano bene per la maggior parte dei casi (esempio v1, v2 e WolframAlpha), ma non riescono spettacolare se la somma del1/n iserie è 0, dovrei ottenere un'altra divisione per 0 avvertimento, ma non lo faccio ...
Si consideri il seguente insieme: -2, 3, 6
(WolframAlpha dice che è un complesso infinito):
1/-2 // -0.5
+ 1/3 // 0.33333333333333333333333333333333
+ 1/6 // 0.16666666666666666666666666666667
= 0
Tuttavia, entrambe le implementazioni restituiscono -2.7755575615629E-17
come la somma (v1, v2) anziché 0
.
Mentre il risultato ritorno sul CodePad è -108086391056890000
mia macchina dev (32-bit) dice che è -1.0808639105689E+17
, ancora è niente come il 0
o INF
mi aspettavo. Ho anche provato a chiamare is_infinite()
sul valore restituito, ma è tornato come false
come previsto.
Ho trovato anche la funzione stats_harmonic_mean()
che fa parte dell'estensione stats
PECL, ma con mia sorpresa ho ottenuto esattamente lo stesso risultato buggy: -1.0808639105689E+17
, se uno degli argomenti è 0
, 0
viene restituito, ma nessun controllo sono fatto per la somma della serie, 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 /* }}} */
Questo appare come un tipico errore di precisione galleggiante, ma non posso davvero capire il motivo per cui dal momento che i singoli calcoli sono piuttosto precisi:
0.123.Array
(
[0] => -0.5
[1] => 0.33333333333333
[2] => 0.16666666666667
)
È possibile aggirare questo problema senza ripristinare le estensioni gmp
/bcmath
?
Risposta e osservazioni molto piacevoli Jeffrey, usando una libreria arbitraria di precisione fa il trucco, arrotondando anche alla massima precisione ('round (array_sum ($ arguments), ini_get ('precision'))') restituisce '-0' che potrebbe anche essere un buon modo per evitare la dipendenza da 'gmp' o' bcmath'. Per quanto riguarda la tua meta-osservazione, hai ragione. Dovrei semplicemente filtrare i valori negativi o usare il loro valore assoluto? –
@AlixAxel L'arrotondamento sta spostando i pali della porta. Potrebbe funzionare per valori che sono esattamente pari a zero, ma a un certo punto darà il risultato sbagliato per valori molto vicini a 0. Prendete 'H (999999, -999998, -999997,999996)' per esempio. Il risultato è intorno a '1e + 18', ma arrotondando al massimo. la doppia precisione darebbe 0. –
@AlixAxel Il modo in cui gestisci gli input negativi dipende dalle tue esigenze. Se è puramente a scopo informativo, quindi vorrei solo dare un avvertimento. –