Warum wird (inf + 0j) * 1 zu inf + nanj ausgewertet?

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

Warum? Dies verursachte einen bösen Fehler in meinem Code.

Warum gibt 1die multiplikative Identität nicht (inf + 0j)?

marnix
quelle
1
Ich denke, das Schlüsselwort, das Sie suchen, ist " Feld ". Addition und Multiplikation werden standardmäßig in einem einzelnen Feld definiert. In diesem Fall ist das einzige Standardfeld, das Ihren Code aufnehmen kann, das Feld komplexer Zahlen. Daher müssen beide Zahlen standardmäßig als komplexe Zahlen behandelt werden, bevor die Operation erfolgreich ist. definiert. Das heißt nicht, dass sie diese Definitionen nicht erweitern konnten , aber anscheinend gingen sie einfach mit der Standardsache um und verspürten keinen Drang, sich die Mühe zu machen, die Definitionen zu erweitern.
user541686
1
Oh, und wenn Sie diese Eigenheiten frustrierend finden und Ihren Computer schlagen wollen, haben Sie mein Mitgefühl .
user541686
2
@Mehrdad Sobald Sie diese nicht finiten Elemente hinzugefügt haben, ist es kein Feld mehr. Da es keine multiplikative Neutralität mehr gibt, kann es per Definition kein Feld sein.
Paul Panzer
@PaulPanzer: Ja, ich denke, sie haben diese Elemente danach einfach hineingeschoben.
user541686
1
Gleitkommazahlen (auch wenn Sie unendlich und NaN ausschließen) sind kein Feld. Die meisten Identitäten, die für Felder gelten, gelten nicht für Gleitkommazahlen.
Plugwash

Antworten:

95

Das 1wird zuerst in eine komplexe Zahl umgewandelt 1 + 0j, was dann zu einer inf * 0Multiplikation führt, was zu a führt 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
Marat
quelle
8
Für die Beantwortung der Frage "Warum ...?" Ist der wahrscheinlich wichtigste Schritt der erste, an den 1gegossen wird 1 + 0j.
Warren Weckesser
5
Beachten Sie, dass C99 angibt, dass echte Gleitkommatypen beim Multiplizieren mit einem komplexen Typ nicht zu komplex heraufgestuft werden (Abschnitt 6.3.1.8 des Standardentwurfs). Soweit ich weiß, gilt dies auch für std :: complex von C ++. Dies kann teilweise aus Leistungsgründen geschehen, vermeidet aber auch unnötige NaNs.
Benrg
@benrg In NumPy wird array([inf+0j])*1auch ausgewertet array([inf+nanj]). Unter der Annahme, dass die eigentliche Multiplikation irgendwo im C / C ++ - Code stattfindet, würde dies bedeuten, dass sie benutzerdefinierten Code geschrieben haben, um das CPython-Verhalten zu emulieren, anstatt _Complex oder std :: complex zu verwenden?
Marlix
1
@marnix es ist mehr als das beteiligt. numpyhat eine zentrale Klasse, ufuncvon der fast jeder Operator und jede Funktion abgeleitet ist. ufunckümmert sich um die Verwaltung des Rundfunks und macht all den kniffligen Administrator fertig, der das Arbeiten mit Arrays so bequem macht. Genauer gesagt besteht die Arbeitsteilung zwischen einem bestimmten Bediener und der allgemeinen Maschine darin, dass der bestimmte Bediener für jede Kombination von Eingabe- und Ausgabeelementtypen, die er verarbeiten möchte, eine Reihe von "innersten Schleifen" implementiert. Die allgemeine Maschinerie kümmert sich um alle äußeren Schleifen und wählt die am besten passende innerste Schleife aus ...
Paul Panzer
1
... nach Bedarf für nicht genau passende Typen werben. Wir können über die Liste der vorgesehenen inneren Schleife Zugang typesfür Attribute np.multiplydieser Erträge ['??->?', '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']können wir sehen , dass es so gut wie keine Mischtypen sind, insbesondere keine , die Mischung Schwimmer "efdg"mit komplexen "FDG".
Paul Panzer
32

