Confronto relativo di numeri in virgola mobile


10

Ho una funzione numerica che f(x, y)restituisce un doppio numero in virgola mobile che implementa una formula e voglio verificare che sia corretta rispetto alle espressioni analitiche per tutte le combinazioni dei parametri xe yche mi interessa. Qual è il modo corretto di confrontare il calcolato e numeri analitici in virgola mobile?

Diciamo che i due numeri sono ae b. Finora mi sono assicurato che gli errori sia assoluti ( abs(a-b) < eps) che relativi ( abs(a-b)/max(abs(a), abs(b)) < eps) siano inferiori a eps. In questo modo rileverà imprecisioni numeriche anche se i numeri sono diciamo intorno a 1e-20.

Tuttavia, oggi ho scoperto un problema, il valore numerico ae il valore analitico berano:

In [47]: a                                                                     
Out[47]: 5.9781943146790832e-322

In [48]: b                                                                     
Out[48]: 6.0276008792632078e-322

In [50]: abs(a-b)                                                              
Out[50]: 4.9406564584124654e-324

In [52]: abs(a-b) / max(a, b)                                                  
Out[52]: 0.0081967213114754103

Quindi l'errore assoluto [50] è (ovviamente) piccolo, ma l'errore relativo [52] è grande. Quindi ho pensato di avere un bug nel mio programma. Con il debug, mi sono reso conto che questi numeri sono denormali . Come tale, ho scritto la seguente routine per fare il confronto relativo corretto:

real(dp) elemental function rel_error(a, b) result(r)
real(dp), intent(in) :: a, b
real(dp) :: m, d
d = abs(a-b)
m = max(abs(a), abs(b))
if (d < tiny(1._dp)) then
    r = 0
else
    r = d / m
end if
end function

Dove tiny(1._dp)restituisce 2.22507385850720138E-308 sul mio computer. Ora tutto funziona e ottengo semplicemente 0 come errore relativo e tutto è ok. In particolare, l'errore relativo [52] sopra riportato è errato, è semplicemente causato da un'accuratezza insufficiente dei numeri denormali. La mia implementazione della rel_errorfunzione è corretta? Devo solo controllare che abs(a-b)sia inferiore a tiny (= denormal) e restituire 0? O dovrei controllare qualche altra combinazione, come max(abs(a), abs(b))?

Vorrei solo sapere qual è il modo "corretto".

Risposte:


11

È possibile verificare direttamente la presenza di denormali utilizzando isnormal()da math.h(C99 o successivo, POSIX.1 o successivo). In Fortran, se il modulo ieee_arithmeticè disponibile, è possibile utilizzare ieee_is_normal(). Per essere più precisi sull'uguaglianza fuzzy, devi considerare la rappresentazione in virgola mobile dei denormali e decidere cosa intendi per i risultati siano abbastanza buoni.

Più precisamente, per credere che entrambi i risultati siano corretti, devi essere sicuro di non aver perso troppe cifre in una fase intermedia. Il calcolo con denormali è generalmente inaffidabile e dovrebbe essere evitato facendo rivalutare l'algoritmo internamente. Per assicurarsi che il ridimensionamento interno abbia avuto esito positivo, vi consiglio di attivare le eccezioni in virgola mobile utilizzando feenableexcept()con C99 o il ieee_arithmeticmodulo in Fortran.

Sebbene la tua applicazione possa catturare il segnale generato dalle eccezioni in virgola mobile, tutti i kernel che ho provato hanno ripristinato il flag hardware in modo da fetestexcept()non restituire un risultato utile. Se eseguiti con -fp_trap, i programmi PETSc (per impostazione predefinita) stamperanno una traccia dello stack quando viene generato un errore in virgola mobile, ma non identificheranno la linea offensiva. Se si esegue in un debugger, il debugger conserva il flag hardware e si interrompe sull'espressione offensiva. Puoi verificare il motivo preciso chiamando fetestexceptdal debugger dove il risultato è un bit bit OR dei seguenti flag (i valori possono variare in base alla macchina, vedi fenv.h; questi valori sono per x86-64 con glibc).

  • FE_INVALID = 0x1
  • FE_DIVBYZERO = 0x4
  • FE_OVERFLOW = 0x8
  • FE_UNDERFLOW = 0x10
  • FE_INEXACT = 0x20

