¿Por qué log(inf + inf j) es igual a (inf + 0.785398 j), en C++/Python/NumPy?

9 minutos de lectura

Avatar de usuario de Firman
Firmán

He estado encontrando un comportamiento extraño de log funciones en C++ y numpy sobre el comportamiento de log función que maneja números infinitos complejos. Específicamente, log(inf + inf * 1j) es igual (inf + 0.785398j) cuando espero que sea (inf + nan * 1j).

Al tomar el logaritmo de un número complejo, la parte real es el logaritmo del valor absoluto de la entrada y la parte imaginaria es la fase de la entrada. Devolviendo 0.785398 como la parte imaginaria de log(inf + inf * 1j) significa que asume la infs en la parte real e imaginaria tienen la misma longitud. Esta suposición no parece ser consistente con otros cálculos, por ejemplo, inf - inf == nan, inf / inf == nan que asume 2 infs no tienen necesariamente los mismos valores.

¿Por qué la suposición de log(inf + inf * 1j) ¿diferente?

Reproduciendo código C++:

#include <complex>
#include <limits>
#include <iostream>
int main() {
    double inf = std::numeric_limits<double>::infinity();
    std::complex<double> b(inf, inf);
    std::complex<double> c = std::log(b);
    std::cout << c << "\n";
}

Reproduciendo el código de Python (numpy):

import numpy as np

a = complex(float('inf'), float('inf'))
print(np.log(a))

EDITAR: Gracias a todos los que están involucrados en la discusión sobre la razón histórica y la razón matemática. Todos ustedes convierten esta pregunta ingenua en una discusión realmente interesante. Las respuestas proporcionadas son todas de alta calidad y deseo poder aceptar más de 1 respuesta. Sin embargo, decidí aceptar la respuesta de @ simon, ya que explica con más detalle la razón matemática y proporcioné un enlace al documento que explica la lógica (aunque no puedo entenderlo completamente).

  • pitón y cpp

    – Marek R.

    14 de diciembre a las 12:58

  • [C++] “… Manejo de errores y valores especiales…” “…Si z es (+∞,+∞), el resultado es (+∞,π/4).” es.cppreference.com/w/cpp/numeric/complex/log

    – Richard Critten

    14 de diciembre a las 13:02


  • @RichardCritten que debería publicarse como respuesta. La razón es probablemente “La semántica de esta función pretende ser consistente con la función C clog“.

    – Andras Deak — Слава Україні

    14 de diciembre a las 13:03


  • Sería interesante cuán atrás en la historia se remonta este comportamiento. al menos gfortran también hace esto, aunque esto puede deberse más a que comparte código con el GCC centrado en C que al propio Fortran.

    – a la izquierda

    15 de diciembre a las 10:09


Andras Deak -- avatar de usuario de Слава Україні
Andras Deak — Слава Україні

Él borrador final gratuito de la especificación C99 dice en la página 491

clog(+∞, +i∞) devuelve +∞ + iπ/4.

Este sigue siendo el caso en la actualidad. Él Especificación C++ explica las mismas reglas con la nota

La semántica de esta función pretende ser consistente con la función C clog.

Estoy de acuerdo en que el comportamiento es confuso desde el punto de vista matemático y podría decirse que no es coherente con otros inf semántica, como usted señaló. Pero pragmáticamente, es parte del estándar C, lo que lo convierte en parte del estándar C++, y dado que NumPy normalmente se basa en el comportamiento de C (incluso en casos confusos), esto se hereda en el ejemplo de Python.

La biblioteca estándar cmath.log() la función tiene el mismo comportamiento (si lo prueba bien…):

>>> import cmath

>>> cmath.log(complex(float('inf'), float('inf')))
(inf+0.7853981633974483j)

No tengo medios para investigar la lógica que se introdujo en el estándar C. Supongo que aquí se tomaron decisiones pragmáticas, potencialmente al considerar cómo estas funciones complejas interactúan entre sí.

  • @Fareanor hay bastante equipaje de C directamente en NumPy, y esperaría un gran rechazo si alguien propusiera “arreglar” esto. El ejemplo más flagrante es cómo np.int_ depende de la plataforma porque la C long el tipo depende de la plataforma. Por supuesto, es difícil separar “C hace esto” de “lo hemos estado haciendo de esta manera durante 15 años”.

    – Andras Deak — Слава Україні

    14 de diciembre a las 13:27


  • @Fareanor Por supuesto, la premisa es que todas estas extensiones están en C. Pero este es un detalle de implementación, y las bibliotecas podrían ocultar elecciones extrañas realizadas por C aunque estén implementadas en él. Por ejemplo, por carcasa especial log para números complejos cuando los argumentos son infinitos. Pero punto tomado, y esta es probablemente principalmente una pregunta filosófica. Pragmáticamente, si escribe su biblioteca en C, naturalmente hace lo que hace.

    – Andras Deak — Слава Україні

    14 de diciembre a las 13:41

  • Tienes razón. Mi suposición es que probablemente simplemente reenviaron la llamada a la función c log sin hacer nada más (eso explicaría que los resultados son los mismos).

    – Faranor

    14 de diciembre a las 13:43

  • @Fareanor: “no es un” ¡C hace esto! Nosotros también !”.” Como la persona que implementó esto, puedo decirles que es exactamente ese. 🙂 El módulo cmath de CPython no envuelve las operaciones matemáticas complejas de C; implementa todo directamente. El manejo de casos especiales fue elegido deliberadamente (por mí) para que coincida con lo que está en el estándar C99.

    –Mark Dickinson

    15 de diciembre a las 11:36


  • @MarkDickinson esa es una idea increíble, gracias. Debería considerar publicar una respuesta, porque aunque solo aborda la parte de Python de la pregunta, es la respuesta menos conjetura/no sé (y una parte muy interesante de la historia relevante).

    – Andras Deak — Слава Україні

    15 de diciembre a las 19:50

