>>> (float('inf')+0j)*1
(inf+nanj)
Czemu? Spowodowało to paskudny błąd w moim kodzie.
Dlaczego 1
tożsamość multiplikatywna nie jest dawaniem (inf + 0j)
?
>>> (float('inf')+0j)*1
(inf+nanj)
Czemu? Spowodowało to paskudny błąd w moim kodzie.
Dlaczego 1
tożsamość multiplikatywna nie jest dawaniem (inf + 0j)
?
Odpowiedzi:
1
Przekształca się liczbę zespoloną pierwszy 1 + 0j
, co następnie prowadzi do inf * 0
rozmnażania, w wyniku 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
jest rzucane 1 + 0j
.
array([inf+0j])*1
również zwraca array([inf+nanj])
. Zakładając, że faktyczne mnożenie ma miejsce gdzieś w kodzie C / C ++, czy oznaczałoby to, że napisali niestandardowy kod, aby emulować zachowanie CPythona, zamiast używać _Complex lub std :: complex?
numpy
ma jedną klasę centralną, ufunc
z której wywodzi się prawie każdy operator i funkcja. ufunc
dba o rozgłaszanie, zarządzając krokami wszystkich tych podstępnych administratorów, które sprawiają, że praca z tablicami jest tak wygodna. Dokładniej, podział pracy między konkretnego operatora a maszynę ogólną polega na tym, że określony operator implementuje zestaw „najbardziej wewnętrznych pętli” dla każdej kombinacji typów elementów wejściowych i wyjściowych, które chce obsługiwać. Ogólna maszyneria dba o wszelkie zewnętrzne pętle i wybiera najlepszą wewnętrzną pętlę meczu ...
types
atrybutu dla np.multiply
tych plonów ['??->?', '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']
, widzimy, że prawie nie ma typów mieszanych, w szczególności nie ma takich, które mieszają zmiennoprzecinkowe "efdg"
ze złożonymi "FDG"
.
Pod względem mechanicznym przyjęta odpowiedź jest oczywiście poprawna, ale uważam, że można udzielić głębszej odpowiedzi.
Po pierwsze, warto wyjaśnić pytanie, tak jak robi to @PeterCordes w komentarzu: „Czy istnieje tożsamość multiplikatywna dla liczb zespolonych, która działa na inf + 0j?” lub innymi słowy, jest tym, co OP widzi słabość w implementacji komputerowej złożonego mnożenia lub czy jest coś koncepcyjnie niewłaściwego zinf+0j
Używając współrzędnych biegunowych, możemy zobaczyć złożone mnożenie jako skalowanie i obrót. Obracając nieskończone „ramię” nawet o 0 stopni, jak w przypadku pomnożenia przez jeden, nie możemy oczekiwać, że umieścimy jego końcówkę ze skończoną precyzją. Tak więc, rzeczywiście, jest coś zasadniczo nie w porządku inf+0j
, a mianowicie, że gdy tylko osiągniemy nieskończoność, skończone przesunięcie staje się bez znaczenia.
Tło: „Wielką rzeczą”, wokół której obraca się to pytanie, jest kwestia rozszerzenia systemu liczb (myśl o liczbach rzeczywistych lub zespolonych). Jednym z powodów, dla których ktoś mógłby chcieć to zrobić, jest dodanie jakiejś koncepcji nieskończoności lub „ujednolicenie”, jeśli ktoś jest matematykiem. Są też inne powody ( https://en.wikipedia.org/wiki/Galois_theory , https://en.wikipedia.org/wiki/Non-standard_analysis ), ale nie jesteśmy nimi zainteresowani.
Problem z takim rozszerzeniem polega oczywiście na tym, że chcemy, aby te nowe liczby pasowały do istniejącej arytmetyki. Najprostszym sposobem jest dodanie pojedynczego elementu w nieskończoności ( https://en.wikipedia.org/wiki/Alexandroff_extension ) i uczynienie go równym wszystkim innym niż zero podzielone przez zero. Działa to w przypadku liczb rzeczywistych ( https://en.wikipedia.org/wiki/Projectively_extended_real_line ) i liczb zespolonych ( https://en.wikipedia.org/wiki/Riemann_sphere ).
Chociaż jednopunktowe zagęszczenie jest proste i matematycznie poprawne, poszukiwano „bogatszych” rozszerzeń obejmujących wiele infintiesów. Standard IEEE 754 dla rzeczywistych liczb zmiennoprzecinkowych ma + inf i -inf ( https://en.wikipedia.org/wiki/Extended_real_number_line ). Wygląda naturalnie i prosto, ale już zmusza nas do skakania przez obręcze i wymyślania rzeczy takich jak -0
https://en.wikipedia.org/wiki/Signed_zero
A co z więcej niż jednym rozszerzeniem inf płaszczyzny złożonej?
W komputerach liczby zespolone są zwykle implementowane przez sklejenie dwóch liczb rzeczywistych fp, jednej dla części rzeczywistej i jednej dla części urojonej. To jest w porządku, o ile wszystko jest skończone. Jednak gdy tylko uważa się, że nieskończoności sprawy stają się trudne.
Płaszczyzna zespolona ma naturalną symetrię obrotową, która dobrze łączy się ze złożoną arytmetyką, ponieważ pomnożenie całej płaszczyzny przez e ^ phij jest tym samym, co obrót fi radianu wokół 0
.
Teraz, aby wszystko było proste, złożone fp po prostu używa rozszerzeń (+/- inf, nan itd.) Podstawowej implementacji liczb rzeczywistych. Ten wybór może wydawać się tak naturalny, że nie jest nawet postrzegany jako wybór, ale przyjrzyjmy się bliżej, co on oznacza. Prosta wizualizacja tego przedłużenia płaszczyzny zespolonej wygląda następująco (I = nieskończony, f = skończony, 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
Ale ponieważ prawdziwa złożona płaszczyzna to taka, która uwzględnia złożone mnożenie, projekcja byłaby bardziej pouczająca
III
I I
fffff
fffffff
fffffffff
I fffffffff I
I ffff0ffff I
I fffffffff I
fffffffff
fffffff
fffff
I I
III
W tej projekcji widzimy „nierównomierny rozkład” nieskończoności, który jest nie tylko brzydki, ale także źródłem problemów tego rodzaju, na jakie cierpiał OP: większość nieskończoności (tych z form (+/- inf, skończona) i (skończona, + / -inf) są skupione razem w czterech głównych kierunkach, wszystkie inne kierunki są reprezentowane przez tylko cztery nieskończoności (+/- inf, + -inf). Nie powinno dziwić, że rozszerzenie złożonego mnożenia na tę geometrię jest koszmarem .
Załącznik G specyfikacji C99 dokłada wszelkich starań, aby działał, w tym naginając zasady dotyczące sposobu inf
i nan
interakcji (zasadniczo inf
atuty nan
). Problem OP jest omijany przez nie promowanie rzeczywistych i proponowany typ czysto urojony na złożony, ale fakt, że prawdziwe 1 zachowuje się inaczej niż złożone 1, nie wydaje mi się rozwiązaniem. Co znamienne, w załączniku G brakuje pełnego określenia, jaki powinien być iloczyn dwóch nieskończoności.
Kusi, aby spróbować rozwiązać te problemy, wybierając lepszą geometrię nieskończoności. Analogicznie do wydłużonej linii rzeczywistej moglibyśmy dodać jedną nieskończoność dla każdego kierunku. Ta konstrukcja jest podobna do płaszczyzny rzutowej, ale nie skupia się w przeciwnych kierunkach. Nieskończoności byłyby reprezentowane we współrzędnych biegunowych inf xe ^ {2 omega pi i}, definiowanie iloczynów byłoby proste. W szczególności problem OP zostałby rozwiązany w sposób dość naturalny.
Ale na tym kończy się dobra wiadomość. W pewnym sensie możemy cofnąć się do punktu wyjścia jeden przez - nie bez powodu - wymaganie, aby nasze nieskończoności nowego stylu obsługiwały funkcje, które wydobywają ich rzeczywiste lub urojone części. Dodawanie to kolejny problem; dodając dwie nieantypodalne nieskończoności, musielibyśmy ustawić kąt na niezdefiniowany, tj. nan
(można argumentować, że kąt musi leżeć między dwoma kątami wejściowymi, ale nie ma prostego sposobu na przedstawienie tej „częściowej nan-ności”)
W świetle tego wszystkiego być może stare dobre jednopunktowe zagęszczanie jest najbezpieczniejszą rzeczą do zrobienia. Być może autorzy Aneksu G czuli to samo, przypisując funkcję, cproj
która łączy w sobie wszystkie nieskończoności.
Oto pokrewne pytanie, na które odpowiedziały osoby bardziej kompetentne w tej dziedzinie niż ja.
nan != nan
. Rozumiem, że ta odpowiedź jest pół żartem, ale nie rozumiem, dlaczego miałaby być pomocna dla OP w formie, w jakiej jest napisana.
==
(i biorąc pod uwagę, że zaakceptowali inną odpowiedź), wydaje się, że był to tylko problem ze sposobem wyrażenia tytułu w PO. Przeformułowałem tytuł, aby naprawić tę niespójność. (Celowe unieważnienie pierwszej połowy tej odpowiedzi, ponieważ zgadzam się z @cmaster: nie o to chodzi w tym pytaniu).
To jest szczegół implementacji, w jaki sposób złożone mnożenie jest zaimplementowane w CPythonie. W przeciwieństwie do innych języków (np. C lub C ++), CPython przyjmuje nieco uproszczone podejście:
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;
}
Jeden problematyczny przypadek z powyższym kodem to:
(0.0+1.0*j)*(inf+inf*j) = (0.0*inf-1*inf)+(0.0*inf+1.0*inf)j
= nan + nan*j
Jednak chciałoby się mieć -inf + inf*j
taki wynik.
Pod tym względem inne języki nie są daleko do przodu: mnożenie liczb zespolonych przez długi czas nie było częścią standardu C, uwzględnione tylko w C99 jako dodatek G, który opisuje, jak należy wykonywać złożone mnożenie - i nie jest tak proste, jak powyższa formuła szkoły! Standard C ++ nie określa, jak złożone powinno działać mnożenie, dlatego większość implementacji kompilatorów powraca do implementacji C, która może być zgodna z C99 (gcc, clang) lub nie (MSVC).
W powyższym „problematycznym” przykładzie implementacje zgodne ze standardem C99 (które są bardziej skomplikowane niż formuła szkolna) dałyby ( zobacz na żywo ) oczekiwany wynik:
(0.0+1.0*j)*(inf+inf*j) = -inf + inf*j
Nawet ze standardem C99 jednoznaczny wynik nie jest zdefiniowany dla wszystkich wejść i może być inny nawet dla wersji zgodnych z C99.
Innym efektem ubocznym float
braku awansu complex
w C99 jest to, że pomnożenie inf+0.0j
z 1.0
lub 1.0+0.0j
może prowadzić do różnych wyników (patrz tutaj na żywo):
(inf+0.0j)*1.0 = inf+0.0j
(inf+0.0j)*(1.0+0.0j) = inf-nanj
, część urojona będąca -nan
a nie nan
(jak w przypadku CPythona) nie odgrywa tutaj roli, ponieważ wszystkie ciche nany są równoważne (zobacz to ), nawet niektóre z nich mają ustawiony bit znaku (i dlatego są drukowane jako "-", zobacz to ), a niektóre nie.Co jest przynajmniej sprzeczne z intuicją.
Moim kluczowym wnioskiem jest to, że nie ma nic prostego w „prostym” mnożeniu (lub dzieleniu) liczb zespolonych i podczas przełączania się między językami, a nawet kompilatorami, należy przygotować się na subtelne błędy / różnice.
printf
i podobne działa z double: patrzą na znak-bit, aby zdecydować, czy „-” ma zostać wydrukowane, czy nie (bez względu na to, czy jest to nan, czy nie). Więc masz rację, nie ma znaczącej różnicy między „nan” i „-nan”, co wkrótce naprawi tę część odpowiedzi.
Zabawna definicja z Pythona. Jeśli mamy do rozwiązywania tego piórem i papierem Powiedziałbym, że oczekiwany wynik byłby expected: (inf + 0j)
jak wskazał, ponieważ wiemy, że mamy na myśli normę 1
tak (float('inf')+0j)*1 =should= ('inf'+0j)
:
Ale tak nie jest, jak widać ... po uruchomieniu otrzymujemy:
>>> Complex( float('inf') , 0j ) * 1
result: (inf + nanj)
Python rozumie to *1
jako liczbę zespoloną, a nie normę, 1
więc interpretuje jako, *(1+0j)
a błąd pojawia się, gdy próbujemy zrobić inf * 0j = nanj
tak , jak inf*0
nie można go rozwiązać.
Co tak naprawdę chcesz zrobić (zakładając, że 1 to norma 1):
Przypomnijmy, że jeśli z = x + iy
jest liczbą zespoloną z częścią rzeczywistą x i częścią urojoną y, sprzężona liczba zespolona z
jest definiowana jako z* = x − iy
, a wartość bezwzględna, zwana również norm of z
jest definiowana jako:
Zakładając, że 1
jest to norma 1
, powinniśmy zrobić coś takiego:
>>> c_num = complex(float('inf'),0)
>>> value = 1
>>> realPart=(c_num.real)*value
>>> imagPart=(c_num.imag)*value
>>> complex(realPart,imagPart)
result: (inf+0j)
niezbyt intuicyjny, wiem ... ale czasami języki kodowania są definiowane w inny sposób niż to, czego używamy na co dzień.