>>> (float('inf')+0j)*1
(inf+nanj)
Perché? Ciò ha causato un brutto bug nel mio codice.
Perché 1
l'identità moltiplicativa non è dare (inf + 0j)
?
>>> (float('inf')+0j)*1
(inf+nanj)
Perché? Ciò ha causato un brutto bug nel mio codice.
Perché 1
l'identità moltiplicativa non è dare (inf + 0j)
?
Risposte:
La 1
viene convertito in un numero complesso prima 1 + 0j
, che poi porta ad una inf * 0
moltiplicazione, 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
1
viene lanciato 1 + 0j
.
array([inf+0j])*1
anche 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?
numpy
ha una classe centrale ufunc
da cui derivano quasi tutti gli operatori e le funzioni. ufunc
si 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 ...
types
attributo per np.multiply
questo 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"
.
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
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.
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.
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 ).
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
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
.
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 inf
e nan
interagire (essenzialmente inf
trionfi 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.
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")
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 cproj
che raggruppasse tutti gli infiniti.
Ecco una domanda correlata a cui hanno risposto persone più competenti in materia di me.
nan != nan
. Capisco che questa risposta sia quasi scherzosa, ma non vedo perché dovrebbe essere utile all'OP nel modo in cui è scritta.
==
(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).
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:
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*j
come 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 float
non essere promossi complex
in C99 è che moltiplicarsi inf+0.0j
con 1.0
o 1.0+0.0j
può 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 è -nan
e 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.
printf
e 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.
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 1
questo (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 *1
come un numero complesso e non la norma, 1
quindi lo interpreta come *(1+0j)
e l'errore appare quando proviamo a fare inf * 0j = nanj
perché inf*0
non 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:
Supponendo che 1
sia la norma 1
che 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.