>>> (float('inf')+0j)*1
(inf+nanj)
Warum? Dies verursachte einen bösen Fehler in meinem Code.
Warum gibt 1
die multiplikative Identität nicht (inf + 0j)
?
>>> (float('inf')+0j)*1
(inf+nanj)
Warum? Dies verursachte einen bösen Fehler in meinem Code.
Warum gibt 1
die multiplikative Identität nicht (inf + 0j)
?
Antworten:
Das 1
wird zuerst in eine komplexe Zahl umgewandelt 1 + 0j
, was dann zu einer inf * 0
Multiplikation 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
1
gegossen wird 1 + 0j
.
array([inf+0j])*1
auch 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?
numpy
hat eine zentrale Klasse, ufunc
von der fast jeder Operator und jede Funktion abgeleitet ist. ufunc
kü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 ...
types
für Attribute np.multiply
dieser 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"
.
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
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.
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.
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 ).
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
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
.
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 inf
und Weise und nan
Interaktion (im Wesentlichen inf
Trü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.
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).
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.
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.
==
(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).
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:
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*j
als 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 float
Nicht-Beförderung complex
in C99 ist, dass die Multiplikation inf+0.0j
mit 1.0
oder 1.0+0.0j
zu unterschiedlichen Ergebnissen führen kann (siehe hier live):
(inf+0.0j)*1.0 = inf+0.0j
(inf+0.0j)*(1.0+0.0j) = inf-nanj
Das Imaginärteil Sein -nan
und 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.
printf
und ä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.
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 1
so (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 *1
als eine komplexe Zahl und nicht als die Norm, 1
daher interpretiert es als *(1+0j)
und der Fehler tritt auf, wenn wir versuchen, das zu tun inf * 0j = nanj
, inf*0
was 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 + iy
es sich um eine komplexe Zahl mit Realteil x und Imaginärteil y handelt, das komplexe Konjugat von z
definiert ist als z* = x − iy
und der Absolutwert, auch als norm of z
bezeichnet, definiert ist als:
Angenommen, es 1
ist die Norm, dass 1
wir 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.