Qual è la logica di tutti i confronti che restituiscono false per i valori NaN IEEE754?


267

Perché i confronti dei valori NaN si comportano diversamente da tutti gli altri valori? Cioè, tutti i confronti con gli operatori ==, <=,> =, <,> in cui uno o entrambi i valori sono NaN restituiscono false, contrariamente al comportamento di tutti gli altri valori.

Suppongo che questo semplifichi in qualche modo i calcoli numerici, ma non sono riuscito a trovare un motivo esplicitamente dichiarato, nemmeno nelle Note di conferenza sullo stato dell'IEEE 754 di Kahan che discute in dettaglio altre decisioni di progettazione.

Questo comportamento deviante sta causando problemi durante la semplice elaborazione dei dati. Ad esempio, quando si ordina un elenco di record per scrivere un campo a valore reale in un programma C, è necessario scrivere un codice aggiuntivo per gestire NaN come elemento massimo, altrimenti l'algoritmo di ordinamento potrebbe confondersi.

Modifica: le risposte finora sostengono che non ha senso confrontare i NaN.

Sono d'accordo, ma ciò non significa che la risposta corretta sia falsa, piuttosto sarebbe un Not-a-Boolean (NaB), che fortunatamente non esiste.

Quindi la scelta di restituire vero o falso per i confronti è a mio avviso arbitraria e, per l'elaborazione generale dei dati, sarebbe vantaggioso se obbedisse alle normali leggi (riflessività di ==, tricotomia di <, ==,>), per timore di strutture di dati che si basano su queste leggi si confondono.

Quindi sto chiedendo un vantaggio concreto di infrangere queste leggi, non solo il ragionamento filosofico.

Modifica 2: Penso di capire ora perché rendere NaN massimo sarebbe una cattiva idea, rovinerebbe il calcolo dei limiti superiori.

NaN! = NaN potrebbe essere desiderabile per evitare di rilevare la convergenza in un loop come

while (x != oldX) {
    oldX = x;
    x = better_approximation(x);
}

che tuttavia dovrebbe essere meglio scritto confrontando la differenza assoluta con un piccolo limite. Quindi IMHO questo è un argomento relativamente debole per rompere la riflessività a NaN.


2
Una volta che un NaN è entrato nel calcolo, in genere non se ne andrà mai, quindi il test di convergenza diventerebbe un ciclo infinito. Di solito è preferibile segnalare l'incapacità di convergere alla routine di chiamata, possibilmente restituendo NaN. Pertanto, la struttura del loop diventerebbe in genere qualcosa di simile while (fabs(x - oldX) > threshold), uscendo dal loop se si verifica la convergenza o se un NaN entra nel calcolo. Il rilevamento della NaN e il rimedio appropriato avverrebbero quindi al di fuori del ciclo.
Stephen Canon,

1
Se NaN fosse l'elemento minimo dell'ordine, il ciclo while continuerebbe a funzionare.
Starblue,

Risposte:


535

Sono stato membro del comitato IEEE-754, cercherò di aiutare a chiarire un po 'le cose.

Prima di tutto, i numeri in virgola mobile non sono numeri reali e l'aritmetica in virgola mobile non soddisfa gli assiomi dell'aritmetica reale. La tricotomia non è l'unica proprietà dell'aritmetica reale che non vale per i galleggianti, né la più importante. Per esempio:

  • L'aggiunta non è associativa.
  • La legge distributiva non vale.
  • Esistono numeri in virgola mobile senza inversioni.

Potrei andare avanti. Non è possibile specificare un tipo aritmetico di dimensioni fisse che soddisfi tutte le proprietà dell'aritmetica reale che conosciamo e amiamo. Il comitato 754 deve decidere di piegare o rompere alcuni di essi. Questo è guidato da alcuni principi piuttosto semplici:

  1. Quando possiamo, abbiniamo il comportamento dell'aritmetica reale.
  2. Quando non possiamo, cerchiamo di rendere le violazioni il più prevedibili e facili da diagnosticare.

Per quanto riguarda il tuo commento "ciò non significa che la risposta corretta sia falsa", questo è sbagliato. Il predicato (y < x)chiede se yè inferiore a x. Se yè NaN, allora è non meno di qualsiasi valore a virgola mobile x, quindi la risposta è necessariamente falso.

Ho detto che la tricotomia non vale per i valori in virgola mobile. Tuttavia, esiste una proprietà simile che detiene. Clausola 5.11, paragrafo 2 della norma 754-2008:

Sono possibili quattro relazioni reciprocamente esclusive: minore di, uguale, maggiore di e non ordinata. L'ultimo caso si presenta quando almeno un operando è NaN. Ogni NaN si confronta non ordinata con tutto, incluso se stessa.

Per quanto riguarda la scrittura di codice aggiuntivo per gestire i NaN, di solito è possibile (anche se non sempre facile) strutturare il codice in modo tale che i NaN cadano correttamente, ma non è sempre così. In caso contrario, potrebbe essere necessario un codice aggiuntivo, ma questo è un piccolo prezzo da pagare per la comodità che la chiusura algebrica ha portato all'aritmetica in virgola mobile.


