Calcolo robusto della media di due numeri in virgola mobile?


15

Sia x, yessere due numeri in virgola mobile. Qual è il modo giusto per calcolare la loro media?

Il modo ingenuo (x+y)/2può provocare traboccamenti quando xe ysono troppo grandi. Penso 0.5 * x + 0.5 * yforse meglio, ma comporta due moltiplicazioni (che forse è inefficiente) e non sono sicuro che sia abbastanza buono. C'è un modo migliore?

Un'altra idea con cui ho giocato è (y/2)(1 + x/y)if x<=y. Ma ancora una volta, non sono sicuro di come analizzare questo e dimostrare che soddisfa i miei requisiti.

Inoltre, ho bisogno di una garanzia che la media calcolata sarà >= min(x,y)e <= max(x,y). Come sottolineato nella risposta di Don Hatch , forse un modo migliore di porre questa domanda è: qual è un'implementazione della media di due numeri che dà sempre il risultato più accurato possibile? Cioè, se xe ysono numeri in virgola mobile, come calcolare il numero in virgola mobile più vicino a (x+y)/2? In questo caso, la media calcolata è automaticamente >= min(x,y)e <= max(x,y). Vedi la risposta di Don Hatch per i dettagli.

Nota: la mia priorità è la solida precisione. L'efficienza è sacrificabile. Tuttavia, se ci sono molti algoritmi robusti e precisi, sceglierei il più efficiente.


(+1) Domanda interessante, sorprendentemente non banale.
Kirill

1
In passato, i valori in virgola mobile venivano calcolati e mantenuti in una forma di precisione più elevata per risultati intermedi. Se a + b (raddoppia a 64 bit) produce un risultato intermedio a 80 bit e questo è ciò che è diviso per 2, non devi preoccuparti di overflow. La perdita di precisione è meno evidente.
JDługosz,

La soluzione sembra relativamente semplice ( ho aggiunto una risposta ). Il fatto è che sono un programmatore e non un esperto di informatica, quindi cosa mi manca che rende questa domanda molto più difficile?
IQAndreas

Non preoccuparti del costo di moltiplicazioni e divisioni per due; il tuo compilatore li ottimizzerà per te.
Federico Poloni,

Risposte:


18

Penso che l' accuratezza e la stabilità degli algoritmi numerici di Higham riguardino il modo in cui si possono analizzare questi tipi di problemi. Vedi il capitolo 2, in particolare l'esercizio 2.8.

In questa risposta vorrei sottolineare qualcosa che non è realmente affrontato nel libro di Higham (non sembra essere molto conosciuto, del resto). Se sei interessato a dimostrare le proprietà di semplici algoritmi numerici come questi, puoi usare la potenza dei moderni solutori SMT ( Teorie del Modulo di soddisfazione ), come z3 , usando un pacchetto come sbv in Haskell. Questo è un po 'più semplice rispetto all'uso di carta e matita.

Supponiamo che mi venga dato 0Xy , e vorrei sapere se soddisfa x z y . Il seguente codice Haskellz=(X+y)/2Xzy

import Data.SBV

test1 :: (SFloat -> SFloat -> SFloat) -> Symbolic SBool
test1 fun =
  do [x, y] <- sFloats ["x", "y"]
     constrain $ bnot (isInfiniteFP x) &&& bnot (isInfiniteFP y)
     constrain $ 0 .<= x &&& x .<= y
     let z = fun x y
     return $ x .<= z &&& z .<= y

test2 :: (SFloat -> SFloat -> SFloat) -> Symbolic SBool
test2 fun =
  do [x, y] <- sFloats ["x", "y"]
     constrain $ bnot (isInfiniteFP x) &&& bnot (isInfiniteFP y)
     constrain $ x .<= y
     let z = fun x y
     return $ x .<= z &&& z .<= y

mi permetterà di farlo automaticamente . Ecco test1 funla proposizione che per tutti i galleggianti finiti x , y con 0 x y .Xfun(X,y)yX,y0Xy

λ> prove $ test1 (\x y -> (x + y) / 2)
Falsifiable. Counter-example:
  x = 2.3089316e36 :: Float
  y = 3.379786e38 :: Float

Trabocca. Supponiamo che ora prenda la tua altra formula: z=X/2+y/2

λ> prove $ test1 (\x y -> x/2 + y/2)
Falsifiable. Counter-example:
  x = 2.3509886e-38 :: Float
  y = 2.3509886e-38 :: Float

