>>> (float('inf')+0j)*1
(inf+nanj)
Por quê? Isso causou um bug desagradável no meu código.
Por que 1
a identidade multiplicativa não está dando (inf + 0j)
?
>>> (float('inf')+0j)*1
(inf+nanj)
Por quê? Isso causou um bug desagradável no meu código.
Por que 1
a identidade multiplicativa não está dando (inf + 0j)
?
Respostas:
O 1
é convertido em um número complexo primeiro 1 + 0j
, o que leva a uma inf * 0
multiplicaçã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
1
é lançado 1 + 0j
.
array([inf+0j])*1
també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?
numpy
tem uma classe central ufunc
da qual quase todos os operadores e funções derivam. ufunc
cuida 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 ...
types
atributo para que np.multiply
isso 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"
.
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
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.
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.
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 ).
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
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
.
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 inf
e nan
interagir (essencialmente inf
trunfos 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.
É 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")
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 cproj
que agrupa todos os infinitos.
Aqui está uma pergunta relacionada respondida por pessoas mais competentes no assunto do que eu.
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.
==
(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).
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:
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*j
como 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 float
não ser promovido complex
em C99 é que a multiplicação inf+0.0j
com 1.0
ou 1.0+0.0j
pode 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 -nan
e 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.
printf
e 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.
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 1
modo (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 *1
como um número complexo e não a norma, 1
então ele interpreta como *(1+0j)
e o erro aparece quando tentamos fazer inf * 0j = nanj
como inf*0
não pode ser resolvido.
O que você realmente deseja fazer (assumindo que 1 é a norma de 1):
Lembre-se de que se z = x + iy
for 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:
Supondo que 1
seja 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.