>>> (float('inf')+0j)*1
(inf+nanj)
¿Por qué? Esto causó un error desagradable en mi código.
¿Por qué 1
la identidad multiplicativa no es dar (inf + 0j)
?
>>> (float('inf')+0j)*1
(inf+nanj)
¿Por qué? Esto causó un error desagradable en mi código.
¿Por qué 1
la identidad multiplicativa no es dar (inf + 0j)
?
Respuestas:
El 1
se convierte en un número complejo primero, 1 + 0j
, que entonces lleva a una inf * 0
multiplicación, lo que resulta en un 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
se lanza 1 + 0j
.
array([inf+0j])*1
también se evalúa como array([inf+nanj])
. Suponiendo que la multiplicación real ocurre en algún lugar del código C / C ++, ¿significaría esto que escribieron código personalizado para emular el comportamiento de CPython, en lugar de usar _Complex o std :: complex?
numpy
tiene una clase central ufunc
de la que derivan casi todos los operadores y funciones. ufunc
se encarga de la transmisión de la gestión de todos los pasos de ese administrador complicado que hace que trabajar con matrices sea tan conveniente. Más precisamente, la división del trabajo entre un operador específico y la maquinaria general es que el operador específico implementa un conjunto de "bucles más internos" para cada combinación de tipos de elementos de entrada y salida que desea manejar. La maquinaria general se encarga de cualquier bucle externo y selecciona el bucle más interno que mejor coincida ...
types
atributo para np.multiply
este rendimiento ['??->?', '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 casi no hay tipos mixtos, en particular, ninguno que mezcle float "efdg"
con complex "FDG"
.
Mecánicamente, la respuesta aceptada es, por supuesto, correcta, pero yo diría que se puede dar una respuesta más profunda.
Primero, es útil aclarar la pregunta como lo hace @PeterCordes en un comentario: "¿Existe una identidad multiplicativa para números complejos que funcione en inf + 0j?"o, en otras palabras, es lo que OP ve una debilidad en la implementación informática de la multiplicación compleja o hay algo conceptualmente incorrecto coninf+0j
Usando coordenadas polares podemos ver la multiplicación compleja como una escala y una rotación. Al girar un "brazo" infinito incluso en 0 grados, como en el caso de multiplicar por uno, no podemos esperar colocar su punta con precisión finita. Entonces, de hecho, hay algo fundamentalmente que no está bien con inf+0j
, a saber, que tan pronto como estamos en el infinito, un desplazamiento finito deja de tener sentido.
Antecedentes: El "gran elemento" en torno al cual gira esta pregunta es la cuestión de extender un sistema de números (piense en números reales o complejos). Una de las razones por las que uno podría querer hacer eso es agregar algún concepto de infinito, o "compactar" si uno es matemático. También hay otras razones ( https://en.wikipedia.org/wiki/Galois_theory , https://en.wikipedia.org/wiki/Non-standard_analysis ), pero aquí no nos interesan.
Lo complicado de esta extensión es, por supuesto, que queremos que estos nuevos números encajen en la aritmética existente. La forma más sencilla es agregar un solo elemento en el infinito ( https://en.wikipedia.org/wiki/Alexandroff_extension ) y hacer que sea igual a todo menos cero dividido por cero. Esto funciona para los reales ( https://en.wikipedia.org/wiki/Projectively_extended_real_line ) y los números complejos ( https://en.wikipedia.org/wiki/Riemann_sphere ).
Si bien la compactación de un punto es simple y matemáticamente sólida, se han buscado extensiones "más ricas" que comprendan múltiples infintos. El estándar IEEE 754 para números de coma flotante reales tiene + inf y -inf ( https://en.wikipedia.org/wiki/Extended_real_number_line ). Parece natural y sencillo, pero ya nos obliga a saltar e inventar cosas como -0
https://en.wikipedia.org/wiki/Signed_zero
¿Qué pasa con las extensiones de más de una inf del plano complejo?
En las computadoras, los números complejos generalmente se implementan uniendo dos fp reales, uno para el real y otro para la parte imaginaria. Eso está perfectamente bien siempre que todo sea finito. Sin embargo, tan pronto como se consideran infinitos, las cosas se vuelven complicadas.
El plano complejo tiene una simetría rotacional natural, que se relaciona muy bien con la aritmética compleja, ya que multiplicar el plano completo por e ^ phij es lo mismo que una rotación phi radianes alrededor 0
.
Ahora, para mantener las cosas simples, fp complejo simplemente usa las extensiones (+/- inf, nan, etc.) de la implementación del número real subyacente. Esta elección puede parecer tan natural que ni siquiera se percibe como una elección, pero echemos un vistazo más de cerca a lo que implica. Una visualización simple de esta extensión del plano complejo se ve así (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
Pero dado que un verdadero plano complejo es aquel que respeta la multiplicación compleja, una proyección más informativa sería
III
I I
fffff
fffffff
fffffffff
I fffffffff I
I ffff0ffff I
I fffffffff I
fffffffff
fffffff
fffff
I I
III
En esta proyección vemos la "distribución desigual" de infinitos que no solo es fea sino también la raíz de problemas del tipo que ha sufrido OP: La mayoría de los infinitos (los de las formas (+/- inf, finito) y (finito, + / -inf) se agrupan en las cuatro direcciones principales, todas las demás direcciones están representadas por solo cuatro infinitos (+/- inf, + -inf). No debería sorprender que extender la multiplicación compleja a esta geometría sea una pesadilla .
El anexo G de la especificación C99 hace todo lo posible para que funcione, incluida la flexión de las reglas sobre cómo inf
e nan
interactuar (esencialmente inf
triunfa nan
). El problema de OP se evita al no promover los reales y un tipo puramente imaginario propuesto a complejo, pero hacer que el 1 real se comporte de manera diferente al complejo 1 no me parece una solución. Es revelador que el Anexo G no llegue a especificar completamente cuál debería ser el producto de dos infinitos.
Es tentador intentar solucionar estos problemas eligiendo una mejor geometría de infinitos. En analogía con la línea real extendida, podríamos agregar un infinito para cada dirección. Esta construcción es similar al plano proyectivo, pero no agrupa direcciones opuestas. Los infinitos se representarían en coordenadas polares inf xe ^ {2 omega pi i}, la definición de productos sería sencilla. En particular, el problema de OP se resolvería de forma bastante natural.
Pero aquí es donde terminan las buenas noticias. En cierto modo, podemos ser arrojados de nuevo al punto de partida, no sin razón, requiriendo que nuestros infinitos de estilo nuevo admitan funciones que extraigan sus partes reales o imaginarias. La adición es otro problema; agregando dos infinitos no antipodales, tendríamos que establecer el ángulo en indefinido, es decir nan
(se podría argumentar que el ángulo debe estar entre los dos ángulos de entrada, pero no hay una forma simple de representar esa "nan-nidad parcial")
En vista de todo esto, tal vez la compactación de un solo punto sea lo más seguro. Quizás los autores del Anexo G sintieron lo mismo al imponer una función cproj
que agrupa todos los infinitos.
Aquí hay una pregunta relacionada respondida por personas más competentes en el tema que yo.
nan != nan
. Entiendo que esta respuesta es medio en broma, pero no veo por qué debería ser útil para el OP de la forma en que está escrito.
==
(y dado que aceptaron la otra respuesta), parece que fue solo un problema de cómo el OP expresó el título. Reformulé el título para corregir esa inconsistencia. (Invalidar intencionalmente la primera mitad de esta respuesta porque estoy de acuerdo con @cmaster: eso no es lo que preguntaba esta pregunta).
Este es un detalle de implementación de cómo se implementa la multiplicación compleja en CPython. A diferencia de otros lenguajes (por ejemplo, C o C ++), CPython adopta un enfoque algo 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;
}
Un caso problemático con el código anterior sería:
(0.0+1.0*j)*(inf+inf*j) = (0.0*inf-1*inf)+(0.0*inf+1.0*inf)j
= nan + nan*j
Sin embargo, a uno le gustaría tener -inf + inf*j
como resultado.
En este sentido, otros lenguajes no están muy por delante: la multiplicación de números complejos no fue durante mucho tiempo parte del estándar C, incluido solo en C99 como apéndice G, que describe cómo se debe realizar una multiplicación compleja, y no es tan simple como la fórmula de la escuela anterior! El estándar C ++ no especifica cómo debería funcionar la multiplicación compleja, por lo que la mayoría de las implementaciones de compiladores están recurriendo a la implementación de C, que puede ser conforme a C99 (gcc, clang) o no (MSVC).
Para el ejemplo "problemático" anterior, las implementaciones compatibles con C99 (que son más complicadas que la fórmula de la escuela) darían ( ver en vivo ) el resultado esperado:
(0.0+1.0*j)*(inf+inf*j) = -inf + inf*j
Incluso con el estándar C99, no se define un resultado inequívoco para todas las entradas y podría ser diferente incluso para las versiones compatibles con C99.
Otro efecto secundario de float
no ser promocionado complex
en C99 es que multiplicar inf+0.0j
con 1.0
o 1.0+0.0j
puede conducir a resultados diferentes (ver aquí en vivo):
(inf+0.0j)*1.0 = inf+0.0j
(inf+0.0j)*(1.0+0.0j) = inf-nanj
, ser parte imaginaria -nan
y no nan
(como para CPython) no juega un papel aquí, porque todos los nans silenciosos son equivalentes (ver esto ), incluso algunos de ellos tienen un bit de signo establecido (y por lo tanto se imprimen como "-", ver esto ) y otros no.Lo cual es al menos contrario a la intuición.
Mi conclusión clave es: no hay nada simple en la multiplicación (o división) de números complejos "simples" y cuando se cambia entre lenguajes o incluso compiladores uno debe prepararse para errores / diferencias sutiles.
printf
y similar funciona con double: miran el bit de signo para decidir si "-" debe imprimirse o no (sin importar si es nan o no). Entonces tiene razón, no hay una diferencia significativa entre "nan" y "-nan", arreglando esta parte de la respuesta pronto.
Definición divertida de Python. Si estamos resolviendo esto con lápiz y papel, diría que el resultado esperado sería el expected: (inf + 0j)
que usted señaló porque sabemos que nos referimos a la norma de 1
eso (float('inf')+0j)*1 =should= ('inf'+0j)
:
Pero ese no es el caso como puede ver ... cuando lo ejecutamos obtenemos:
>>> Complex( float('inf') , 0j ) * 1
result: (inf + nanj)
Python entiende esto *1
como un número complejo y no como la norma, por 1
lo que interpreta como *(1+0j)
y el error aparece cuando intentamos hacer lo inf * 0j = nanj
que inf*0
no se puede resolver.
Lo que realmente quiere hacer (asumiendo que 1 es la norma de 1):
Recuerde que si z = x + iy
es un número complejo con parte real xy parte imaginaria y, el conjugado complejo de z
se define como z* = x − iy
, y el valor absoluto, también llamado norm of z
se define como:
Suponiendo que 1
es la norma 1
, deberíamos hacer 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)
no muy intuitivo, lo sé ... pero a veces los lenguajes de codificación se definen de una forma diferente a la que usamos en nuestro día a día.