Por que (inf + 0j) * 1 é avaliado como inf + nanj?


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

Por quê? Isso causou um bug desagradável no meu código.

Por que 1a identidade multiplicativa não está dando (inf + 0j)?


1
Acho que a palavra-chave que você está procurando é " campo ". A adição e a multiplicação são definidas por padrão em um único campo e, neste caso, o único campo padrão que pode acomodar seu código é o campo de números complexos, portanto, ambos os números precisam ser tratados como números complexos por padrão antes que a operação seja bem- definiram. O que não quer dizer que eles não pudessem estender essas definições, mas, aparentemente, eles apenas seguiram o padrão e não sentiram o desejo de sair de seu caminho para estender as definições.
user541686

1
Ah, e se você acha essas idiossincrasias frustrantes e quer socar seu computador, você tem minha simpatia .
user541686

2
@Mehrdad uma vez que você adiciona aqueles elementos não finitos, ele deixa de ser um campo. Na verdade, como não existe mais um neutro multiplicativo, ele não pode, por definição, ser um campo.
Paul Panzer

@PaulPanzer: Sim, acho que eles empurraram esses elementos depois.
user541686

1
números de ponto flutuante (mesmo se você excluir infinito e NaN) não são um campo. A maioria das identidades válidas para campos não são válidas para números de ponto flutuante.
plugwash de

Respostas:


95

O 1é convertido em um número complexo primeiro 1 + 0j, o que leva a uma inf * 0multiplicação, resultando em a 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
Para responder à pergunta "por que ...?", Provavelmente o passo mais importante é o primeiro, para onde 1é lançado 1 + 0j.
Warren Weckesser de

5
Observe que o C99 especifica que os tipos de ponto flutuante reais não são promovidos a complexos ao se multiplicar por um tipo complexo (seção 6.3.1.8 do padrão de rascunho) e, pelo que sei, o mesmo é verdadeiro para std :: complex do C ++. Isso pode ser parcialmente por motivos de desempenho, mas também evita NaNs desnecessários.
benrg

@benrg Em NumPy, array([inf+0j])*1também avalia como array([inf+nanj]). Assumindo que a multiplicação real ocorre em algum lugar no código C / C ++, isso significa que eles escreveram um código personalizado para emular o comportamento do CPython, em vez de usar _Complex ou std :: complex?
marnix

1
@marnix é mais envolvente do que isso. numpytem uma classe central ufuncda qual quase todos os operadores e funções derivam. ufunccuida da transmissão, gerenciando todos aqueles administradores complicados que tornam o trabalho com matrizes tão conveniente. Mais precisamente, a divisão do trabalho entre um operador específico e a máquina geral é que o operador específico implementa um conjunto de "loops mais internos" para cada combinação de tipos de elementos de entrada e saída que deseja manipular. A maquinaria geral cuida de quaisquer loops externos e seleciona o melhor loop interno mais adequado ...
Paul Panzer

1
... promovendo qualquer tipo que não corresponda exatamente, conforme necessário. Podemos acessar a lista de loops internos fornecidos por meio do typesatributo para que np.multiplyisso produza ['??->?', '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'], podemos ver que quase não há tipos mistos, em particular, nenhum que mistura float "efdg"com complexo "FDG".
Paul Panzer

32

Mecanicamente, a resposta aceita é, obviamente, correta, mas eu argumentaria que uma resposta mais profunda pode ser dada.

Primeiro, é útil esclarecer a questão como @PeterCordes faz em um comentário: "Existe uma identidade multiplicativa para números complexos que funcione em inf + 0j?" ou em outras palavras é o que OP vê uma fraqueza na implementação de computador de multiplicação complexa ou há algo conceitualmente incorreto cominf+0j

Resposta curta:

Usando coordenadas polares, podemos ver a multiplicação complexa como uma escala e uma rotação. Girando um "braço" infinito mesmo em 0 graus, como no caso da multiplicação por um, não podemos esperar colocar sua ponta com precisão finita. Então, de fato, há algo fundamentalmente errado inf+0j, a saber, que assim que estamos no infinito, um deslocamento finito torna-se sem sentido.

