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

Perché i confronti tra i valori NaN si comportano in modo diverso rispetto a tutti gli altri valori? Cioè, tutti i confronti con gli operatori ==, =, dove uno o entrambi i valori è NaN restituisce falso, contrariamente al comportamento di tutti gli altri valori.

Suppongo che questo semplifichi in qualche modo i calcoli numerici, ma non sono riuscito a trovare una ragione esplicitamente dichiarata, nemmeno nelle Lecture Notes sullo status di IEEE 754 di Kahan che discute in dettaglio le altre decisioni di progettazione.

Questo comportamento deviante sta causando problemi quando si esegue una semplice elaborazione dei dati. Ad esempio, quando si ordina un elenco di record in un campo con valori reali in un programma C, è necessario scrivere codice aggiuntivo per gestire NaN come elemento massimo, altrimenti l’algoritmo di ordinamento potrebbe confondersi.

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

Sono d’accordo, ma ciò non significa che la risposta corretta sia falsa, piuttosto sarebbe un non-booleano (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 usuali leggi (riflessività di ==, tricotomia di ), per evitare che le strutture di dati che si basano su queste leggi diventano confusi.

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

Edit 2: Penso di capire ora perché rendere NaN maximal sarebbe una ctriggers idea, farebbe confusione nel calcolo dei limiti superiori.

NaN! = NaN potrebbe essere desiderabile per evitare di rilevare la convergenza in un ciclo 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, questo è un argomento relativamente debole per rompere la riflessività in NaN.

Ero un 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 regge per i galleggianti, né per i più importanti. Per esempio:

  • L’addizione non è associativa.
  • La legge distributiva non regge.
  • Ci sono numeri a virgola mobile senza inversi.

Potrei andare avanti Non è ansible specificare un tipo aritmetico di dimensione fissa che soddisfi tutte le proprietà dell’aritmetica reale che conosciamo e amiamo. Il comitato 754 deve decidere di piegare o rompere alcuni di loro. 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 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 è minore di x . Se y è NaN, allora non è inferiore a qualsiasi valore in virgola mobile x , quindi la risposta è necessariamente falsa.

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

Sono possibili quattro relazioni mutuamente esclusive: inferiori, uguali, maggiori e non ordinate. L'ultimo caso si verifica quando almeno un operando è NaN. Ogni NaN si confronterà non ordinato con tutto, incluso se stesso.

Per quanto riguarda la scrittura di codice aggiuntivo per gestire i NaN, solitamente è ansible (anche se non sempre facile) strutturare il codice in modo tale che i NaN cadano correttamente, ma questo non è sempre il caso. Quando non lo è, 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 stato più utile preservare la riflessività dell'uguaglianza e della tricotomia sulla base del fatto che l'adozione di NaN! = NaN non sembra conservare alcun assioma familiare. Confesso di avere qualche simpatia per questo punto di vista, quindi ho pensato di rivisitare questa risposta e fornire un po 'più di contesto.

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

  • Che x == y dovrebbe essere equivalente a x - y == 0 quando ansible (oltre ad essere un teorema dell'aritmetica reale, questo rende l'implementazione hardware del confronto più efficiente sotto il profilo dello spazio, che era della massima importanza al momento dello sviluppo dello standard - nota, tuttavia, che questo è violato per x = y = infinito, quindi non è una grande ragione a sé stante: potrebbe essere stato ragionevolmente piegato a (x - y == 0) or (x and y are both NaN) ) .

  • Ancora più importante, non esisteva un predicato isnan( ) nel momento in cui NaN veniva formalizzato nell'aritmetica 8087; era necessario fornire ai programmatori un mezzo pratico ed efficiente per rilevare i valori di NaN che non dipendevano dai linguaggi di programmazione che fornivano qualcosa come isnan( ) che poteva richiedere molti anni. Citerò la scrittura di Kahan sull'argomento:

Se non ci fosse modo di sbarazzarsi dei NaN, sarebbero inutili come gli Indefiniti su CRAY; non appena ne è stato incontrato uno, il calcolo sarebbe stato meglio arrestato piuttosto che continuato per un tempo indefinito a una conclusione indefinita. Questo è il motivo per cui alcune operazioni sui NaN devono fornire risultati non NaN. Quali operazioni? ... Le eccezioni sono predicati C "x == x" e "x! = X", che sono rispettivamente 1 e 0 per ogni numero infinito o finito x ma inverso se x non è un numero (NaN); questi forniscono l'unica semplice distinzione non eccezionale tra NaN e numeri in lingue a cui manca una parola per NaN e un predicato IsNaN (x).

Si noti che questa è anche la logica che esclude la restituzione di qualcosa come un "Not-A-Boolean". Forse questo pragmatismo era fuori luogo, e lo standard avrebbe dovuto richiedere isnan( ) , ma ciò avrebbe reso NaN praticamente imansible 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.

Essere schietto: il risultato di NaN == NaN non cambierà ora. Meglio imparare a conviverci che lamentarsi su internet. Se vuoi sostenere che dovrebbe esistere anche una relazione di ordine adatta ai contenitori, ti consiglio di sostenere che il tuo linguaggio di programmazione preferito implementa il predicato totalOrder standardizzato in IEEE-754 (2008). Il fatto che non abbia già parlato della validità della preoccupazione di Kahan che ha motivato lo stato attuale delle cose.

NaN può essere pensato come uno stato / numero non definito. simile al concetto di 0/0 non definito o sqrt (-3) (nel sistema di numeri reali in cui vive il punto mobile).

NaN è usato come una sorta di segnaposto per questo stato indefinito. Matematicamente parlando, indefinito non è uguale a indefinito. Né puoi dire che un valore indefinito sia maggiore o minore di un altro valore indefinito. Pertanto tutti i confronti restituiscono false.

Questo comportamento è anche vantaggioso nei casi in cui si confronta sqrt (-3) a sqrt (-2). Entrambi restituirebbero NaN ma non sono equivalenti anche se restituiscono lo stesso valore. Quindi avere l’uguaglianza che restituisce sempre false quando si ha a che fare con NaN è il comportamento desiderato.

Per aggiungere un’altra analogia. Se ti do due scatole e ti dico che nessuno dei due contiene una mela, vuoi dirmi che le scatole contengono la stessa cosa?

NaN non contiene informazioni su cosa sia qualcosa, solo che cosa non lo è. Pertanto, non si può mai dire che questi elementi siano uguali.

Dall’articolo di Wikipedia su NaN , le seguenti pratiche possono causare i NaN:

  • Tutte le operazioni matematiche> con un NaN come almeno un operando
  • Le divisioni 0/0, ∞ / ∞, ∞ / -∞, -∞ / ∞ e -∞ / -∞
  • Le moltiplicazioni 0 × ∞ e 0 × -∞
  • Le aggiunte ∞ + (-∞), (-∞) + ∞ e sottrazioni equivalenti.
  • Applicazione di una funzione agli argomenti al di fuori del dominio, tra cui la radice quadrata di un numero negativo, il logaritmo di un numero negativo, la tangente di un multiplo dispari di 90 gradi (o π / 2 radianti) o l’inverso del seno o coseno di un numero che è inferiore a -1 o maggiore di +1.

Poiché non c’è modo di sapere quale di queste operazioni abbia creato il NaN, non c’è modo di confrontarle che abbia senso.

Non conosco la logica del design, ma ecco un estratto dallo standard IEEE 754-1985:

“Deve essere ansible confrontare i numeri in virgola mobile in tutti i formati supportati, anche se i formati degli operandi differiscono.I confronti sono esatti e non superano mai o non subiscono il trajar.Queste relazioni reciprocamente esclusive sono possibili: minori di, uguali, maggiori e non ordinate L’ultimo caso sorge quando almeno un operando è NaN, ogni NaN deve essere confrontato non ordinato con tutto, incluso se stesso. ”

Sembra strano perché la maggior parte degli ambienti di programmazione che consentono ai NaN non consentono anche la logica a 3 valori. Se lanci la logica a 3 valori nel mix, diventa coerente:

  • (2.7 == 2.7) = true
  • (2.7 == 2.6) = falso
  • (2.7 == NaN) = sconosciuto
  • (NaN == NaN) = sconosciuto

Anche .NET non fornisce un bool? operator==(double v1, double v2) bool? operator==(double v1, double v2) , quindi sei ancora bloccato con lo sciocco (NaN == NaN) = false risultato (NaN == NaN) = false .

Immagino che NaN (Not A Number) significhi esattamente questo: questo non è un numero e quindi confrontarlo non ha molto senso.

È un po ‘come l’aritmetica in SQL con gli operandi null : tutti risultano in null .

I confronti per i numeri in virgola mobile confrontano i valori numerici. Pertanto, non possono essere utilizzati per valori non numerici. Pertanto, NaN non può essere confrontato in senso numerico.

La risposta eccessivamente semplificata è che un NaN non ha valore numerico, quindi non c’è nulla in esso da confrontare con qualcos’altro.

Potresti considerare di provare e sostituire i tuoi NaN con + INF se vuoi che si comportino come + INF.

Mentre sono d’accordo sul fatto che i confronti di NaN con qualsiasi numero reale dovrebbero essere non ordinati, penso che ci sia una giusta causa per confrontare NaN con se stesso. Come, ad esempio, si scopre la differenza tra la segnalazione di NaNs e NaNs silenziosi? Se pensiamo ai segnali come a un insieme di valori booleani (cioè un vettore bit), ci si potrebbe chiedere se i bit-vettori sono uguali o diversi e ordinare gli insiemi di conseguenza. Ad esempio, sulla decodifica di un esponente con polarizzazione massima, se il significato e la sinistra sono stati spostati in modo da allineare il bit più significativo del significato e sul bit più significativo del formato binario, un valore negativo sarebbe un NaN silenzioso e qualsiasi valore positivo sarebbe essere un segnale NaN. Naturalmente, lo zero è riservato all’infinito e il confronto non sarebbe ordinato. L’allineamento MSB consentirebbe il confronto diretto dei segnali anche da diversi formati binari. Due NaN con lo stesso insieme di segnali sarebbero quindi equivalenti e dare un significato all’uguaglianza.

NaN è una nuova istanza implicita (di un tipo speciale di errore di runtime). Ciò significa NaN !== NaN per la stessa ragione del new Error !== new Error ;

E tenere a mente che tale implicità è vista anche fuori dagli errori, per esempio nel contesto delle espressioni regolari significa /a/ !== /a/ che è solo syntax zucchero per il new RegExp('a') !== new RegExp('a')

Perché la matematica è il campo in cui i numeri “esistono”. Nel calcolo è necessario inizializzare tali numeri e mantenere il loro stato in base alle proprie esigenze. In quei vecchi tempi l’inizializzazione della memoria funzionava in modi in cui non si poteva mai fare affidamento. Non hai mai potuto permetterti di pensare a questo “oh, sarebbe stato inizializzato con 0xCD tutto il tempo, il mio algo non si sarebbe rotto” .

Quindi è necessario un solvente di non miscelazione adeguato, abbastanza appiccicoso da non lasciare che il tuo algoritmo venga risucchiato e rotto. I buoni algoritmi che coinvolgono i numeri funzionano principalmente con le relazioni e quelle if () saranno omesse.

Questo è solo grasso che puoi inserire nella nuova variabile alla creazione, invece di programmare l’inferno casuale dalla memoria del computer. E il tuo algoritmo qualunque cosa sia, non si romperà.

Successivamente, quando improvvisamente scopri che il tuo algoritmo sta producendo NaN, è ansible eliminarlo, esaminando ogni ramo uno alla volta. Ancora una volta, la regola “sempre falso” sta aiutando molto in questo.

Per me, il modo più semplice per spiegarlo è:

Ho qualcosa e se non è una mela, allora è un’arancia?

Non puoi confrontare NaN con qualcos’altro (anche se stesso) perché non ha un valore. Inoltre può essere qualsiasi valore (tranne un numero).

Ho qualcosa e se non è uguale a un numero, allora è una stringa?

Risposta molto breve:

Perché il seguente: nan / nan = 1 NON deve contenere. Altrimenti inf/inf sarebbe 1.

(Quindi nan non può essere uguale a nan . Per quanto riguarda > o < , se nan rispetterebbe qualsiasi relazione di ordine in un set che soddisfi la proprietà di Archimede, avremmo di nuovo nan / nan = 1 al limite).