Чому (inf + 0j) * 1 має значення inf + nanj?


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

Чому? Це спричинило неприємну помилку в моєму коді.

Чому 1мультиплікативна ідентичність не дає (inf + 0j)?


1
Я думаю, що ключове слово, яке ви шукаєте, - " поле ". Додавання та множення за замовчуванням визначаються в межах одного поля, і в цьому випадку єдиним стандартним полем, яке вміщує ваш код, є поле комплексних чисел, тому обидва числа за замовчуванням повинні розглядатися як комплексні числа, перш ніж операція буде добре- визначений. Що не означає, що вони не могли розширити ці визначення, але, очевидно, вони просто пішли зі стандартною річчю і не відчували бажання піти назустріч, щоб розширити визначення.
user541686

1
О, і якщо ви вважаєте ці ідіосинкразії розчаруванням і хочете вдарити ваш комп’ютер, ви маєте моє співчуття .
user541686

2
@Mehrdad як тільки ви додасте ці нескінченні елементи, він перестає бути полем. Справді, оскільки мультипликативного нейтралу вже не існує, він не може за визначенням бути полем.
Paul Panzer,

@PaulPanzer: Так, я думаю, вони згодом просто засунули ці елементи.
user541686

1
числа з плаваючою точкою (навіть якщо виключити нескінченність і NaN) не є полем. Більшість тотожностей, що відповідають полям, не відповідають числам із плаваючою комою.
промивка штекера

Відповіді:


95

Спочатку 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

8
Для відповіді на запитання "чому ...?", Мабуть, найважливішим кроком є ​​перший крок, куди 1спрямовано 1 + 0j.
Уоррен Векессер,

5
Зверніть увагу, що C99 вказує, що реальні типи з плаваючою комою не перетворюються на складні при множенні на складний тип (розділ 6.3.1.8 проекту стандарту), і, наскільки мені відомо, те саме стосується і std :: complex С ++. Частково це може бути з міркувань продуктивності, але також дозволяє уникнути непотрібних NaN.
benrg

@benrg У NumPy, array([inf+0j])*1також оцінює до array([inf+nanj]). Якщо припустити, що фактичне множення відбувається десь у коді C / C ++, чи означає це, що вони написали власний код для емуляції поведінки CPython, а не за допомогою _Complex або std :: complex?
marnix

1
@marnix це більше задіяно. numpyмає один центральний клас, ufuncз якого походить майже кожен оператор і функція. ufuncдбає про мовлення, керуючи кроками всього того хитрого адміністратора, що робить роботу з масивами настільки зручною. Точніше, розподіл праці між конкретним оператором та загальним механізмом полягає в тому, що конкретний оператор реалізує набір "найпотаємніших циклів" для кожної комбінації типів вхідних та вихідних елементів, які він хоче обробити. Загальна техніка опікується будь-якими зовнішніми петлями і вибирає найкращу відповідність внутрішній петлі ...
Paul Panzer

1
... просування будь-яких не зовсім відповідних типів, як потрібно. Ми можемо отримати доступ до списку передбачених внутрішніх циклів через 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".
Paul Panzer,

32

Механічно прийнята відповідь, звичайно, правильна, але я б стверджував, що можна дати глибшу відповідь.

По-перше, корисно пояснити питання, як це робить @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.

Що додаток G річ

Тепер, щоб зробити речі простими, складний 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яка об'єднує всі нескінченності.


Ось відповідне запитання, на яке відповідають люди, більш компетентні в цій темі, ніж я.


5
Так, тому що nan != nan. Я розумію, що ця відповідь напівжартома, але я не розумію, чому вона повинна бути корисною для ОП так, як вона написана.
cmaster - відновити

Враховуючи те, що код у тілі запитань насправді не використовувався ==(і з огляду на те, що вони прийняли іншу відповідь), здається, це була просто проблема в тому, як OP висловив заголовок. Я переформулював заголовок, щоб виправити цю невідповідність. (Навмисне недійсне першої половини цієї відповіді, тому що я погоджуюсь з @cmaster: це не те, про що запитувало це питання).
Пітер Кордес,