Resposta longa:

Contexto: A "grande coisa" em torno da qual esta questão gira é a questão de estender um sistema de números (pense em reais ou números complexos). Uma razão pela qual alguém pode querer fazer isso é adicionar algum conceito de infinito, ou "compactar" se alguém for um matemático. Existem outros motivos também ( https://en.wikipedia.org/wiki/Galois_theory , https://en.wikipedia.org/wiki/Non-standard_analysis ), mas não estamos interessados ​​neles aqui.

Compactação de um ponto

A parte complicada dessa extensão é, obviamente, que queremos que esses novos números se ajustem à aritmética existente. A maneira mais simples é adicionar um único elemento ao infinito ( https://en.wikipedia.org/wiki/Alexandroff_extension ) e torná-lo igual a qualquer coisa, menos zero dividido por zero. Isso funciona para os reais ( https://en.wikipedia.org/wiki/Projectively_extended_real_line ) e os números complexos ( https://en.wikipedia.org/wiki/Riemann_sphere ).

Outras extensões ...

Embora a compactação de um ponto seja simples e matematicamente correta, foram buscadas extensões "mais ricas" compreendendo vários infintos. O padrão IEEE 754 para números de ponto flutuante real tem + inf e -inf ( https://en.wikipedia.org/wiki/Extended_real_number_line ). Parece natural e direto, mas já nos obriga a saltar obstáculos e inventar coisas como -0 https://en.wikipedia.org/wiki/Signed_zero

... do plano complexo

E quanto às extensões mais de um inf do plano complexo?

Em computadores, os números complexos são geralmente implementados juntando dois reais fp, um para a parte real e outro para a parte imaginária. Isso está perfeitamente bem, desde que tudo seja finito. No entanto, assim que o infinito é considerado, as coisas ficam complicadas.

O plano complexo tem uma simetria rotacional natural, que se encaixa perfeitamente com a aritmética complexa, visto que multiplicar o plano inteiro por e ^ phij é o mesmo que uma rotação phi radiana 0.

Aquela coisa do anexo G

Agora, para manter as coisas simples, o fp complexo simplesmente usa as extensões (+/- inf, nan etc.) da implementação de número real subjacente. Essa escolha pode parecer tão natural que nem mesmo é percebida como uma escolha, mas vamos dar uma olhada mais de perto no que ela implica. Uma visualização simples desta extensão do plano complexo se parece com (I = infinito, f = finito, 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

Mas, uma vez que um verdadeiro plano complexo é aquele que respeita a multiplicação complexa, uma projeção mais informativa seria

     III    
 I         I  
    fffff    
   fffffff   
  fffffffff  
I fffffffff I
I ffff0ffff I
I fffffffff I
  fffffffff  
   fffffff   
    fffff    
 I         I 
     III    

Nesta projeção, vemos a "distribuição desigual" de infinitos que não é apenas feia, mas também a raiz de problemas do tipo que OP sofreu: a maioria dos infinitos (aqueles das formas (+/- inf, finito) e (finito, + / -inf) são agrupados nas quatro direções principais, todas as outras direções são representadas por apenas quatro infinitos (+/- inf, + -inf). Não deveria ser uma surpresa que estender a multiplicação complexa a esta geometria seja um pesadelo .

O Anexo G da especificação C99 tenta o seu melhor para fazê-lo funcionar, incluindo burlar as regras sobre como infe naninteragir (essencialmente inftrunfos nan). O problema de OP é contornado por não promover reais e um tipo puramente imaginário proposto para complexo, mas ter o real 1 se comportando de maneira diferente do complexo 1 não me parece uma solução. De forma reveladora, o Anexo G deixa de especificar totalmente qual deve ser o produto de dois infinitos.

Podemos fazer melhor?

É tentador tentar corrigir esses problemas escolhendo uma geometria de infinitos melhor. Em analogia com a linha real estendida, poderíamos adicionar um infinito para cada direção. Esta construção é semelhante ao plano projetivo, mas não agrupa direções opostas. Os infinitos seriam representados em coordenadas polares inf xe ^ {2 omega pi i}, a definição de produtos seria direta. Em particular, o problema de OP seria resolvido de forma bastante natural.

Mas é aqui que as boas novas terminam. De certa forma, podemos ser arremessados ​​de volta à estaca zero - não sem razão - exigindo que nossos infinitos de novo estilo suportem funções que extraiam suas partes reais ou imaginárias. A adição é outro problema; adicionando dois infinitos não antípodas, teríamos que definir o ângulo como indefinido, ou seja nan(pode-se argumentar que o ângulo deve estar entre os dois ângulos de entrada, mas não há uma maneira simples de representar essa "nulidade parcial")

Riemann para o resgate

Em vista de tudo isso, talvez a boa e velha compactação de um ponto seja a coisa mais segura a se fazer. Talvez os autores do Anexo G tenham sentido o mesmo ao ordenar uma função cprojque agrupa todos os infinitos.


Aqui está uma pergunta relacionada respondida por pessoas mais competentes no assunto do que eu.


5
Sim, porque nan != nan. Eu entendo que esta resposta é meio de brincadeira, mas não consigo ver por que deveria ser útil para o OP da maneira como está escrita.
cmaster - restabelecer monica em

Dado que o código no corpo da pergunta não estava realmente usando ==(e considerando que eles aceitaram a outra resposta), parece que era apenas um problema de como o OP expressava o título. Eu reformulei o título para corrigir essa inconsistência. (Invalida intencionalmente a primeira metade desta resposta porque concordo com @cmaster: não é sobre isso que esta pergunta está perguntando).
Peter Cordes

3
@PeterCordes isso seria problemático porque usando coordenadas polares podemos ver a multiplicação complexa como uma escala e uma rotação. Girando um "braço" infinito mesmo em 0 graus, como no caso da multiplicação por um, não podemos esperar colocar sua ponta com precisão finita. Esta é, em minha opinião, uma explicação mais profunda do que a aceita, e também com ecos na regra nan! = Nan.
Paul Panzer

3
C99 especifica que os tipos de ponto flutuante reais não são promovidos a complexos ao multiplicar por um tipo complexo (seção 6.3.1.8 do padrão de rascunho) e, até onde eu sei, o mesmo é verdadeiro para std :: complex de C ++. Isso significa que 1 é uma identidade multiplicativa para esses tipos nessas línguas. Python deve fazer o mesmo. Eu chamaria seu comportamento atual simplesmente de um bug.
benrg de

2
@PaulPanzer: Não, mas o conceito básico seria que um zero (que chamarei de Z) sempre sustentaria x + Z = x e x * Z = Z, e 1 / Z = NaN, um (positivo infinitesimal) sustentaria 1 / P = + INF, um (infinitesimal negativo) sustentaria 1 / N = -INF e (infinitesimal sem sinal) resultaria em 1 / U = NaN. Em geral, xx seria U a menos que x fosse um inteiro verdadeiro, caso em que resultaria em Z.
supercat

6

Este é um detalhe de implementação de como a multiplicação complexa é implementada no CPython. Ao contrário de outras linguagens (por exemplo, C ou C ++), CPython tem uma abordagem um tanto simplista:

  1. ints / floats são promovidos a números complexos na multiplicação
  2. é usada a fórmula escolar simples , que não fornece os resultados desejados / esperados assim que números infinitos estão envolvidos:
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;
}

Um caso problemático com o código acima seria:

(0.0+1.0*j)*(inf+inf*j) = (0.0*inf-1*inf)+(0.0*inf+1.0*inf)j
                        =  nan + nan*j

No entanto, gostaríamos de ter -inf + inf*jcomo resultado.

Nesse aspecto, outras linguagens não estão muito à frente: a multiplicação de números complexos por muito tempo não fez parte do padrão C, incluído apenas em C99 como apêndice G, que descreve como uma multiplicação complexa deve ser realizada - e não é tão simples quanto a fórmula escolar acima! O padrão C ++ não especifica como a multiplicação complexa deve funcionar, portanto, a maioria das implementações de compiladores está caindo na implementação C, que pode estar em conformidade com C99 (gcc, clang) ou não (MSVC).

Para o exemplo "problemático" acima, as implementações em conformidade com C99 (que são mais complicadas do que a fórmula escolar) dariam ( veja ao vivo ) o resultado esperado:

(0.0+1.0*j)*(inf+inf*j) = -inf + inf*j 

Mesmo com o padrão C99, um resultado inequívoco não é definido para todas as entradas e pode ser diferente mesmo para versões compatíveis com C99.

Outro efeito colateral de floatnão ser promovido complexem C99 é que a multiplicação inf+0.0jcom 1.0ou 1.0+0.0jpode levar a resultados diferentes (veja aqui ao vivo):

  • (inf+0.0j)*1.0 = inf+0.0j
  • (inf+0.0j)*(1.0+0.0j) = inf-nanj, parte imaginária sendo -nane não nan(como para CPython) não desempenha um papel aqui, porque todos os nans silenciosos são equivalentes (veja isto ), mesmo alguns deles têm um bit de sinal definido (e, portanto, impresso como "-", veja isto ) e outros não.

O que é pelo menos contra-intuitivo.


Minha principal conclusão é: não há nada simples sobre a multiplicação (ou divisão) de números complexos "simples" e ao alternar entre linguagens ou mesmo compiladores, deve-se preparar-se para erros / diferenças sutis.


Eu sei que existem muitos padrões de nan bits. Não sabia a coisa do sinal, no entanto. Mas eu quis dizer semanticamente Como -nan é diferente de nan? Ou devo dizer mais diferente do que nan é de nan?
Paul Panzer

@PaulPanzer Este é apenas um detalhe de implementação de como printfe similares funcionam com double: eles olham para o bit de sinal para decidir se "-" deve ser impresso ou não (não importa se é nan ou não). Então você está certo, não há diferença significativa entre "nan" e "-nan", consertando essa parte da resposta em breve.
ead

Ah bom. Fiquei preocupado por um momento que tudo que eu pensava que sabia sobre fp não estava correto ...
Paul Panzer

Desculpe por ser chato, mas você tem certeza de que "não há 1.0 imaginário, ou seja, 1.0j que não é o mesmo que 0.0 + 1.0j em relação à multiplicação." está correto? Esse anexo G parece especificar um tipo puramente imaginário (G.2) e também prescrever como ele deve ser multiplicado etc. (G.5.1)
Paul Panzer

@PaulPanzer Não, obrigado por apontar os problemas! Como c ++ - codificador, eu vejo principalmente C99-standard até C ++ - glases - me esqueci, que C é um passo à frente aqui - você está certo, obviamente, mais uma vez.
ead

3

Definição engraçada de Python. Se estamos resolvendo isso com uma caneta e papel, eu diria que resultado esperado seria expected: (inf + 0j)como você apontou, porque sabemos que queremos dizer a norma do 1modo (float('inf')+0j)*1 =should= ('inf'+0j):

Mas esse não é o caso, como você pode ver ... quando o executamos, obtemos:

>>> Complex( float('inf') , 0j ) * 1
result: (inf + nanj)

Python entende isso *1como um número complexo e não a norma, 1então ele interpreta como *(1+0j)e o erro aparece quando tentamos fazer inf * 0j = nanjcomo inf*0não pode ser resolvido.

O que você realmente deseja fazer (assumindo que 1 é a norma de 1):

Lembre-se de que se z = x + iyfor um número complexo com parte real xe parte imaginária y, o conjugado complexo de zé definido como z* = x − iy, e o valor absoluto, também chamado de norm of zé definido como:

insira a descrição da imagem aqui

Supondo que 1seja a norma 1, devemos fazer algo como:

>>> c_num = complex(float('inf'),0)
>>> value = 1
>>> realPart=(c_num.real)*value
>>> imagPart=(c_num.imag)*value
>>> complex(realPart,imagPart)
result: (inf+0j)

não muito intuitivo eu sei ... mas às vezes as linguagens de codificação são definidas de uma maneira diferente da que usamos no nosso dia a dia.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.