Non funziona (a causa del underflow graduale: , che potrebbe non essere intuitivo a causa del fatto che tutta l'aritmetica è base-2).(X/2)×2X

Ora prova :z=X+(y-X)/2

λ> prove $ test1 (\x y -> x + (y-x)/2)
Q.E.D.

Lavori! Il Q.E.D.è una prova che la test1proprietà vale per tutti i carri come sopra definito.

Che dire dello stesso, ma limitato a (invece di 0 x y )?Xy0Xy

λ> prove $ test2 (\x y -> x + (y-x)/2)
Falsifiable. Counter-example:
  x = -3.1300826e34 :: Float
  y = 3.402721e38 :: Float

Ok, quindi se trabocca, che ne dici di zy-X ?z=X+(y/2-X/2)

λ> prove $ test2 (\x y -> x + (y/2 - x/2))
Q.E.D.

Quindi sembra che tra le formule che ho provato qui, sembra funzionare (anche con una prova). L'approccio del risolutore SMT mi sembra un modo molto più rapido di rispondere ai sospetti su semplici formule in virgola mobile rispetto all'analisi degli errori in virgola mobile con carta e matita.X+(y/2-X/2)

Infine, l'obiettivo di precisione e stabilità è spesso in contrasto con l'obiettivo della prestazione. Per quanto riguarda le prestazioni, non vedo davvero come si possa fare meglio di , soprattutto perché il compilatore farà comunque il duro lavoro di tradurre questo in istruzioni macchina per te.(X+y)/2

PS Questo è tutto con l'aritmetica in virgola mobile IEEE754 a precisione singola. Ho controllato con aritmetica a doppia precisione (sostituirecon), e funziona anche.XX+(y/2-X/2)ySFloatSDouble

-ffast-math(X+y)/2

PPPS Mi sono lasciato trasportare un po 'guardando solo espressioni algebriche semplici senza condizionali. La formula di Don Hatch è strettamente migliore.


2
Resisti; hai affermato che se x <= y (indipendentemente dal fatto che x> = 0 o meno) allora x + (y / 2-x / 2) è un buon modo per farlo? Mi sembra che non possa essere giusto, dato che dà la risposta sbagliata nel seguente caso quando la risposta è esattamente rappresentabile: x = -1, y = 1 + 2 ^ -52 (il numero rappresentabile più piccolo maggiore di 1), nel qual caso la risposta è 2 ^ -53. Conferma in pitone: >>> x = -1.; y = 1.+2.**-52; print `2**-53`, `(x+y)/2.`, `x+(y/2.-x/2.)`
Don Hatch,

2
X(X+y)/2yX,y(X+y)/2(X+y)/2

8

Innanzitutto, osserva che se hai un metodo che fornisce una risposta più accurata in tutti i casi, allora soddisferà le tue condizioni richieste. (Nota che dico una risposta più accurata piuttosto che la risposta più accurata, dal momento che potrebbero esserci due vincitori.) Prova: se, al contrario, hai una risposta il più accurata possibile che non soddisfa la condizione richiesta, che significa o answer<min(x,y)<=max(x,y)(nel qual caso min(x,y)è una risposta migliore, una contraddizione), oppure min(x,y)<=max(x,y)<answer(nel qual caso max(x,y)è una risposta migliore, una contraddizione).

Quindi penso che ciò significhi che la tua domanda si riduce a trovare una risposta il più accurata possibile. Supponendo che l'aritmetica IEEE754 sia valida, propongo quanto segue:

if max(abs(x),abs(y)) >= 1.:
    return x/2. + y/2.
else:
    return (x+y)/2.

La mia tesi secondo cui ciò fornisce una risposta più accurata è un'analisi del caso alquanto noiosa. Ecco qui:

  • Astuccio max(abs(x),abs(y)) >= 1. :

    • La sottoclasse né x né y viene denormalizzata: in questo caso la risposta calcolata x/2.+y/2. manipola le stesse mantisse e quindi fornisce la stessa risposta esatta del calcolo (x+y)/2che produrrebbe se assumessimo esponenti estesi per evitare il trabocco. Questa risposta può dipendere dalla modalità di arrotondamento ma in ogni caso è garantita da IEEE754 la migliore risposta possibile (dal fatto che il calcolo x+yè la migliore approssimazione a matematica x + y, e la divisione per 2 è esatta in questo Astuccio).
    • Il sottocase x è denormalizzato (e così abs(y)>=1):

      answer = x/2. + y/2. = y/2. since abs(x/2.) is so tiny compared to abs(y/2.) = the exact mathematical value of y/2 = a best possible answer.

    • La sottoclasse y è denormalizzata (e così abs(x)>=1): analoga.

  • Astuccio max(abs(x),abs(y)) < 1. :
    • Sottoscrivere il calcolo non x+yè né denormalizzato né denormalizzato e "pari": anche se il calcolo x+ypotrebbe non essere esatto, è garantito da IEEE754 come una migliore approssimazione possibile al matematico x + y. In questo caso la successiva divisione per 2 nell'espressione (x+y)/2.è esatta, quindi la risposta calcolata (x+y)/2.è la migliore approssimazione possibile al matematico (x + y) / 2.
    • Sottrai che il calcolo x+yè denormalizzato e "dispari": in questo caso esattamente uno di x, y deve anche essere denormalizzato-e- "dispari", il che significa che l'altro di x, y è denormalizzato con il segno opposto, e quindi il calcolo x+yè esattamente il matematico x + y, e quindi il calcolo (x+y)/2.è garantito da IEEE754 per essere la migliore approssimazione possibile al matematico (x + y) / 2.

Mi rendo conto che quando ho detto "denormalizzato" intendevo davvero qualcos'altro, vale a dire numeri vicini l'uno all'altro dei numeri, ovvero l'intervallo di numeri che è circa due volte più grande dell'intervallo di numeri denormalizzati, cioè i primi 8 tick o giù di lì nel diagramma su en.wikipedia.org/wiki/Denormal_number . Il punto è che i "dispari" di questi sono gli unici numeri per i quali dividerli per due non è esatto. Devo riformulare questa parte della risposta per chiarire questo punto.
Don Hatch,

fl(op(X,y))=op(X,y)(1+δ)|δ|uX/2+y/2(X+y)/2sono sempre arrotondati correttamente, sono presenti troppopieno / underflow assenti, tutto ciò che resta è non mostrare nulla di over- / underflow, il che è semplice.
Kirill

@Kirill Mi sono perso un po '... da dove vieni? Inoltre, non credo sia del tutto vero che "le divisioni per 2 sono esatte per i numeri non denormali" ... questa è la stessa cosa su cui sono inciampato, e sembra essere un po 'imbarazzante cercare di farlo bene. L'affermazione precisa è qualcosa di più simile a "x / 2 è esatto fintanto che abs (x) è almeno il doppio del massimo numero subnormale" ... argh, imbarazzante!
Don Hatch,

3

Per i formati binari in virgola mobile IEEE-754, esemplificati da binary64 calcolo (doppia precisione), S. Boldo ha formalmente dimostrato che il semplice algoritmo mostrato di seguito fornisce la media correttamente arrotondata.

Sylvie Boldo, "Verifica formale di programmi che calcolano la media in virgola mobile". In Conferenza internazionale sui metodi di ingegneria formale , pagg. 17-32. Springer, Cham, 2015. ( bozza online )

(X+y)/2X/2+y/2binary64C[2-967,2970]C in modo da fornire le migliori prestazioni per un caso d'uso particolare.

Questo produce il seguente ISO-C99codice esemplare :

double average (double x, double y) 
{
    const double C = 1; /* 0x1p-967 <= C <= 0x1p970 */
    return (C <= fabs (x)) ? (x / 2 + y / 2) : ((x + y) / 2);
}

In un recente lavoro di follow-up, S. Boldo e i coautori hanno mostrato come ottenere i migliori risultati possibili per i formati decimali IEEE-754 in virgola mobile utilizzando operazioni di aggiunta multipla (FMA) fuse e una nota precisione raddoppiare blocco (TwoSum):

Sylvie Boldo, Florian Faissole e Vincent Tourneur, "Un algoritmo formalmente provato per calcolare la media corretta dei numeri decimali in virgola mobile". Nel 25 ° Simposio IEEE sull'aritmetica informatica (ARITH 25) , giugno 2018, pp. 69-75. ( bozza online )


2

Anche se potrebbe non essere super efficiente dal punto di vista delle prestazioni, esiste un modo molto semplice per (1) assicurarsi che nessuno dei numeri sia maggiore di uno xo y(nessun overflow) e (2) mantenere il virgola mobile "accurato" come possibile (e (3) , come bonus aggiuntivo, anche se viene utilizzata la sottrazione, nessun valore verrà mai memorizzato come numero negativo.

float difference = max(x, y) - min(x, y);
return min(x, y) + (difference / 2.0);

In effetti, se vuoi davvero andare per la precisione, non hai nemmeno bisogno di eseguire la divisione sul posto; restituire semplicemente i valori di min(x, y)e differenceche è possibile utilizzare per semplificare logicamente o manipolare in seguito.


Quello che sto cercando di capire ora è come far funzionare questa stessa risposta con più di due elementi , mantenendo tutte le variabili inferiori al massimo dei numeri e usando solo una divisione per preservare l'accuratezza.
IQAndreas

@becko Sì, faresti divisione almeno due volte. Inoltre, l'esempio che hai fornito farebbe apparire la risposta sbagliata. Immagina la media di 2,4,9, non è la stessa media di 3,9.
IQAndreas

Hai ragione, la mia ricorsione era sbagliata. Non sono sicuro di come risolverlo in questo momento, senza perdere precisione.
becko,

Puoi provare che questo dà il risultato più accurato possibile? Cioè, se xe ysono in virgola mobile, il tuo calcolo produce un virgola mobile più vicino a (x+y)/2?
becko,

1
Non si verificherà questo overflow quando x, y sono i numeri minimo e massimo espressibili?
Don Hatch,

1

Converti in precisione più alta, aggiungi i valori lì e converti indietro.

Non dovrebbe esserci overflow nella precisione superiore e se entrambi si trovano nell'intervallo di virgola mobile valido, anche il numero calcolato dovrebbe essere all'interno.

E dovrebbe essere tra di loro, nel peggiore dei casi solo la metà del numero più grande se la preclusione non è sufficiente.


Questo è l'approccio della forza bruta. Probabilmente funziona, ma stavo cercando un'analisi che non richiedesse una precisione intermedia superiore. Inoltre, puoi stimare quanta precisione intermedia è richiesta? In ogni caso, non cancellare questa risposta (+1), non la accetterò come risposta.
becko

1

teoricamente, x/2 può essere calcolato sottraendo 1 dalla mantissa.

Tuttavia, implementare effettivamente operazioni bit per bit come questa non è necessariamente semplice, soprattutto se non si conosce il formato dei numeri in virgola mobile.

Se puoi farlo, l'intera operazione è ridotta a 3 addizioni / sottrazioni, il che dovrebbe essere un miglioramento significativo.


0

Stavo pensando sulla stessa linea di @Roland Heath ma non posso ancora commentare, ecco la mia opinione:

x/2 può essere calcolato sottraendo 1 da esponente (non mantissa, sottraendo 1 dalla mantissa è sottraendo 2^(value_of_exponent-length_of_mantissa)dal valore totale).

Senza limitazione del caso generale, supponiamo x < y. (If x > y, rietichettare le variabili. If x = y,(x+y) / 2 è banale.)

  • Trasformare (x+y) / 2 inx/2 + y/2 , che può essere eseguito da due sottrazioni intere (da una dall'esponente)
    • Tuttavia, esiste un limite inferiore per l'esponente a seconda della rappresentazione. Se l'esponente è già minimo prima di sottrarre 1, questo metodo richiederà la gestione di casi speciali. xFarà un esponente minimox/2 più piccolo di quanto sia rappresentabile (supponendo che la mantissa sia rappresentata con un primo implicito 1).
    • Invece di sottrarre 1 dall'esponente di x, spostax la mantissa a destra di una (e aggiungi l'eventuale 1 iniziale implicito).
    • Sottrai 1 dall'esponente di y, se non è minimo. Se è minimo (y è maggiore di x, a causa della mantissa), sposta la mantissa a destra di uno (aggiungi l'eventuale 1 iniziale implicito).
    • Sposta la nuova mantissa xa destra secondo l'esponente di y.
    • Esegui l'aggiunta di numeri interi sulle mantisse, a meno che la mantissa di non xsia stata completamente spostata. Se entrambi gli esponenti erano minimi, quelli principali traboccerebbero, il che va bene, perché si suppone che quel trabocco diventi nuovamente un leader implicito.
  • e un'aggiunta in virgola mobile.
    • Non riesco a pensare a nessun caso speciale qui; ad eccezione dell'arrotondamento, che si applica anche al cambio sopra descritto.
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.