Почему (inf + 0j) * 1 оценивается как inf + nanj? - PullRequest
75 голосов
/ 20 сентября 2019
>>> (float('inf')+0j)*1
(inf+nanj)

Почему?Это вызвало неприятную ошибку в моем коде.

Почему 1 не является мультипликативной идентичностью, давая (inf + 0j)?

Ответы [ 3 ]

79 голосов
/ 20 сентября 2019

1 сначала преобразуется в комплексное число, 1 + 0j, что затем приводит к умножению inf * 0, в результате чего 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
27 голосов
/ 20 сентября 2019

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

Во-первых, полезно уточнить вопрос, как @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

... комплексной плоскости

А как насчет расширений комплексной плоскости больше, чем один-inf?

В компьютерах комплексное числоrs обычно реализуются путем склеивания двух действительных чисел вместе одного для реального и одного для мнимой части.Это прекрасно, пока все конечно.Однако, как только считаются бесконечности, вещи становятся хитрыми.

Комплексная плоскость обладает естественной вращательной симметрией, которая хорошо сочетается со сложной арифметикой, поскольку умножение всей плоскости на e ^ phij аналогично phiвращение радиана вокруг 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).Проблема ОП обходится тем, что не продвигает реальные и предложенный чисто мнимый тип на сложный, но наличие действительного 1 ведет себя иначе, чем комплексное 1, не кажется мне решением.Что характерно, в Приложении G. не дается полное определение того, каким должно быть произведение двух бесконечностей.

Можем ли мы добиться большего успеха?

Соблазнительно попытаться решить эти проблемы, выбрав лучшую геометриюбесконечностей.По аналогии с расширенной реальной линией мы можем добавить одну бесконечность для каждого направления.Эта конструкция похожа на проективную плоскость, но не объединяет противоположные направления.Бесконечности будут представлены в полярных координатах inf xe ^ {2 omega pi i}, определение продуктов будет простым.В частности, проблема ОП была бы решена вполне естественно.

Но на этом хорошие новости заканчиваются.В некотором смысле мы можем быть отброшены к исходной точке, не безосновательно, требуя, чтобы наши бесконечности в новом стиле поддерживали функции, извлекающие их действительные или мнимые части.Дополнение является еще одной проблемой;добавив две неантиподальные бесконечности, мы должны установить угол на неопределенное значение, то есть nan (можно утверждать, что угол должен лежать между двумя входными углами, но не существует простого способа представления этой "частичной нанности")

Риман на помощь

С учетом всего этого, возможно, самое старое доброе компактификация - это самое безопасное.Возможно, авторы Приложения G чувствовали то же самое, когда предписывали функцию cproj, которая объединяет все бесконечности.


Вот связанный вопрос , на который ответили люди, более компетентные впредмет, чем я.

4 голосов
/ 22 сентября 2019

Это деталь реализации того, как сложное умножение реализовано в CPython.В отличие от других языков (например, C или C ++), CPython использует несколько упрощенный подход:

  1. целые числа / числа с плавающей запятой переводятся в комплексные числа при умножении
  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 в качестве результата.

В этом отношении другие языки не далеко впереди: умножение комплексных чисел долгое время не было частью стандарта C, включало тольков 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 эквивалентны (см. this ), даже у некоторых из них установлен бит знака (и, таким образом, напечатан как"-", см. это), а некоторые нет.

Что, по крайней мере, нелогично.


Мой ключ к выводу: ничего простого в этом нет«Умножение (или деление) комплексных чисел, и при переключении между языками или даже компиляторами нужно готовиться к незначительным ошибкам / различиям.

...