>>> (float('inf')+0j)*1
(inf+nanj)
Чому? Це спричинило неприємну помилку в моєму коді.
Чому 1
мультиплікативна ідентичність не дає (inf + 0j)
?
>>> (float('inf')+0j)*1
(inf+nanj)
Чому? Це спричинило неприємну помилку в моєму коді.
Чому 1
мультиплікативна ідентичність не дає (inf + 0j)
?
Відповіді:
Спочатку 1
перетворюється в комплексне число 1 + 0j
, яке потім призводить до inf * 0
множення, в результаті чого отримується anan
.
(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
спрямовано 1 + 0j
.
array([inf+0j])*1
також оцінює до array([inf+nanj])
. Якщо припустити, що фактичне множення відбувається десь у коді C / C ++, чи означає це, що вони написали власний код для емуляції поведінки CPython, а не за допомогою _Complex або std :: complex?
numpy
має один центральний клас, ufunc
з якого походить майже кожен оператор і функція. ufunc
дбає про мовлення, керуючи кроками всього того хитрого адміністратора, що робить роботу з масивами настільки зручною. Точніше, розподіл праці між конкретним оператором та загальним механізмом полягає в тому, що конкретний оператор реалізує набір "найпотаємніших циклів" для кожної комбінації типів вхідних та вихідних елементів, які він хоче обробити. Загальна техніка опікується будь-якими зовнішніми петлями і вибирає найкращу відповідність внутрішній петлі ...
types
атрибут для np.multiply
цього, ['??->?', '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']
ми бачимо, що майже немає змішаних типів, зокрема, жодного, що змішує плаваючі "efdg"
та складні "FDG"
.
Механічно прийнята відповідь, звичайно, правильна, але я б стверджував, що можна дати глибшу відповідь.
По-перше, корисно пояснити питання, як це робить @PeterCordes у коментарі: "Чи існує мультиплікативна ідентичність для комплексних чисел, яка працює на inf + 0j?" або іншими словами, це те, що OP бачить слабку сторону в комп’ютерній реалізації складного множення або щось концептуально незрозумілеinf+0j
Використовуючи полярні координати, ми можемо розглядати складне множення як масштабування та обертання. Обертаючи нескінченну "руку" навіть на 0 градусів, як у випадку множення на одиницю, ми не можемо очікувати розміщення її кінчика з кінцевою точністю. Тож справді є щось принципово неправильне inf+0j
, а саме те, що як тільки ми перебуваємо на нескінченності, кінцеве зміщення стає безглуздим.
Передумови: «Велика річ», навколо якої обертається це питання, полягає в розширенні системи чисел (думаю, реальні чи комплексні числа). Однією з причин, яку можна зробити, це додати якесь поняття нескінченності або "компактифікувати", якщо хтось випадково є математиком. Є й інші причини ( https://en.wikipedia.org/wiki/Galois_theory , https://en.wikipedia.org/wiki/Non-standard_analysis ), але нас тут не цікавлять.
Хитрий момент такого розширення, звичайно, полягає в тому, що ми хочемо, щоб ці нові числа відповідали існуючій арифметиці. Найпростіший спосіб - це додати один елемент на нескінченності ( https://en.wikipedia.org/wiki/Alexandroff_extension ) і зробити так, щоб він дорівнював будь-чому, крім нуля, поділеного на нуль. Це працює для реальних даних ( https://en.wikipedia.org/wiki/Projectively_extended_real_line ) та комплексних чисел ( https://en.wikipedia.org/wiki/Riemann_sphere ).
Хоча компактифікація в одну точку проста і математично обгрунтована, шукаються "багатші" розширення, що включають кілька нескінченностей. Стандарт IEEE 754 для реальних чисел з плаваючою комою має + inf та -inf ( https://en.wikipedia.org/wiki/Extended_real_number_line ). Виглядає природно і прямолінійно, але вже змушує нас перескакувати обручі і вигадувати речі, такі як -0
https://en.wikipedia.org/wiki/Signed_zero
Що можна сказати про розширення комплексної площини, що відрізняються від одного?
В комп’ютерах складні числа, як правило, реалізуються шляхом з’єднання двох fp-реалів, одного для реальної та одного для уявної частини. Це цілком добре, доки все обмежено. Однак, як тільки нескінченності вважаються, речі стають складними.
Складна площина має природну симетрію обертання, яка прекрасно поєднується зі складною арифметикою, оскільки множення всієї площини на e ^ phij те саме, що обертання фі радіана навколо 0
.
Тепер, щоб зробити речі простими, складний fp просто використовує розширення (+/- inf, nan тощо) базової реалізації реального числа. Цей вибір може здатися настільки природним, що навіть не сприймається як вибір, але давайте детальніше розглянемо, що він передбачає. Виглядає проста візуалізація цього продовження комплексної площини (I = нескінченна, f = кінцева, 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
Але оскільки справжньою складною площиною є та, яка поважає складне множення, більш інформативною буде проекція
III
I I
fffff
fffffff
fffffffff
I fffffffff I
I ffff0ffff I
I fffffffff I
fffffffff
fffffff
fffff
I I
III
У цій проекції ми бачимо "нерівномірний розподіл" нескінченностей, який є не тільки потворним, але й корінням проблем типу OP: більшість нескінченностей (ті з форм (+/- inf, скінченні) та (скінченні, + / -inf) об'єднані в чотирьох основних напрямках, а всі інші напрямки представлені лише чотирма нескінченностями (+/- inf, + -inf). Не слід дивуватися тому, що поширення складного множення на цю геометрію є кошмаром .
Додаток G до специфікації C99 намагається зробити все можливе, щоб він працював, включаючи виправлення правил щодо взаємодії inf
та nan
взаємодії (по суті, inf
козирів nan
). Проблема OP уникнута тим, що не просуває реальність та запропонований суто уявний тип до комплексу, але наявність реального 1 поводиться інакше, ніж комплекс 1, не здається мені рішенням. Характерно, що Додаток G повністю не визначає, яким повинен бути добуток двох нескінченностей.
Спокусливо спробувати вирішити ці проблеми, вибравши кращу геометрію нескінченностей. Аналогічно розширеній дійсній лінії ми могли б додати одну нескінченність для кожного напрямку. Ця конструкція схожа на проективну площину, але не поєднує протилежних напрямків. Нескінченності були б представлені в полярних координатах inf xe ^ {2 omega pi i}, визначення продуктів було б простим. Зокрема, проблема OP була б вирішена цілком природно.
Але на цьому хороша новина закінчується. Певним чином, ми можемо бути повернуті на перший план, --- не безпідставно --- вимагаючи, щоб наші нескінченності newstyle підтримували функції, що виділяють їх реальні або уявні частини. Додавання - ще одна проблема; додавши дві неантиподні нескінченності, нам довелося б встановити кут невизначеним, тобто nan
(можна сказати, кут повинен лежати між двома вхідними кутами, але немає простого способу представити цю "часткову нанність")
З огляду на все це, можливо, найстаріше зробити стару добру компактність в одну точку. Можливо, автори Додатку G відчували те саме, коли встановлювали функцію, cproj
яка об'єднує всі нескінченності.
Ось відповідне запитання, на яке відповідають люди, більш компетентні в цій темі, ніж я.
nan != nan
. Я розумію, що ця відповідь напівжартома, але я не розумію, чому вона повинна бути корисною для ОП так, як вона написана.
==
(і з огляду на те, що вони прийняли іншу відповідь), здається, це була просто проблема в тому, як OP висловив заголовок. Я переформулював заголовок, щоб виправити цю невідповідність. (Навмисне недійсне першої половини цієї відповіді, тому що я погоджуюсь з @cmaster: це не те, про що запитувало це питання).
Це деталь реалізації того, як складне множення реалізоване в CPython. На відміну від інших мов (наприклад, C або C ++), CPython застосовує дещо спрощений підхід:
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;
}
Одним із проблемних випадків із наведеним вище кодом буде:
(0.0+1.0*j)*(inf+inf*j) = (0.0*inf-1*inf)+(0.0*inf+1.0*inf)j
= nan + nan*j
Однак хотілося б мати -inf + inf*j
як результат.
У цьому відношенні інші мови не далеко вперед: множення складних чисел тривалий час не було частиною стандарту С, включеного лише до C99 як додаток G, який описує, як слід виконувати складне множення - і це не так просто, як шкільна формула вище! Стандарт C ++ не визначає, як має працювати складне множення, тому більшість реалізацій компілятора повертаються до реалізації C, яка може відповідати C99 (gcc, clang) чи ні (MSVC).
У наведеному вище "проблемному" прикладі реалізації, сумісні з C99 (які є більш складними, ніж шкільна формула), дадуть ( див. У прямому ефірі ) очікуваний результат:
(0.0+1.0*j)*(inf+inf*j) = -inf + inf*j
Навіть зі стандартом C99 однозначний результат визначений не для всіх входів, і він може відрізнятися навіть для версій, сумісних із C99.
Ще один побічний ефект float
не призначено complex
в C99 є те , що множення inf+0.0j
з 1.0
або 1.0+0.0j
може привести до різних результатів (див тут живуть):
(inf+0.0j)*1.0 = inf+0.0j
(inf+0.0j)*(1.0+0.0j) = inf-nanj
, уявна частина, -nan
а не nan
(як для CPython), тут не грає ролі, оскільки всі тихі nans еквівалентні (див. це ), навіть деякі з них мають бітовий знак (і, отже, надруковані як "-", див. це ), а деякі ні.Що, принаймні, протиінтуїтивно.
Мій ключовий висновок від цього: немає нічого простого в "простому" множенні (або діленні) складних чисел, і при перемиканні між мовами чи навіть компіляторами потрібно готуватися до тонких помилок / відмінностей.
printf
і подібні роботи з double: вони дивляться на біт знаку, щоб вирішити, чи слід друкувати "-" (незалежно від того, чи є він nn чи ні). Отже, ви маєте рацію, немає суттєвої різниці між "nan" та "-nan", незабаром виправляючи цю частину відповіді.
Смішне визначення з Python. Якщо ми вирішуємо це ручкою та папером, я б сказав, що очікуваний результат буде таким, expected: (inf + 0j)
як ви вказали, оскільки ми знаємо, що маємо на увазі норму 1
так (float('inf')+0j)*1 =should= ('inf'+0j)
:
Але це не так, як ви можете бачити ... коли ми запускаємо його, ми отримуємо:
>>> Complex( float('inf') , 0j ) * 1
result: (inf + nanj)
Python розуміє це *1
як комплексне число, а не норму, 1
тому інтерпретує як, *(1+0j)
і помилка з'являється, коли ми намагаємось зробити так, inf * 0j = nanj
як inf*0
неможливо вирішити.
Що ви насправді хочете зробити (припускаючи, що 1 є нормою 1):
Нагадаємо, що якщо z = x + iy
є комплексним числом із дійсною частиною x та уявною частиною y, то комплексна спряженість z
is визначається як z* = x − iy
, а абсолютна величина, яку також називають norm of z
, визначається як:
Припускаючи, що 1
це норма, 1
ми повинні робити щось на зразок:
>>> c_num = complex(float('inf'),0)
>>> value = 1
>>> realPart=(c_num.real)*value
>>> imagPart=(c_num.imag)*value
>>> complex(realPart,imagPart)
result: (inf+0j)
я не дуже інтуїтивно зрозумілий ... але іноді мови кодування визначаються по-іншому, ніж те, чим ми користуємося в наші дні.