Grazie per l'ottima risposta L'espressione analitica che ho confrontato nel regime asintotico è exp(log_gamma(m+0.5_dp) - (m+0.5_dp)*log(t)) / 2per m = 234, t = 2000. Aumenta rapidamente a zero man mano che aumento m. Tutto quello che voglio assicurarmi che la mia routine numerica ritorni numeri "corretti" (per restituire lo zero anche perfettamente bene) ad almeno 12 cifre significative. Quindi, se il calcolo restituisce un numero denormale, è semplicemente zero e non dovrebbero esserci problemi. Quindi solo la routine di confronto deve essere robusta contro questo.
Ondřej Čertík,

5

Donald Knuth ha una proposta per un algoritmo di confronto in virgola mobile nel volume 2 "Algoritmi seminumerici" di "The Art of Computer Programming". È stato implementato in C da Th. Belding (vedi pacchetto fcmp ) ed è disponibile nel GSL .


2
Ecco la mia implementazione di Fortran: gist.github.com/3776847 , nota che in ogni caso devo gestire esplicitamente i numeri denormali. Altrimenti penso che sia praticamente equivalente all'errore relativo, l'unica differenza è che invece di fare abs(a-b)/max(a, b) < eps, lo facciamo abs(a-b)/2**exponent(max(a, b)) < eps, che praticamente lascia cadere la mantissa nella max(a, b), quindi secondo me la differenza è trascurabile.
Ondřej Čertík,

5

I numeri denormalizzati arrotondati in modo ottimale possono effettivamente presentare un errore relativo elevato. (Svuotarlo a zero mentre lo si chiama ancora un errore relativo è fuorviante.)

Ma vicino allo zero, il calcolo degli errori relativi non ha senso.

Pertanto, anche prima di raggiungere numeri denormalizzati, dovresti probabilmente passare alla precisione assoluta (cioè quella che vuoi garantire in questo caso).

yx|yx|absacc+relaccmax(|x|,|y|)

Quindi gli utenti del tuo codice sanno esattamente quanta precisione hanno davvero.


Sei sicuro che non abbia senso calcolare errori relativi vicini allo zero? Penso che sia privo di significato solo se si verifica una perdita di precisione (per qualsiasi motivo). Se ad esempio si verifica una perdita di precisione per x <1e-150 a causa di alcuni problemi numerici (come la sottrazione di due grandi numeri), allora hai ragione. Nel mio caso, tuttavia, i numeri sembrano essere precisi fino allo zero, tranne quando colpisce i numeri denormali. Quindi nel mio caso absacc = 1e-320 o giù di lì e posso solo controllare abs(a-b) < tiny(1._dp)come faccio sopra.
Ondřej Čertík,

@ OndřejČertík: in tal caso, sostituire 1e-150 con 1e-300 o qualsiasi altro limite verificabile. In ogni caso molto vicino allo zero commetti un errore assoluto e la tua richiesta di errore dovrebbe riflettere questo invece di dichiarare l'errore relativo come zero.
Arnold Neumaier,

Vedo. Posso verificare che tutto funzioni per numeri superiori a tiny(1._dp)=2.22507385850720138E-308(ho fatto un errore nel mio commento precedente, è 2e-308, non 1e-320). Quindi questo è il mio errore assoluto. Quindi devo confrontare l'errore relativo. Vedo il tuo punto, penso che tu abbia ragione. Grazie!
Ondřej Čertík,

1
@ OndřejČertík: per trovare l'errore relativo aggiuntivo dato absacc, monitorare il massimo di . |yx|absaccmax(|x|,|y|)
Arnold Neumaier,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.