복소 자연로그: clog(C99)

복소 자연로그: clog(C99)

실수에서 log가 자연로그를 구하듯, 복소수에도 거듭제곱과 로그를 구하는 함수가 필요해요. 그중 밑이 자연상수 e인 복소 로그를 계산하는 함수가 clog예요. 복소 로그는 정의역을 나눠야 하기 때문에 실수 로그보다 한 가지 더 생각할 게 있는데, 바로 분지 절단(branch cut)이에요.

출처: cppreference

본문

시그니처

<complex.h> 헤더에 정의돼 있어요.

형태 주석
float complex clogf ( float complex z ); (1) C99부터
double complex clog ( double complex z ); (2) C99부터
long double complex clogl ( long double complex z ); (3) C99부터
#define log(z) (4) C99부터, <tgmath.h>

동작 방식

음의 실수축을 따라 분지 절단을 두고, 복소수 z의 자연(밑 e) 로그를 계산해요.

(4)의 log는 타입 제네릭 매크로예요. zfloat complex, double complex, long double complex면 각각 맞는 버전을 불러요. 그런데 z가 실수나 정수 타입이면 복소 함수 대신 대응하는 실수 함수(logf, log, logl)를 호출하고, 허수 타입이면 복소 버전을 호출한다는 특이한 규칙이 있어요.

반환값

오류가 없다면 z의 복소 자연로그를 반환해요. 값의 범위는 허수축 방향으로는 구간 [−iπ, +iπ]에, 실수축 방향으로는 수학적으로 무한히 뻗어 있는 띠(strip) 안에 들어와요.

오류 처리와 특수값

오류는 math_errhandling에 따라 보고돼요. 구현이 IEEE 부동소수점 산술을 지원한다면 다음 규칙들이 적용돼요.

  • 함수는 분지 절단 위에서 허수부의 부호를 고려하여 연속이에요.
  • clog(conj(z)) == conj(clog(z))가 성립해요.
  • z-0+0i면 결과는 -∞+πi이고 FE_DIVBYZERO가 발생해요.
  • z+0+0i면 결과는 -∞+0i이고 FE_DIVBYZERO가 발생해요.
  • zx+∞i(유한한 모든 x)면 결과는 +∞+πi/2예요.
  • zx+NaNi(유한한 모든 x)면 결과는 NaN+NaNi이고 FE_INVALID가 발생할 수 있어요.
  • z-∞+yi(유한한 양의 모든 y)면 결과는 +∞+πi예요.
  • z+∞+yi(유한한 양의 모든 y)면 결과는 +∞+0i예요.
  • z-∞+∞i면 결과는 +∞+3πi/4예요.
  • z+∞+∞i면 결과는 +∞+πi/4예요.
  • z±∞+NaNi면 결과는 +∞+NaNi예요.
  • zNaN+yi(유한한 모든 y)면 결과는 NaN+NaNi이고 FE_INVALID가 발생할 수 있어요.
  • zNaN+∞i면 결과는 +∞+NaNi예요.
  • zNaN+NaNi면 결과는 NaN+NaNi예요.

규칙이 많아 보이지만 전부 '무한대와 NaN이 섞이면 로그가 어떻게 수렴하는가'에 관한 정리예요. 특수값을 다루는 복소 함수는 이런 표가 유용하답니다.

주의할 점

극좌표 성분 (r, θ)를 가진 복소수 z의 자연로그는 ln r + i(θ+2nπ)와 같아요. 여기서 주요값(principal value)은 ln r + iθ`예요. 2π의 배수만큼 더해진 값들이 모두 로그가 될 수 있다는 게 복소 로그의 다가성(多價性)이에요.

예제

몇 가지 대표 입력의 복소 로그를 구해 보는 코드예요.

#include <stdio.h>
#include <math.h>
#include <complex.h>

int main(void)
{
    double complex z = clog(I); // r = 1, θ = pi/2
    printf("2*log(i) = %.1f%+fi\n", creal(2*z), cimag(2*z));

    double complex z2 = clog(sqrt(2)/2 + sqrt(2)/2*I); // r = 1, θ = pi/4
    printf("4*log(sqrt(2)/2+sqrt(2)i/2) = %.1f%+fi\n", creal(4*z2), cimag(4*z2));

    double complex z3 = clog(-1); // r = 1, θ = pi
    printf("log(-1+0i) = %.1f%+fi\n", creal(z3), cimag(z3));

    double complex z4 = clog(conj(-1)); // or clog(CMPLX(-1, -0.0)) in C11
    printf("log(-1-0i) (the other side of the cut) = %.1f%+fi\n", creal(z4), cimag(z4));
}

출력:

2*log(i) = 0.0+3.141593i
4*log(sqrt(2)/2+sqrt(2)i/2) = 0.0+3.141593i
log(-1+0i) = 0.0+3.141593i
log(-1-0i) (the other side of the cut) = 0.0-3.141593i

마지막 두 줄을 잘 보면 분지 절단의 양쪽이 어떻게 갈리는지 드러나요. log(-1+0i)+πi로, 그 반대편 log(-1-0i)-πi로 수렴해요. 바로 이 지점에서 복소 로그는 연속이 아니게 됩니다. 그래서 극한값을 잘 맞추려면 어느 쪽인지가 중요해요.

더 알아보기

  • cexp: 복소 지수 함수(밑 e).
  • log, logf, logl: 실수 자연로그.
  • conj: 켤레 복소수.