Home > Software design >  Why is `log(inf infj)` equal to `(inf 0.785398)`?
Why is `log(inf infj)` equal to `(inf 0.785398)`?

Time:12-14

I've been finding a strange behaviour of log functions in C and numpy about the behaviour of log function handling complex infinite numbers. Specifically, log(inf inf * 1j) equals to (inf 0.785398j) when I expect it to be (inf nan * 1j).

When taking log of a complex number as input, the real part is the log of absolute value of the input and the imaginary part is the phase of the input. Returning 0.785398 as the imaginary part of log(inf inf * 1j) means it assumes the infs in the real and the imaginary part have the same length. This assumption does not seem to be consistent with other calculation, for example, inf - inf == nan, inf / inf == nan which assumes 2 infs do not necessarily have the same values.

Why is the assumption for log(inf inf * 1j) different?

Reproducing code 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";
}

Reproducing code Python (numpy):

import numpy as np

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

CodePudding user response:

The free final draft of the C99 specification says on page 491

clog( ∞, i∞) returns ∞ iπ/4.

This is still the case currently. The C specification explains the same rules with the note

The semantics of this function are intended to be consistent with the C function clog.

I agree the behaviour is confusing from a math point of view, and arguably inconsistent with other inf semantics, as you pointed out. But pragmatically, it's part of the C standard, which makes it part of the C standard, and since NumPy normally relies on C behaviour (even in confusing cases), this is inherited in the Python example.

The standard-library cmath.log() function has the same behaviour (if you test it right...):

>>> import cmath

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

I have no means to investigate the rationale that went into the C standard. I assume there were pragmatic choices being made here, potentially when considering how these complex functions interact with each other.

CodePudding user response:

The value of 0.785398 (actually pi/4) is consistent with at least some other functions: as you said, the imaginary part of the logarithm of a complex number is identical with the phase angle of the number. This can be reformulated to a question of its own: what is the phase angle of inf j * inf?

We can calculate the phase angle of a complex number z by atan2(Im(z), Re(z)). With the given number, this boils down to calculating atan2(inf, inf), which is also 0.785398 (or pi/4), both for Numpy and C/C . So now a similar question could be asked: why is atan2(inf, inf) = 0.785398?

I do not have an answer to the latter (except for "the C/C specifications say so", as others already answered), I only have a guess: as atan2(y, x) == atan(y / x) for x > 0, probably someone made the decision in this context to not interpret inf / inf as "undefined" but instead as "a very large number divided by the same very large number". The result of this ratio would be 1, and atan(1) == pi/4 by the mathematical definition of atan.

Probably this is not a satisfying answer, but at least I could hopefully show that the log definition in the given edge case is not completely inconsistent with similar edge cases of related function definitions.

Edit: As I said, consistent with some other functions: it is also consistent with np.angle(complex(np.inf, np.inf)) == 0.785398, for example.

  • Related