Perché (inf + 0j) * 1 restituisce inf + nanj?


97
>>> (float('inf')+0j)*1
(inf+nanj)

Perché? Ciò ha causato un brutto bug nel mio codice.

Perché 1l'identità moltiplicativa non è dare (inf + 0j)?


1
Penso che la parola chiave che stai cercando sia " campo ". L'addizione e la moltiplicazione sono definite per impostazione predefinita all'interno di un singolo campo, e in questo caso l'unico campo standard che può ospitare il tuo codice è il campo dei numeri complessi, quindi entrambi i numeri devono essere trattati come numeri complessi per impostazione predefinita prima che l'operazione sia ben avviata- definito. Il che non vuol dire che non potessero estendere queste definizioni, ma a quanto pare hanno semplicemente seguito la cosa standard e non hanno sentito il bisogno di fare di tutto per estendere le definizioni.
user541686

1
Oh, e se trovi frustranti queste idiosincrasie e vuoi dare un pugno al tuo computer, hai la mia comprensione .
user541686

2
@Mehrdad una volta aggiunti quegli elementi non finiti, cessa di essere un campo. Infatti, poiché non esiste più un neutro moltiplicativo, non può essere per definizione un campo.
Paul Panzer

@PaulPanzer: Sì, penso che abbiano semplicemente inserito quegli elementi in seguito.
user541686

1
i numeri in virgola mobile (anche se escludi infinito e NaN) non sono un campo. La maggior parte delle identità che valgono per i campi non valgono per i numeri in virgola mobile.
plugwash

Risposte:


95

La 1viene convertito in un numero complesso prima 1 + 0j, che poi porta ad una inf * 0moltiplicazione, con conseguente nan.

(inf + 0j) * 1
(inf + 0j) * (1 + 0j)
inf * 1  + inf * 0j  + 0j * 1 + 0j * 0j
#          ^ this is where it comes from
inf  + nan j  + 0j - 0
inf  + nan j

8
Per rispondere alla domanda "perché ...?", Probabilmente il passo più importante è il primo, dove 1viene lanciato 1 + 0j.
Warren Weckesser,

5
Si noti che C99 specifica che i tipi a virgola mobile reali non vengono promossi a complessi quando vengono moltiplicati per un tipo complesso (sezione 6.3.1.8 della bozza di standard), e per quanto ne so lo stesso vale per std :: complex di C ++. Ciò può essere in parte dovuto a motivi di prestazioni, ma evita anche NaN non necessari.
benrg

@benrg In NumPy, restituisce array([inf+0j])*1anche a array([inf+nanj]). Supponendo che la moltiplicazione effettiva avvenga da qualche parte nel codice C / C ++, significherebbe che hanno scritto codice personalizzato per emulare il comportamento di CPython, piuttosto che usare _Complex o std :: complex?
marnix

1
@marnix è più coinvolto di così. numpyha una classe centrale ufuncda cui derivano quasi tutti gli operatori e le funzioni. ufuncsi occupa della trasmissione, gestendo passi da gigante tutto quel complicato amministratore che rende il lavoro con gli array così conveniente. Più precisamente la divisione del lavoro tra un operatore specifico e la macchina generale è che l'operatore specifico implementa una serie di "cicli più interni" per ogni combinazione di tipi di elementi di input e output che desidera gestire. La macchina generale si prende cura di tutti i loop esterni e seleziona il miglior match loop più interno ...
Paul Panzer

1
... promuovendo tipi non esattamente corrispondenti come richiesto. Possiamo accedere all'elenco dei cicli interni forniti tramite l' typesattributo per np.multiplyquesto rendimenti ['??->?', 'bb->b', 'BB->B', 'hh->h', 'HH->H', 'ii->i', 'II->I', 'll->l', 'LL->L', 'qq->q', 'QQ->Q', 'ee->e', 'ff->f', 'dd->d', 'gg->g', 'FF->F', 'DD->D', 'GG->G', 'mq->m', 'qm->m', 'md->m', 'dm->m', 'OO->O']possiamo vedere che non ci sono quasi tipi misti, in particolare, nessuno che mescola float "efdg"con complex "FDG".
Paul Panzer

32

Dal punto di vista meccanico, la risposta accettata è, ovviamente, corretta, ma direi che si può dare una risposta più profonda.

Innanzitutto, è utile chiarire la domanda come fa @PeterCordes in un commento: "Esiste un'identità moltiplicativa per numeri complessi che funziona su inf + 0j?" o in altre parole è ciò che OP vede come debolezza nell'implementazione al computer di moltiplicazioni complesse o c'è qualcosa di concettualmente non corretto coninf+0j

