Skip to content

Perché (inf + 0j)*1 è valutato come inf + nanj?

Dopo aver consultato esperti del settore, programmatori di diversi rami e professori, abbiamo trovato la soluzione al problema e l'abbiamo riflessa in questo post.

Soluzione:

Il 1 viene prima convertito in un numero complesso, 1 + 0jche porta a un numero inf * 0 moltiplicazione, con il risultato di un 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

Dal punto di vista meccanico, la risposta accettata è ovviamente corretta, ma ritengo che sia possibile fornire un'analisi più approfondita.

In primo luogo, è utile chiarire la domanda come fa @PeterCordes in
@PeterCordes in un commento: "Esiste un'identità moltiplicativa per
numeri complessi che funziona su inf + 0j?".
o in altre parole è quello che OP
vede una debolezza nell'implementazione informatica della moltiplicazione complessa o è
c'è qualcosa di concettualmente sbagliato con inf+0j

Risposta breve:

Utilizzando le coordinate polari possiamo vedere la moltiplicazione complessa come una scalatura e una rotazione. Ruotando un "braccio" infinito anche di 0 gradi, come nel caso della moltiplicazione per uno, non possiamo aspettarci di collocarne la punta con precisione finita.
Quindi, in effetti, c'è qualcosa di fondamentalmente sbagliato in inf+0j, cioè,
che non appena ci troviamo all'infinito un offset finito diventa privo di significato.

Risposta lunga:

Premessa: La "grande cosa" attorno alla quale ruota questa domanda è la questione
di estendere un sistema di numeri (si pensi ai reali o ai numeri complessi). Una ragione
per cui lo si vuole fare è aggiungere il concetto di infinito, o per "compattare", se si è un matematico.
"compattare", se si è un matematico. Ci sono anche altre
ragioni (https://en.wikipedia.org/wiki/Galois_theory, https://en.wikipedia.org/wiki/Non-standard_analysis), ma non ci interessano in questa sede.

Compattazione di un punto

Il problema di un'estensione di questo tipo è, ovviamente, che i nuovi numeri devono
numeri si inseriscano nell'aritmetica esistente. Il modo più semplice è aggiungere un
singolo elemento all'infinito
(https://en.wikipedia.org/wiki/Alexandroff_extension) e renderlo uguale a tutto ciò che non è zero diviso per zero. Questo funziona per i
reali (https://en.wikipedia.org/wiki/Projectively_extended_real_line) e i numeri complessi (https://en.wikipedia.org/wiki/Riemann_sphere).

Altre estensioni ...

Mentre la compattazione a un punto è semplice e matematicamente valida, si sono cercate estensioni più "ricche" che comprendessero più infiniti. Lo standard IEEE 754 per i numeri reali in virgola mobile prevede +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 a inventare
inventare cose come -0 https://en.wikipedia.org/wiki/Signed_zero

... del piano complesso

E le estensioni del piano complesso con più di un'informazione?

Nei computer, i numeri complessi sono tipicamente implementati mettendo insieme due fp reali, uno per la parte reale e uno per la parte immaginaria. Questo va benissimo finché tutto è finito. Tuttavia, non appena si considerano gli infiniti, le cose si complicano.

Il piano complesso ha una naturale simmetria rotazionale, che si collega bene all'aritmetica complessa, poiché moltiplicare l'intero piano per e^phij equivale a una rotazione di phi radianti intorno a 0.

Quella cosa dell'allegato G

Ora, per mantenere le cose semplici, fp complesso utilizza semplicemente le estensioni (+/-inf, nan ecc.) dell'implementazione dei numeri reali sottostante. Questa scelta può sembrare così naturale da non essere nemmeno percepita come una scelta, ma diamo un'occhiata più da vicino a ciò che implica. Una semplice visualizzazione di questa estensione del piano complesso si presenta 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 è un piano che rispetta la moltiplicazione complessa, una proiezione più informativa sarebbe

     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 non uniforme" degli infiniti che non è solo brutta, ma è anche la radice di problemi come quello di cui soffre OP: La maggior parte degli infiniti (quelli delle forme (+/-inf, finito) e (finito, +/-inf) sono raggruppati nelle quattro direzioni principali, mentre 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 delle specifiche C99 fa del suo meglio per far funzionare la cosa, anche piegando le regole su come inf e nan interagiscono (essenzialmente inf batte nan). Il problema dell'OP viene aggirato non promuovendo i reali e un tipo proposto puramente immaginario a complesso, ma far sì che l'1 reale si comporti in modo diverso dall'1 complesso non mi sembra una soluzione. È interessante notare che l'Allegato G non specifica completamente quale debba essere il prodotto di due infiniti.

Possiamo fare di meglio?

Si è tentati di risolvere questi problemi scegliendo una migliore geometria degli infiniti. In analogia con la linea reale estesa, potremmo aggiungere un infinito per ogni direzione. Questa costruzione è simile a quella del piano proiettivo, ma non mette insieme direzioni opposte.
Gli infiniti sarebbero rappresentati in coordinate polari inf x e^{2 omega pi i},
la definizione dei prodotti sarebbe semplice. In particolare, il problema di OP sarebbe risolto in modo abbastanza naturale.

Ma qui finiscono le buone notizie. In un certo senso possiamo essere riportati al punto di partenza, richiedendo - non irragionevolmente - che i nostri infiniti di nuovo stile supportino funzioni che estraggano le loro parti reali o immaginarie. L'addizione è un altro problema; aggiungendo due infiniti non antipodali, dovremmo impostare l'angolo a indefinito, cioè nan (si potrebbe sostenere che l'angolo deve essere compreso tra i due angoli in ingresso, ma non esiste un modo semplice per rappresentare questa "nanità 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 hanno pensato lo stesso quando hanno imposto una funzione cproj che raggruppa tutti gli infiniti.


Ecco una domanda correlata a cui hanno risposto persone più competenti di me sull'argomento.

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

  1. gli ints/float vengono promossi a numeri complessi nella moltiplicazione
  2. viene utilizzata la semplice formula scolastica, che non fornisce i risultati desiderati/attesi quando vengono 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 di cui 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.

A questo proposito, gli altri linguaggi non sono molto avanti: la moltiplicazione dei numeri complessi non ha fatto parte per molto tempo dello standard C, ed è stata inclusa solo nel C99 come appendice G, che descrive come dovrebbe essere eseguita una moltiplicazione complessa - e non è così semplice come la formula scolastica di cui sopra! Lo standard C++ non specifica come dovrebbe funzionare la moltiplicazione complessa, quindi la maggior parte delle implementazioni dei compilatori si rifanno all'implementazione C, che può essere conforme al C99 (gcc, clang) o meno (MSVC).

Per l'esempio "problematico" di cui sopra, le implementazioni conformi al C99 (che sono più complicate della formula scolastica) darebbero (vedi live) 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 input e potrebbe essere diverso anche per le versioni conformi al C99.

Un altro effetto collaterale di float non viene promosso a complex in C99 è che moltiplicando inf+0.0j con 1.0 o 1.0+0.0j può portare a risultati diversi (vedere qui dal vivo):

  • (inf+0.0j)*1.0 = inf+0.0j
  • (inf+0.0j)*(1.0+0.0j) = inf-nanj, essendo la parte immaginaria -nan e non nan (come per CPython) non ha alcun ruolo in questo caso, perché tutte le nane tranquille sono equivalenti (si veda questo), anche se alcune hanno il bit di segno impostato (e quindi stampato come "-", si veda questo) e altre no.

Il che è perlomeno controintuitivo.


La mia conclusione principale è: non c'è nulla di semplice nella moltiplicazione (o divisione) di numeri complessi "semplici" e quando si passa da un linguaggio all'altro o addirittura da un compilatore all'altro ci si deve preoccupare di eventuali bug/differenze.

Ti mostriamo commenti e valutazioni

Se possiedi qualche esitazione e capacità di organizzare il nostro saggio, sei in grado di scrivere un'interpretazione e noi la leggeremo volentieri.



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.