3
@PeterCordes, що може викликати занепокоєння, оскільки за допомогою полярних координат ми можемо розглядати складне множення як масштабування та обертання. Обертаючи нескінченну "руку" навіть на 0 градусів, як у випадку множення на одиницю, ми не можемо очікувати розміщення її кінчика з кінцевою точністю. На мою думку, це глибше пояснення, ніж прийняте, а також таке, що має відлуння у правилі nan! = Nan.
Paul Panzer,

3
C99 вказує, що реальні типи з плаваючою комою не підвищуються до складних при множенні на складний тип (розділ 6.3.1.8 проекту стандарту), і, наскільки мені відомо, те саме стосується і std :: complex С ++. Це означає, що 1 є мультиплікативною ідентичністю для цих типів у цих мовах. Python повинен робити те саме. Я б назвав його поточну поведінку просто помилкою.
benrg

2
@PaulPanzer: Ні, але основною концепцією було б те, що один нуль (який я називатиму Z) завжди підтримуватиме x + Z = x і x * Z = Z, а 1 / Z = NaN, один (позитивний нескінченно малий) підтримував би 1 / P = + INF, один (негативний нескінченно малий) підтримував би 1 / N = -INF, а (без підпису нескінченно малий) давав 1 / U = NaN. Загалом, хй буде U , якщо й не є істинним цілим числом, і в цьому випадку вона буде поступатися Z.
SUPERCAT

6

Це деталь реалізації того, як складне множення реалізоване в CPython. На відміну від інших мов (наприклад, C або C ++), CPython застосовує дещо спрощений підхід:

  1. ints / floats збільшуються до комплексних чисел шляхом множення
  2. використовується проста шкільна формула , яка не дає бажаних / очікуваних результатів, як тільки задіяно нескінченне число:
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 еквівалентні (див. це ), навіть деякі з них мають бітовий знак (і, отже, надруковані як "-", див. це ), а деякі ні.

Що, принаймні, протиінтуїтивно.


Мій ключовий висновок від цього: немає нічого простого в "простому" множенні (або діленні) складних чисел, і при перемиканні між мовами чи навіть компіляторами потрібно готуватися до тонких помилок / відмінностей.


Я знаю, що існує багато нано-бітових шаблонів. Хоча не знав, що таке біт. Але я мав на увазі семантично Чим -nan відрізняється від nan? Або я повинен сказати, що більше відрізняється, ніж nan від nan?
Paul Panzer,

@PaulPanzer Це лише подробиця реалізації того, як printfі подібні роботи з double: вони дивляться на біт знаку, щоб вирішити, чи слід друкувати "-" (незалежно від того, чи є він nn чи ні). Отже, ви маєте рацію, немає суттєвої різниці між "nan" та "-nan", незабаром виправляючи цю частину відповіді.
Свинець

Ах, добре. Мене хвилювало те, що все, що я думав, що я знаю про fp, насправді було неправильним ...
Paul Panzer

Вибачте, що дратуєте, але ви впевнені, що "немає уявного 1.0, тобто 1.0j, який не є таким самим, як 0.0 + 1.0j щодо множення". правильно? Цей додаток G, схоже, визначає суто уявний тип (G.2), а також передбачає, як його слід помножувати тощо (G.5.1)
Paul Panzer,

@PaulPanzer Ні, дякую, що вказали на проблеми! Як c ++ - кодер, я в основному бачу стандарт C99 через C ++ - glases - мені це сповзло до думки, що C тут на крок вперед - ви, очевидно, ще раз маєте рацію.
Свинець

3

Смішне визначення з 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, то комплексна спряженість zis визначається як 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)

я не дуже інтуїтивно зрозумілий ... але іноді мови кодування визначаються по-іншому, ніж те, чим ми користуємося в наші дні.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.