Почему (inf + 0j) * 1 оценивается как inf + nanj?

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

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

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

Ответ 1

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

Ответ 2

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

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

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

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

Можем ли мы сделать лучше?

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

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

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

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


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

Ответ 3

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

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

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


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

Ответ 4

Смешное определение из 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 определяется как z* = x − iy, а абсолютное значение, также называемое norm of z, определяется как:

enter image description here

Предполагая, что 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)

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