Risposta breve:

Usando le coordinate polari possiamo vedere la moltiplicazione complessa come una scala e una rotazione. Ruotando un "braccio" infinito anche di 0 gradi come nel caso della moltiplicazione per uno non possiamo aspettarci di posizionare la sua punta con una precisione finita. Quindi, in effetti, c'è qualcosa di fondamentalmente non giusto inf+0j, vale a dire che non appena siamo all'infinito un offset finito diventa privo di significato.

Risposta lunga:

Background: la "cosa importante" attorno a cui ruota questa domanda è la questione dell'estensione di un sistema di numeri (si pensi ai numeri reali o complessi). Uno dei motivi per cui si potrebbe desiderare di farlo è aggiungere un concetto di infinito o "compattarsi" se si è matematici. Ci sono anche altri motivi ( https://en.wikipedia.org/wiki/Galois_theory , https://en.wikipedia.org/wiki/Non-standard_analysis ), ma qui non ci interessano.

Un punto di compattazione

La parte complicata di tale estensione è, ovviamente, che vogliamo che questi nuovi numeri si adattino all'aritmetica esistente. Il modo più semplice è aggiungere un singolo elemento all'infinito ( https://en.wikipedia.org/wiki/Alexandroff_extension ) e renderlo uguale a tutto tranne che a zero diviso per zero. Questo funziona per i reali ( https://en.wikipedia.org/wiki/Projectively_extended_real_line ) e per i numeri complessi ( https://en.wikipedia.org/wiki/Riemann_sphere ).

Altre estensioni ...

Sebbene la compattazione a un punto sia semplice e matematicamente valida, sono state cercate estensioni "più ricche" comprendenti infiniti multipli. Lo standard IEEE 754 per i numeri in virgola mobile reali ha + inf e -inf ( https://en.wikipedia.org/wiki/Extended_real_number_line ). Sembra naturale e diretto, ma ci costringe già a fare i salti mortali e inventare cose come -0 https://en.wikipedia.org/wiki/Signed_zero

... dell'aereo complesso

E le estensioni più di una inf del piano complesso?

Nei computer, i numeri complessi vengono tipicamente implementati unendo due reali fp insieme uno per la parte reale e uno per la parte immaginaria. Va benissimo finché tutto è finito. Tuttavia, non appena gli infiniti vengono considerati, le cose diventano complicate.

Il piano complesso ha una simmetria rotazionale naturale, che si lega bene con l'aritmetica complessa poiché moltiplicare l'intero piano per e ^ phij è la stessa di una rotazione phi radiante intorno 0.

Quella cosa dell'allegato G.

Ora, per mantenere le cose semplici, complesse fp usa semplicemente le estensioni (+/- inf, nan ecc.) Dell'implementazione del numero reale sottostante. Questa scelta può sembrare così naturale da non essere nemmeno percepita come una scelta, ma diamo un'occhiata più da vicino a cosa implica. Una semplice visualizzazione di questa estensione del piano complesso appare come (I = infinito, f = finito, 0 = 0)

I IIIIIIIII I
             
I fffffffff I
I fffffffff I
I fffffffff I
I fffffffff I
I ffff0ffff I
I fffffffff I
I fffffffff I
I fffffffff I
I fffffffff I
             
I IIIIIIIII I

Ma poiché un vero piano complesso è quello che rispetta la moltiplicazione complessa, sarebbe una proiezione più informativa

     III    
 I         I  
    fffff    
   fffffff   
  fffffffff  
I fffffffff I
I ffff0ffff I
I fffffffff I
  fffffffff  
   fffffff   
    fffff    
 I         I 
     III    

In questa proiezione vediamo la "distribuzione irregolare" degli infiniti che non è solo brutta ma anche la radice dei problemi del tipo che OP ha sofferto: la maggior parte degli infiniti (quelli delle forme (+/- inf, finite) e (finite, + / -inf) sono raggruppati nelle quattro direzioni principali tutte le altre direzioni sono rappresentate solo da quattro infiniti (+/- inf, + -inf) .Non dovrebbe sorprendere che estendere la moltiplicazione complessa a questa geometria sia un incubo .

L'allegato G della specifica C99 fa del suo meglio per farlo funzionare, compreso il piegamento delle regole su come infe naninteragire (essenzialmente inftrionfi nan). Il problema di OP viene aggirato non promuovendo i reali e un tipo proposto puramente immaginario a complesso, ma il fatto che il reale 1 si comporti in modo diverso dal complesso 1 non mi sembra una soluzione. Significativamente, l'allegato G non specifica completamente quale dovrebbe essere il prodotto di due infiniti.

Possiamo fare di meglio?

Si è tentati di provare a risolvere questi problemi scegliendo una migliore geometria degli infiniti. In analogia alla linea reale estesa potremmo aggiungere un infinito per ogni direzione. Questa costruzione è simile al piano proiettivo ma non raggruppa le direzioni opposte. Le infinità sarebbero rappresentate in coordinate polari inf xe ^ {2 omega pi i}, definire i prodotti sarebbe semplice. In particolare, il problema di OP verrebbe risolto in modo del tutto naturale.

Ma è qui che finisce la buona notizia. In un certo senso possiamo essere riportati al punto di partenza --- non irragionevolmente --- richiedendo che i nostri infiniti di nuovo stile supportino funzioni che estraggono le loro parti reali o immaginarie. L'addizione è un altro problema; aggiungendo due infiniti non antipodali dovremmo impostare l'angolo su undefined ie nan(si potrebbe sostenere che l'angolo deve trovarsi tra i due angoli di input ma non esiste un modo semplice per rappresentare quella "nanometria parziale")

Riemann in soccorso

Alla luce di tutto ciò, forse la buona vecchia compattazione a un punto è la cosa più sicura da fare. Forse gli autori dell'Allegato G la pensavano allo stesso modo quando imponevano una funzione cprojche raggruppasse tutti gli infiniti.


Ecco una domanda correlata a cui hanno risposto persone più competenti in materia di me.


5
Sì, perché nan != nan. Capisco che questa risposta sia quasi scherzosa, ma non vedo perché dovrebbe essere utile all'OP nel modo in cui è scritta.
cmaster - ripristina monica il

Dato che il codice nel corpo della domanda non stava effettivamente utilizzando ==(e dato che hanno accettato l'altra risposta), sembra che fosse solo un problema di come l'OP ha espresso il titolo. Ho riformulato il titolo per correggere quell'incongruenza. (Invalido intenzionalmente la prima metà di questa risposta perché sono d'accordo con @cmaster: non è quello su cui si stava chiedendo questa domanda).
Peter Cordes,

3
@PeterCordes che sarebbe preoccupante perché utilizzando coordinate polari possiamo visualizzare la moltiplicazione complessa come un ridimensionamento e una rotazione. Ruotando un "braccio" infinito anche di 0 gradi come nel caso della moltiplicazione per uno non possiamo aspettarci di posizionare la sua punta con una precisione finita. Questa è a mio parere una spiegazione più profonda di quella accettata, e anche una con echi nella regola nan! = Nan.
Paul Panzer,

3
C99 specifica che i tipi a virgola mobile reali non vengono promossi a complessi quando vengono moltiplicati per un tipo complesso (sezione 6.3.1.8 della bozza di standard), e per quanto ne so lo stesso vale per std :: complex di C ++. Ciò significa che 1 è un'identità moltiplicativa per quei tipi in quelle lingue. Python dovrebbe fare lo stesso. Definirei il suo comportamento attuale semplicemente un bug.
benrg

2
@PaulPanzer: non lo so, ma il concetto di base sarebbe che uno zero (che chiamerò Z) sosterrebbe sempre x + Z = x e x * Z = Z e 1 / Z = NaN, uno (positivo infinitesimale) sosterrebbe 1 / P = + INF, uno (infinitesimale negativo) sosterrebbe 1 / N = -INF e (infinitesimale senza segno) restituirebbe 1 / U = NaN. In generale, xx sarebbe U a meno che x non sia un numero intero vero, nel qual caso restituirebbe Z.
supercat

6

Questo è un dettaglio di implementazione di come la moltiplicazione complessa viene implementata in CPython. A differenza di altri linguaggi (ad esempio C o C ++), CPython adotta un approccio un po 'semplicistico:

  1. Gli int / float vengono promossi a numeri complessi nella moltiplicazione
  2. viene utilizzata la semplice formula-scuola , che non fornisce risultati desiderati / attesi non appena sono coinvolti numeri infiniti:
Py_complex
_Py_c_prod(Py_complex a, Py_complex b)
{
    Py_complex r;
    r.real = a.real*b.real - a.imag*b.imag;
    r.imag = a.real*b.imag + a.imag*b.real;
    return r;
}

Un caso problematico con il codice sopra sarebbe:

(0.0+1.0*j)*(inf+inf*j) = (0.0*inf-1*inf)+(0.0*inf+1.0*inf)j
                        =  nan + nan*j

Tuttavia, si vorrebbe avere -inf + inf*jcome risultato.

Da questo punto di vista altre lingue non sono molto più avanti: la moltiplicazione di numeri complessi è stata per lungo tempo non parte dello standard C, incluso solo in C99 come appendice G, che descrive come dovrebbe essere eseguita una moltiplicazione complessa - e non è così semplice come la formula della scuola sopra! Lo standard C ++ non specifica come dovrebbe funzionare la moltiplicazione complessa, quindi la maggior parte delle implementazioni del compilatore ricadono sull'implementazione C, che potrebbe essere conforme a C99 (gcc, clang) o meno (MSVC).

Per l'esempio "problematico" di cui sopra, le implementazioni conformi a C99 (che sono più complicate della formula della scuola) darebbero ( guarda dal vivo ) il risultato atteso:

(0.0+1.0*j)*(inf+inf*j) = -inf + inf*j 

Anche con lo standard C99, un risultato univoco non è definito per tutti gli ingressi e potrebbe essere diverso anche per le versioni conformi a C99.

Un altro effetto collaterale di floatnon essere promossi complexin C99 è che moltiplicarsi inf+0.0jcon 1.0o 1.0+0.0jpuò portare a risultati diversi (vedi qui dal vivo):

  • (inf+0.0j)*1.0 = inf+0.0j
  • (inf+0.0j)*(1.0+0.0j) = inf-nanj, la parte immaginaria che è -nane non nan(come per CPython) non gioca un ruolo qui, perché tutti i nan silenziosi sono equivalenti (vedi questo ), anche alcuni di loro hanno set di bit di segno (e quindi stampati come "-", vedi questo ) e altri no.

Che è almeno controintuitivo.


La mia conclusione chiave è: non c'è niente di semplice nella moltiplicazione (o divisione) di numeri complessi "semplici" e quando si passa da una lingua all'altra o anche da un compilatore è necessario prepararsi a piccoli bug / differenze.


So che ci sono molti modelli nan bit. Non conoscevo la cosa del segno, però. Ma intendevo semanticamente In che modo -nan è diverso da nan? O dovrei dire più diverso di nan è da nan?
Paul Panzer

@PaulPanzer Questo è solo un dettaglio di implementazione di come funziona printfe simili con double: guardano al bit di segno per decidere se "-" deve essere stampato o meno (non importa se è nan o no). Quindi hai ragione, non c'è alcuna differenza significativa tra "nan" e "-nan", risolvendo presto questa parte della risposta.
ead

Ah bene. Mi sono preoccupato per un mese che tutto quello che pensavo di sapere su fp non fosse effettivamente corretto ...
Paul Panzer

Ci scusiamo per essere fastidioso ma sei sicuro che "non esiste 1.0 immaginario, cioè 1.0j che non è uguale a 0.0 + 1.0j rispetto alla moltiplicazione". è corretto? Quell'allegato G sembra specificare un tipo puramente immaginario (G.2) e anche prescrivere come dovrebbe essere moltiplicato ecc. (G.5.1)
Paul Panzer

@PaulPanzer No, grazie per aver segnalato i problemi! Come c ++ - programmatore, vedo principalmente lo standard C99 attraverso C ++ - glases - mi è passato di mente, che C è un passo avanti qui - hai ragione ovviamente, ancora una volta.
Leggi il

3

Definizione divertente da Python. Se stiamo risolvendo questo problema con carta e penna, direi che il risultato atteso sarebbe expected: (inf + 0j)come hai sottolineato perché sappiamo che intendiamo la norma di 1questo (float('inf')+0j)*1 =should= ('inf'+0j):

Ma non è così come puoi vedere ... quando lo eseguiamo otteniamo:

>>> Complex( float('inf') , 0j ) * 1
result: (inf + nanj)

Python lo comprende *1come un numero complesso e non la norma, 1quindi lo interpreta come *(1+0j)e l'errore appare quando proviamo a fare inf * 0j = nanjperché inf*0non può essere risolto.

Cosa vuoi effettivamente fare (supponendo che 1 sia la norma di 1):

Ricorda che se z = x + iyè un numero complesso con parte reale x e parte immaginaria y, il coniugato complesso di zè definito come z* = x − iy, e il valore assoluto, chiamato anche il, norm of zè definito come:

inserisci qui la descrizione dell'immagine

Supponendo che 1sia la norma 1che dovremmo fare qualcosa come:

>>> c_num = complex(float('inf'),0)
>>> value = 1
>>> realPart=(c_num.real)*value
>>> imagPart=(c_num.imag)*value
>>> complex(realPart,imagPart)
result: (inf+0j)

non molto intuitivo lo so ... ma a volte i linguaggi di codifica vengono definiti in modo diverso da quello che siamo usati nel nostro giorno per giorno.

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.