>>> (float('inf')+0j)*1
(inf+nanj)
Зачем? Это вызвало неприятную ошибку в моем коде.
Почему 1
мультипликативная идентичность не дает (inf + 0j)
?
>>> (float('inf')+0j)*1
(inf+nanj)
Зачем? Это вызвало неприятную ошибку в моем коде.
Почему 1
мультипликативная идентичность не дает (inf + 0j)
?
Ответы:
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
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']
мы видим, что почти нет смешанных типов, в частности, ни один, который смешивает float "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
А как насчет более чем одного бесконечного расширения комплексной плоскости?
В компьютерах комплексные числа обычно реализуются путем склеивания двух вещественных чисел, одно для действительной, а другое для мнимой части. Это прекрасно, пока все конечно. Однако, как только бесконечности начинают рассматривать, все становится непросто.
Комплексная плоскость имеет естественную симметрию вращения, которая хорошо согласуется со сложной арифметикой, поскольку умножение всей плоскости на 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}, определение произведений будет простым. В частности, вполне естественно решилась бы проблема ОП.
Но на этом хорошие новости заканчиваются. В каком-то смысле нас может отбросить назад, если - небезосновательно - потребовать, чтобы бесконечности нашего нового стиля поддерживали функции, извлекающие их действительные или мнимые части. Дополнение - еще одна проблема; добавляя две неантиподальные бесконечности, мы должны были бы установить угол равным undefined, т.е. nan
(можно было бы утверждать, что угол должен лежать между двумя входными углами, но нет простого способа представить эту "частичную наночастицу")
В свете всего этого, возможно, самая безопасная вещь - старая добрая компактификация по одной точке. Возможно, авторы Приложения G чувствовали то же самое, когда предписывали функцию cproj
, объединяющую все бесконечности.
Вот связанный с этим вопрос, на который отвечают люди более компетентные в предмете, чем я.
nan != nan
. Я понимаю, что этот ответ полушутя, но я не понимаю, почему он должен быть полезен OP в том виде, в котором он написан.
==
(и учитывая, что они приняли другой ответ), похоже, это была просто проблема того, как 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
результат.
В этом отношении другие языки не далеко впереди: умножение комплексных чисел долгое время не было частью стандарта 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 эквивалентны (см. это ), даже некоторые из них имеют установленный знаковый бит (и поэтому печатаются как "-", см. это ) а некоторые нет.Что, по крайней мере, противоречит интуиции.
Мой ключевой вывод: нет ничего простого в "простом" умножении (или делении) комплексных чисел, и при переключении между языками или даже компиляторами нужно быть готовым к небольшим ошибкам / отличиям.
printf
и подобное работает с double: они смотрят на бит знака, чтобы решить, следует ли печатать «-» или нет (независимо от того, является ли это nan или нет). Итак, вы правы, значимой разницы между «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):
Напомним, что if z = x + iy
- комплексное число с действительной частью x и мнимой частью y, комплексное сопряжение числа z
определяется как 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)
не очень интуитивно понятный, я знаю ... но иногда языки кодирования определяются иначе, чем то, что мы используем в наши дни.