Mechanistisch gesehen ist die akzeptierte Antwort natürlich richtig, aber ich würde argumentieren, dass eine tiefere Antwort gegeben werden kann.

Zunächst ist es nützlich, die Frage zu klären, wie es @PeterCordes in einem Kommentar tut: "Gibt es eine multiplikative Identität für komplexe Zahlen, die mit inf + 0j funktioniert?" oder mit anderen Worten, OP sieht eine Schwäche in der Computerimplementierung komplexer Multiplikation oder gibt es etwas, das konzeptionell nicht stimmtinf+0j

Kurze Antwort:

Mit Hilfe von Polarkoordinaten können wir komplexe Multiplikationen als Skalierung und Rotation betrachten. Wenn wir einen unendlichen "Arm" sogar um 0 Grad drehen, wie im Fall des Multiplizierens mit einem, können wir nicht erwarten, dass seine Spitze mit endlicher Präzision platziert wird. In der Tat stimmt etwas grundsätzlich nicht inf+0j, nämlich dass, sobald wir im Unendlichen sind, ein endlicher Versatz bedeutungslos wird.

Lange Antwort:

Hintergrund: Die "große Sache", um die sich diese Frage dreht, ist die Erweiterung eines Zahlensystems (denken Sie an reelle oder komplexe Zahlen). Ein Grund, warum man das tun möchte, ist, ein Konzept der Unendlichkeit hinzuzufügen oder zu "verdichten", wenn man zufällig Mathematiker ist. Es gibt auch andere Gründe ( https://en.wikipedia.org/wiki/Galois_theory , https://en.wikipedia.org/wiki/Non-standard_analysis ), aber wir sind hier nicht interessiert.

Einpunktverdichtung

Das Knifflige an einer solchen Erweiterung ist natürlich, dass diese neuen Zahlen in die vorhandene Arithmetik passen sollen. Am einfachsten ist es, ein einzelnes Element im Unendlichen hinzuzufügen ( https://en.wikipedia.org/wiki/Alexandroff_extension ) und es gleich Null zu machen, geteilt durch Null. Dies funktioniert für die Realzahlen ( https://en.wikipedia.org/wiki/Projectively_extended_real_line ) und die komplexen Zahlen ( https://en.wikipedia.org/wiki/Riemann_sphere ).

Andere Erweiterungen ...

Während die Ein-Punkt-Verdichtung einfach und mathematisch fundiert ist, wurden "reichhaltigere" Erweiterungen mit mehreren Infinties gesucht. Der IEEE 754-Standard für echte Gleitkommazahlen hat + inf und -inf ( https://en.wikipedia.org/wiki/Extended_real_number_line ). Sieht natürlich und unkompliziert aus, zwingt uns aber bereits dazu, durch Reifen zu springen und Dinge wie https://en.wikipedia.org/wiki/Signed_zero zu erfinden-0

... der komplexen Ebene

Was ist mit mehr als einer Inf-Erweiterung der komplexen Ebene?

In Computern werden komplexe Zahlen typischerweise implementiert, indem zwei fp-Realzahlen zusammengefügt werden, eine für den Realwert und eine für den Imaginärteil. Das ist vollkommen in Ordnung, solange alles endlich ist. Sobald jedoch Unendlichkeiten betrachtet werden, werden die Dinge schwierig.

Die komplexe Ebene hat eine natürliche Rotationssymmetrie, die sich gut in die komplexe Arithmetik einfügt, da das Multiplizieren der gesamten Ebene mit e ^ phij dasselbe ist wie eine Phi-Radian-Rotation um 0.

Das Anhang-G-Ding

Um die Dinge einfach zu halten, verwendet komplexes fp einfach die Erweiterungen (+/- inf, nan usw.) der zugrunde liegenden Realzahlimplementierung. Diese Wahl mag so natürlich erscheinen, dass sie nicht einmal als Wahl wahrgenommen wird, aber schauen wir uns genauer an, was sie impliziert. Eine einfache Visualisierung dieser Erweiterung der komplexen Ebene sieht aus wie (I = unendlich, f = endlich, 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

Da es sich bei einer echten komplexen Ebene jedoch um eine Ebene handelt, die die komplexe Multiplikation berücksichtigt, wäre eine aussagekräftigere Projektion erforderlich

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

In dieser Projektion sehen wir die "ungleichmäßige Verteilung" von Unendlichkeiten, die nicht nur hässlich ist, sondern auch die Wurzel von Problemen der Art, unter der OP gelitten hat: Die meisten Unendlichkeiten (die der Formen (+/- inf, endlich) und (endlich, +) / -inf) werden in den vier Hauptrichtungen zusammengefasst, alle anderen Richtungen werden durch nur vier Unendlichkeiten (+/- inf, + -inf) dargestellt. Es sollte nicht überraschen, dass die Erweiterung der komplexen Multiplikation auf diese Geometrie ein Albtraum ist .

Anhang G der C99-Spezifikation versucht sein Bestes, damit es funktioniert, einschließlich des Verbiegens der Regeln für die Art infund Weise und nanInteraktion (im Wesentlichen infTrümpfe nan). Das Problem von OP wird umgangen, indem Real und ein vorgeschlagener rein imaginärer Typ nicht zum Komplex befördert werden, aber wenn sich die reale 1 anders verhält als die komplexe 1, erscheint mir dies nicht als Lösung. Bezeichnenderweise wird in Anhang G nicht vollständig angegeben, was das Produkt zweier Unendlichkeiten sein soll.

Können wir es besser machen?

Es ist verlockend, diese Probleme durch die Wahl einer besseren Geometrie der Unendlichkeiten zu beheben. In Analogie zur erweiterten reellen Linie könnten wir für jede Richtung eine Unendlichkeit hinzufügen. Diese Konstruktion ähnelt der Projektionsebene, fasst jedoch keine entgegengesetzten Richtungen zusammen. Unendlichkeiten würden in Polarkoordinaten inf xe ^ {2 omega pi i} dargestellt, die Definition von Produkten wäre unkompliziert. Insbesondere würde das Problem von OP ganz natürlich gelöst.

Aber hier endet die gute Nachricht. In gewisser Weise können wir zurück auf den ersten Platz geschleudert werden, indem wir - nicht unangemessen - verlangen, dass unsere Newstyle-Unendlichkeiten Funktionen unterstützen, die ihre Real- oder Imaginärteile extrahieren. Addition ist ein weiteres Problem; Wenn wir zwei nichtantipodale Unendlichkeiten hinzufügen, müssten wir den Winkel auf undefiniert setzen, dh nan(man könnte argumentieren, dass der Winkel zwischen den beiden Eingabewinkeln liegen muss, aber es gibt keine einfache Möglichkeit, diese "partielle Nanität" darzustellen).

Riemann zur Rettung

In Anbetracht dessen ist vielleicht die gute alte Ein-Punkt-Verdichtung die sicherste Sache. Vielleicht haben die Autoren von Anhang G dasselbe empfunden, als sie eine Funktion beauftragt haben cproj, die alle Unendlichkeiten zusammenfasst.


Hier ist eine verwandte Frage , die von Personen beantwortet wird, die in diesem Bereich kompetenter sind als ich.

Paul Panzer
quelle
5
Ja, weil nan != nan. Ich verstehe, dass diese Antwort nur ein Scherz ist, aber ich verstehe nicht, warum sie für das OP so hilfreich sein sollte, wie sie geschrieben ist.
cmaster
Angesichts der Tatsache, dass der Code im Fragentext nicht tatsächlich verwendet wurde ==(und die andere Antwort akzeptiert wurde), scheint es nur ein Problem zu sein, wie das OP den Titel ausdrückt. Ich habe den Titel umformuliert, um diese Inkonsistenz zu beheben. (Absichtlich die erste Hälfte dieser Antwort ungültig machen, weil ich @cmaster zustimme: Das ist nicht das, worüber diese Frage gestellt wurde).
Peter Cordes
3
@PeterCordes, das wäre problematisch, weil wir mit Polarkoordinaten komplexe Multiplikationen als Skalierung und Rotation betrachten können. Wenn wir einen unendlichen "Arm" sogar um 0 Grad drehen, wie im Fall des Multiplizierens mit einem, können wir nicht erwarten, dass seine Spitze mit endlicher Präzision platziert wird. Dies ist meiner Meinung nach eine tiefere Erklärung als die akzeptierte und auch eine mit Echos in der nan! = Nan-Regel.
Paul Panzer
3
C99 gibt an, dass echte Gleitkommatypen beim Multiplizieren mit einem komplexen Typ nicht zu komplex heraufgestuft werden (Abschnitt 6.3.1.8 des Standardentwurfs), und soweit ich weiß, gilt dies auch für std :: complex von C ++. Dies bedeutet, dass 1 eine multiplikative Identität für diese Typen in diesen Sprachen ist. Python sollte dasselbe tun. Ich würde sein aktuelles Verhalten einfach als Fehler bezeichnen.
Benrg
2
@PaulPanzer: Ich nicht, aber das Grundkonzept wäre, dass eine Null (die ich Z nennen werde) immer x + Z = x und x * Z = Z und 1 / Z = NaN, eins (positiv) hochhält infinitesimal) würde 1 / P = + INF aufrechterhalten, eins (negatives infinitesimal) würde 1 / N = -INF aufrechterhalten und (vorzeichenloses infinitesimal) würde 1 / U = NaN ergeben. Im Allgemeinen wäre xx U, es sei denn, x ist eine echte ganze Zahl. In diesem Fall würde es Z ergeben.
Supercat
6

Dies ist ein Implementierungsdetail dafür, wie komplexe Multiplikationen in CPython implementiert werden. Im Gegensatz zu anderen Sprachen (z. B. C oder C ++) verfolgt CPython einen etwas vereinfachten Ansatz:

  1. Ints / Floats werden bei der Multiplikation zu komplexen Zahlen befördert
  2. Es wird die einfache Schulformel verwendet , die keine gewünschten / erwarteten Ergebnisse liefert, sobald es sich um unendliche Zahlen handelt:
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;
}

Ein problematischer Fall mit dem obigen Code wäre:

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

Man möchte jedoch -inf + inf*jals Ergebnis haben.

In dieser Hinsicht sind andere Sprachen nicht weit voraus: Die Multiplikation komplexer Zahlen war lange Zeit nicht Teil des C-Standards, der nur in C99 als Anhang G enthalten ist und beschreibt, wie eine komplexe Multiplikation durchgeführt werden sollte - und sie ist nicht so einfach wie die Schulformel oben! Der C ++ - Standard legt nicht fest, wie die komplexe Multiplikation funktionieren soll. Daher greifen die meisten Compiler-Implementierungen auf die C-Implementierung zurück, die möglicherweise C99-konform ist (gcc, clang) oder nicht (MSVC).

Für das obige "problematische" Beispiel würden C99-konforme Implementierungen (die komplizierter sind als die Schulformel ) das erwartete Ergebnis liefern ( siehe live ):

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

Selbst mit dem C99-Standard ist ein eindeutiges Ergebnis nicht für alle Eingaben definiert und kann auch für C99-kompatible Versionen unterschiedlich sein.

Ein weiterer Nebeneffekt der floatNicht-Beförderung complexin C99 ist, dass die Multiplikation inf+0.0jmit 1.0oder 1.0+0.0jzu unterschiedlichen Ergebnissen führen kann (siehe hier live):

  • (inf+0.0j)*1.0 = inf+0.0j
  • (inf+0.0j)*(1.0+0.0j) = inf-nanjDas Imaginärteil Sein -nanund Nicht- Sein nan(wie bei CPython) spielt hier keine Rolle, da alle stillen Nans gleichwertig sind (siehe dies ), sogar einige von ihnen haben ein Vorzeichen-Bit gesetzt (und werden daher als "-" gedruckt, siehe dies) ) und manche nicht.

Welches ist zumindest kontraintuitiv.


Meine wichtigste Erkenntnis ist: Es gibt nichts Einfaches an der "einfachen" Multiplikation (oder Division) komplexer Zahlen, und wenn man zwischen Sprachen oder sogar Compilern wechselt, muss man sich auf subtile Fehler / Unterschiede einstellen.

ead
quelle
Ich weiß, dass es viele Nanobitmuster gibt. Ich kannte das Zeichen-Bit-Ding allerdings nicht. Aber ich meinte semantisch Wie unterscheidet sich -nan von nan? Oder sollte ich sagen, dass mehr anders ist als nan von nan?
Paul Panzer
@PaulPanzer Dies ist nur ein Implementierungsdetail, wie printfund ähnliches mit double funktioniert: Sie sehen sich das Vorzeichenbit an, um zu entscheiden, ob "-" gedruckt werden soll oder nicht (egal ob es sich um nan handelt oder nicht). Sie haben also Recht, es gibt keinen bedeutenden Unterschied zwischen "nan" und "-nan", wodurch dieser Teil der Antwort bald behoben wird.
Lesen Sie den
Ah gut. Ich habe mir Sorgen gemacht, dass alles, was ich über fp zu wissen glaubte, nicht richtig war ...
Paul Panzer
Entschuldigen Sie, dass Sie nervig sind, aber sind Sie sicher, dass "es keine imaginäre 1.0 gibt, dh 1.0j, die in Bezug auf die Multiplikation nicht mit 0.0 + 1.0j identisch ist". ist richtig? Dieser Anhang G scheint einen rein imaginären Typ (G.2) zu spezifizieren und auch vorzuschreiben, wie er multipliziert werden soll usw. (G.5.1)
Paul Panzer
@PaulPanzer Nein, danke, dass Sie auf die Probleme hingewiesen haben! Als C ++ - Codierer sehe ich meistens C99-Standard durch C ++ - Glases - es ist mir durch den Kopf gegangen, dass C hier einen Schritt voraus ist - Sie haben offensichtlich wieder Recht.
Lesen Sie den
3

Lustige Definition von Python. Wenn wir dies mit einem Stift und Papier lösen würde ich sagen , dass erwartete Ergebnis wäre , expected: (inf + 0j)wie Sie darauf hingewiesen , weil wir wissen , dass wir die Norm bedeuten 1so (float('inf')+0j)*1 =should= ('inf'+0j):

Aber das ist nicht der Fall, wie Sie sehen können ... wenn wir es ausführen, erhalten wir:

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

Python versteht dies *1als eine komplexe Zahl und nicht als die Norm, 1daher interpretiert es als *(1+0j)und der Fehler tritt auf, wenn wir versuchen, das zu tun inf * 0j = nanj, inf*0was nicht gelöst werden kann.

Was Sie tatsächlich tun möchten (vorausgesetzt, 1 ist die Norm von 1):

Denken Sie daran, dass wenn z = x + iyes sich um eine komplexe Zahl mit Realteil x und Imaginärteil y handelt, das komplexe Konjugat von zdefiniert ist als z* = x − iyund der Absolutwert, auch als norm of zbezeichnet, definiert ist als:

Geben Sie hier die Bildbeschreibung ein

Angenommen, es 1ist die Norm, dass 1wir so etwas tun sollten:

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

Ich weiß, dass es nicht sehr intuitiv ist ... aber manchmal werden Codierungssprachen anders definiert als wir es heutzutage tun.

costargc
quelle