python nan ieee-754

python - ¿Por qué(inf+0j)*1 evalúa a inf+nanj?



ieee-754 (3)

El 1 se convierte primero en un número complejo, 1 + 0j , que luego conduce a una multiplicación inf * 0 , 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

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

¿Por qué? Esto causó un error desagradable en mi código.

¿Por qué no es 1 la identidad multiplicativa, dando (inf + 0j) ?


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:

  1. ints / floats son promovidos a números complejos en la multiplicación
  2. Se utiliza la fórmula escolar simple, que no proporciona los resultados deseados / esperados tan pronto como intervienen números infinitos:

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.

A este respecto, otros lenguajes no están muy por delante: la multiplicación de números complejos fue durante mucho tiempo no 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 de arriba! El estándar C ++ no especifica cómo debería funcionar la multiplicación compleja, por lo tanto, la mayoría de las implementaciones del compilador están volviendo a la implementación C, que podría ser C99 conforme (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 puede ser diferente incluso para las versiones compatibles con C99.

Otro efecto secundario de que el float no se promueva a 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 , la parte imaginaria siendo -nan y no nan (como para CPython) no juega un papel aquí, porque todos los nans silenciosos son equivalentes (ver this ), incluso algunos de ellos tienen un conjunto de bits de signo (y, por lo tanto, se imprimen como "-", vea this ) y otros no.

Lo cual es al menos contra-intuitivo.

Mi clave es: no hay nada simple sobre la multiplicación (o división) de números complejos "simples" y cuando se cambia entre idiomas o incluso compiladores uno debe prepararse para errores / diferencias sutiles.


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 por computadora de la multiplicación compleja o hay algo conceptualmente inf+0j con inf+0j

Respuesta corta:

Usando coordenadas polares podemos ver la multiplicación compleja como una escala y una rotación. Girando un "brazo" infinito incluso 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 incorrecto con inf+0j , a saber, que tan pronto como estamos en el infinito, un desplazamiento finito deja de tener sentido.

Respuesta larga:

Antecedentes: La "gran cosa" en torno a la cual gira esta pregunta es la cuestión de extender un sistema de números (piense en números reales o complejos). Una razón por la que uno podría querer hacer eso es agregar algún concepto de infinito, o "compactar" si resulta ser matemático. También hay otras razones ( https://en.wikipedia.org/wiki/Galois_theory , https://en.wikipedia.org/wiki/Non-standard_analysis ), pero no estamos interesados ​​en eso aquí.

Compactificación de un punto

La parte complicada de tal extensión es, por supuesto, que queremos que estos nuevos números se ajusten a la aritmética existente. La forma más simple 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 ).

Otras extensiones ...

Si bien la compactación de un punto es simple y matemáticamente sólida, se han buscado extensiones "más ricas" que comprenden múltiples infintías. El estándar IEEE 754 para números de coma flotante real tiene + inf y -inf ( https://en.wikipedia.org/wiki/Extended_real_number_line ). Parece natural y directo, pero ya nos obliga a saltar a través de aros e inventar cosas como -0 https://en.wikipedia.org/wiki/Signed_zero

... del plano complejo

¿Qué pasa con las extensiones más de una inf del plano complejo?

En las computadoras, los números complejos generalmente se implementan al unir dos reales de fp, 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 los infinitos, las cosas se vuelven difíciles.

El plano complejo tiene una simetría rotacional natural, que se relaciona muy bien con la aritmética compleja, ya que multiplicar todo el plano por e ^ phij es lo mismo que una rotación de radios alrededor de 0 .

Esa cosa del anexo G

Ahora, para simplificar las cosas, fp complejo simplemente usa las extensiones (+/- inf, nan, etc.) de la implementación de números reales subyacentes. 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. Se ve una visualización simple de esta extensión del plano complejo (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 que también es la raíz de los problemas del tipo que OP ha sufrido: 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, incluyendo las reglas sobre cómo interactúan inf y nan (esencialmente inf triunfa nan ). El problema de OP se elude al no promover los reales y un tipo puramente imaginario propuesto como complejo, pero hacer que el 1 real se comporte de manera diferente al complejo 1 no me parece una solución. De manera reveladora, el Anexo G no llega a especificar completamente cuál debería ser el producto de dos infinitos.

¿Podemos hacerlo mejor?

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 se agrupa en 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 termina la buena noticia. En cierto modo, podemos regresar al punto de partida --- no sin razón - requiriendo que nuestros infinitos de nuevo estilo admitan funciones que extraen 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-paridad parcial")

Riemann al rescate

En vista de todo esto, tal vez la buena compactación de un punto es lo más seguro. Tal vez los autores del Anexo G sintieron lo mismo cuando cproj 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.