Addendum: molti commentatori hanno sostenuto che sarebbe più utile preservare la riflessività dell'uguaglianza e della tricotomia sulla base del fatto che l'adozione di NaN! = NaN non sembra preservare alcun assioma familiare. Confesso di avere una certa simpatia per questo punto di vista, quindi ho pensato di rivisitare questa risposta e fornire un po 'più contesto.

La mia comprensione dal parlare con Kahan è che NaN! = NaN ha avuto origine da due considerazioni pragmatiche:

  • Ciò x == ydovrebbe essere equivalente x - y == 0ogni volta che è possibile (oltre ad essere un teorema della vera aritmetica, questo rende l'implementazione hardware del confronto più efficiente in termini di spazio, che era della massima importanza al momento dello sviluppo dello standard; nota, tuttavia, che questo è violato per x = y = infinito, quindi non è un grande motivo da solo; avrebbe potuto ragionevolmente essere piegato (x - y == 0) or (x and y are both NaN)).

  • Ancora più importante, non c'era isnan( )predicato al momento in cui NaN fu formalizzata nell'aritmetica dell'8087; era necessario fornire ai programmatori un mezzo conveniente ed efficiente per rilevare i valori di NaN che non dipendevano dai linguaggi di programmazione fornendo qualcosa di simile isnan( )che potrebbe richiedere molti anni. Citerò gli stessi scritti di Kahan sull'argomento:

Se non ci fosse modo di sbarazzarsi di NaN, sarebbero inutili come gli indefiniti su CRAY; non appena ne si incontrava uno, il calcolo sarebbe meglio fermato piuttosto che continuare per un tempo indefinito fino a una conclusione indefinita. Ecco perché alcune operazioni su NaN devono fornire risultati non NaN. Quali operazioni? … Le eccezioni sono i predicati C “x == x” e “x! = X”, che sono rispettivamente 1 e 0 per ogni numero infinito o finito x ma invertiti se x non è un numero (NaN); questi forniscono l'unica semplice distinzione ineccepibile tra NaN e numeri in lingue che non hanno una parola per NaN e un predicato IsNaN (x).

Si noti che questa è anche la logica che esclude la restituzione di qualcosa di simile a un "non-booleano". Forse questo pragmatismo era fuori luogo e lo standard avrebbe dovuto essere richiesto isnan( ), ma ciò avrebbe reso NaN quasi impossibile da usare in modo efficiente e conveniente per diversi anni mentre il mondo attendeva l'adozione del linguaggio di programmazione. Non sono convinto che sarebbe stato un compromesso ragionevole.

Per essere schietti: il risultato di NaN == NaN non cambierà ora. Meglio imparare a conviverci che a lamentarsi su Internet. Se vuoi sostenere che dovrebbe esistere anche una relazione d'ordine adatta ai container , ti consiglio di sostenere che il tuo linguaggio di programmazione preferito implementi il totalOrderpredicato standardizzato in IEEE-754 (2008). Il fatto che non abbia già parlato della validità della preoccupazione di Kahan che ha motivato la situazione attuale.


16
Ho letto i tuoi punti 1 e 2. Poi ho osservato che nell'aritmetica reale (estesa per consentire a NaN in primo luogo) NaN è uguale a se stesso - semplicemente perché in matematica ogni entità è uguale a se stessa, senza eccezioni. Ora sono confuso: perché IEEE non "corrisponde al comportamento dell'aritmetica reale", il che renderebbe NaN == NaN? Cosa mi sto perdendo?
massimo

12
Concordato; la non riflessività di NaNs non ha creato fine al dolore per linguaggi come Python, con la sua semantica di contenimento basata sull'uguaglianza. Non vuoi davvero che l'uguaglianza non riesca a essere una relazione di equivalenza quando stai provando a costruire dei contenitori sopra di essa. E avere due nozioni distinte di uguaglianza non è nemmeno un'opzione amichevole, per una lingua che dovrebbe essere facile da imparare. Il risultato (nel caso di Python) è un compromesso spiacevolmente fragile tra rispetto per IEEE 754 e semantica di contenimento non troppo spezzata. Fortunatamente, è raro mettere NaN in contenitori.
Mark Dickinson,

5
Alcune belle osservazioni qui: bertrandmeyer.com/2010/02/06/…
Mark Dickinson,

6
@StephenCanon: In che modo (0/0) == (+ INF) + (-INF) sarebbe più privo di senso che avere 1f/3f == 10000001f/30000002f? Se i valori in virgola mobile sono considerati classi di equivalenza, allora a=bnon significa "I calcoli che hanno prodotto ae b, se fatto con precisione infinita, produrrebbero risultati identici", ma piuttosto "Ciò che si sa su acorrisponde a ciò che si sa b". Sono curioso di sapere se hai qualche esempio di codice in cui avere "Nan! = NaN" rende le cose più semplici di quanto sarebbero altrimenti?
supercat

5
Teoricamente, se avessi NaN == NaN e no isNaN, potresti comunque provare NaN con !(x < 0 || x == 0 || x > 0), ma sarebbe stato più lento e più goffo di x != x.
user2357112 supporta Monica
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.