avatar de usuario de simon
Simón

El valor de 0.785398 (realmente pi/4) es consistente con al menos algunos otras funciones: como dijiste, la parte imaginaria del logaritmo de un número complejo es idéntica al ángulo de fase del número. Esto se puede reformular a una pregunta propia: ¿cuál es el ángulo de fase de inf + j * inf?

Podemos calcular el ángulo de fase de un número complejo z por atan2(Im(z), Re(z)). Con el número dado, esto se reduce a calcular atan2(inf, inf), que también es 0,785398 (o pi/4), tanto para Numpy como para C/C++. Así que ahora se podría hacer una pregunta similar: ¿por qué atan2(inf, inf) == 0.785398?

No tengo una respuesta para esto último (a excepción de “las especificaciones de C/C++ lo dicen”, como ya respondieron otros), solo tengo una conjetura: como atan2(y, x) == atan(y / x) por x > 0probablemente alguien tomó la decisión en este contexto de no interpretar inf / inf como “indefinido” sino como “un número muy grande dividido por el mismo número muy grande”. El resultado de esta razón sería 1, y atan(1) == pi/4 por la definición matemática de atan.

Probablemente esta no sea una respuesta satisfactoria, pero al menos espero poder mostrar que el log definición en el caso límite dado no es completamente inconsistente con casos límite similares de definiciones de funciones relacionadas.

Editar: Como dije, consistente con algunos otras funciones: también es consistente con np.angle(complex(np.inf, np.inf)) == 0.785398por ejemplo.

Editar 2: Mirando a la código fuente de un actual atan2 implementación trajo el siguiente comentario de código:

tenga en cuenta que los casos no obvios son y y x ambos infinitos o ambos cero. Para más información, ver Cortes de ramas para funciones elementales complejas o mucho ruido y pocas nueces para el bit de signopor W. Kahan

Desenterré el documento al que se hace referencia, puedes encontrar una copia aquí. En el Capítulo 8 de esta referencia, denominado “Ceros e infinitos complejos”, Guillermo Kahan (que es a la vez matemático e informático y, según Wikipedia, el “padre del punto flotante”) cubre los casos de borde cero e infinito de números complejos y llega a pi/4 para alimentar inf + j * inf en el arg función (arg siendo la función que calcula el ángulo de fase de un número complejo, al igual que np.angle arriba). Encontrará este resultado en la página 17 en el PDF vinculado. No soy lo suficientemente matemático para poder resumir el razonamiento de Kahan (es decir: realmente no lo entiendo), pero tal vez alguien más pueda hacerlo.

  • Gracias por esto. Parece que si está relacionado con el ángulo, inf en real y en imaginario se supone que tienen la misma longitud.

    – Firmán

    14 de diciembre a las 14:59

  • @Firman Eso es exactamente lo que traté de decir con mi respuesta demasiado complicada: D

    – Simón

    14 dic a las 15:00

  • Por una razón por la cual atan2(inf, inf) = pi/4 podría definirse de esta manera: da el cuadrante correcto, que se conoce aunque se desconoce el ángulo exacto. regresando nan daría menos información.

    – jpa

    15 de diciembre a las 11:54

  • @Firman Agregué un enlace a un documento que brinda más información. Ver mi segunda edición.

    – Simón

    15 de diciembre a las 14:34

  • @jpa podrías hacer el mismo argumento para inf/inf. eso sigue siendo claramente no negativo, por lo que definirlo como +1 daría más información que NaN. Pero evidentemente no encontraron esto convincente en esa ocasión.

    – a la izquierda

    15 de diciembre a las 18:34


Si consideramos esto desde un punto de vista matemático puro, entonces podemos ver las operaciones en términos de límites, por ejemplo, cuando x tiende a infinito, 1/x tiende a 0 (denotado lim(x => inf) 1/x = 0), que es lo que observamos con puntos flotantes.

Para operaciones con 2 infinitos, consideramos cada infinito por separado. Por lo tanto:

lim(x => inf) x/1 = inf
lim(x => inf) 1/x = 0

y en general decimos inf/x = inf, y x/inf = 0. Así:

lim(x => inf) inf/x = inf
lim(x => inf) x/inf = 0

¿Cuál de estos 2 deberíamos preferir? La especificación para flotadores se elude declarándolo un NaN.

Sin embargo, para registros complejos observamos:

lim(x=>inf) log(x + 0j) = inf + 0j
lim(x=>inf) log(0 + xj) = inf + pi/2j
lim(x=>inf) log(inf + xj) = inf + 0j
lim(x=>inf) log(x + infj) = inf + pi/2j

Todavía hay una contradicción, pero en lugar de estar entre 0 e inf, está entre 0 y pi/2, por lo que los autores de las especificaciones optaron por dividir la diferencia. No sabría decir por qué tomaron esta decisión, pero los infinitos de punto flotante no son infinitos matemáticos, sino que representan ‘este número es demasiado grande para la representación’. Dado que es probable que los usos de log(complejo) sean más puramente matemáticos que los de resta y división, los autores pueden haber sentido que conservar la identidad im(log(x+xj)) == pi/4 era útil.

  • Peor aún, si x y y son variables separadas entonces como (x, y) -> (inf, inf)puede obtener cualquier ángulo que desee para x + i*y en el rango [0, pi/2].

    – kaya3

    16 dic a las 16:53


¿Ha sido útil esta solución?