Почему (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) не являются полем. Большинство идентификаторов, используемых для полей, не применимы для чисел с плавающей запятой.
plugwash

Ответы:


95

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

8
Для ответа на вопрос «почему ...?», Наверное, самый важный шаг - это первый шаг, куда 1передается 1 + 0j.
Уоррен Векессер

5
Обратите внимание, что C99 указывает, что реальные типы с плавающей запятой не превращаются в сложные при умножении на сложный тип (раздел 6.3.1.8 проекта стандарта), и, насколько мне известно, то же самое верно и для std :: complex в C ++. Частично это может быть связано с производительностью, но также позволяет избежать ненужных NaN.
Benrg

@benrg В NumPy array([inf+0j])*1тоже оценивается array([inf+nanj]). Предполагая, что фактическое умножение происходит где-то в коде C / C ++, будет ли это означать, что они написали собственный код для имитации поведения CPython вместо использования _Complex или std :: complex?
Марникс

1
@marnix это более сложный вопрос. numpyимеет один центральный класс, ufuncот которого происходят почти все операторы и функции. ufuncзаботится о трансляции, управляет успехами, весь этот хитрый админ, который делает работу с массивами такой удобной. Точнее, разделение труда между конкретным оператором и общим механизмом состоит в том, что конкретный оператор реализует набор «самых внутренних циклов» для каждой комбинации типов элементов ввода и вывода, которые он хочет обрабатывать. Общий механизм заботится о любых внешних контурах и выбирает наиболее подходящий внутренний контур ...
Пол Панцер

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']мы видим, что почти нет смешанных типов, в частности, ни один, который смешивает float "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

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

А как насчет более чем одного бесконечного расширения комплексной плоскости?

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

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

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

Риман спешит на помощь

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


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


5
Да потому что nan != nan. Я понимаю, что этот ответ полушутя, но я не понимаю, почему он должен быть полезен OP в том виде, в котором он написан.
cmaster - восстановить Монику

Учитывая, что код в теле вопроса на самом деле не использовался ==(и учитывая, что они приняли другой ответ), похоже, это была просто проблема того, как OP выражал заголовок. Я изменил название, чтобы исправить это несоответствие. (Умышленно аннулирую первую половину этого ответа, потому что я согласен с @cmaster: это не то, о чем спрашивал этот вопрос).
Питер Кордес

3
@PeterCordes, это было бы проблемой, потому что, используя полярные координаты, мы можем рассматривать сложное умножение как масштабирование и вращение. Вращая бесконечную «руку» даже на 0 градусов, как в случае умножения на единицу, мы не можем ожидать, что ее кончик будет размещен с конечной точностью. На мой взгляд, это более глубокое объяснение, чем принятое, а также одно с эхом в правиле nan! = Nan.
Paul Panzer

3
C99 указывает, что реальные типы с плавающей запятой не превращаются в сложные при умножении на сложный тип (раздел 6.3.1.8 проекта стандарта), и, насколько мне известно, то же самое верно и для std :: complex в C ++. Это означает, что 1 - мультипликативное тождество для этих типов в этих языках. Python должен делать то же самое. Я бы назвал его текущее поведение просто ошибкой.
Benrg

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

6

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

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

Что, по крайней мере, противоречит интуиции.


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


Я знаю, что существует множество наноразмерных шаблонов. Однако не знал, что такое битовый знак. Но я имел в виду семантически Чем -nan отличается от nan? Или я должен сказать, что нан больше отличается от нан?
Paul Panzer

@PaulPanzer Это просто деталь реализации того, как printfи подобное работает с double: они смотрят на бит знака, чтобы решить, следует ли печатать «-» или нет (независимо от того, является ли это nan или нет). Итак, вы правы, значимой разницы между «nan» и «-nan» нет, скоро исправлю эту часть ответа.
Свинец

А, хорошо. Одно время беспокоился, что все, что я знал о fp, на самом деле было
Пол Панцер

Извините за раздражение, но уверены ли вы, что «не существует воображаемого 1.0, то есть 1.0j, который не совпадает с 0.0 + 1.0j в отношении умножения». правильно? Это приложение G, кажется, определяет чисто воображаемый тип (G.2), а также предписывает, как его следует умножать и т. Д. (G.5.1)
Пол Панцер

@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):

Напомним, что 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)

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.