Complex numbers라는 이름은 복잡하지 않아요.
그저 덜 놀라운 이름만 있으면 돼요.
이들은 공학과 과학 분야에서 특히나 유용해서, Python은 정수(ints)와 실수(floats)에 더해 complex를 표준 숫자 타입으로 포함하고 있어요.
Python에서 complex 값은 본질적으로 실수 두 개를 짝지은 것이에요.
이 둘은 안타까운 역사적 이유로 "실수부"와 "허수부"라고 불려요.
다시 말하지만, 이상한 이름보다는 그 바탕에 있는 단순함에 집중하는 게 좋아요.
복소수를 만드는 흔한 방법에는 두 가지가 있어요.
complex(real, imag) 생성자는 두 개의 float 매개변수를 받아요:>>> z1 = complex(1.5, 2.0)
>>> z1
(1.5+2j)
이 생성자는 문자열 입력을 해석할 수도 있어요. 다만 문자열에 공백이 들어 있으면 실패한다는 이상한 제약이 있어요.
>>> complex('4+2j')
(4+2j)
>>> complex('4 + 2j')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: complex() arg is a malformed string
<real part> + <complex part>j 리터럴로 지정할 수도 있고, 실수부가 0이면 <complex part>j만 써도 돼요:>>> z2 = 2.0 + 1.5j
>>> z2
(2+1.5j)
이렇게 하면 최종 결과는 complex() 생성자를 쓴 것과 똑같아요.
복소수의 허수부에는 두 가지 규칙이 있어요:
j로 표기해요 (수학 교과서에서 볼 수 있는 i가 아니라요).
j는 반드시 숫자 바로 뒤에 붙여야 해요. 그래야 Python이 이를 변수 이름으로 해석하지 않아요. 필요하면 1j를 써요.
>>> j
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'j' is not defined
>>> 1j
1j
>>> type(1j)
<class 'complex'>
대부분의 엔지니어는 j를 반가워해요.
대부분의 과학자와 수학자는 _허수_를 뜻하는 수학 표기 i를 더 선호하지만, 그 표기는 전기공학에서 _전류_를 뜻하는 i와 충돌해요.
그래서 Python을 설계할 때는 전기 엔지니어들이 이겼어요.
복소수의 각 부분에 개별적으로 접근하려면:
>>> z2.real
2.0
>>> z2.imag
1.5
어느 한쪽 부분이 0일 수도 있는데, 수학자들은 그런 수를 "완전히 실수" 또는 "완전히 허수"라고 말하기도 해요. 하지만 Python에서는 여전히 복소수예요:
>>> complex(0, 1)
1j
>>> type(complex(0, 1))
<class 'complex'>
>>> complex(1, 0)
(1+0j)
"i(또는 j)가 -1의 제곱근이다"라는 말을 들어 봤을지도 몰라요.
지금으로서는, 이 말은 허수부가 정의상 다음 등식을 만족한다는 뜻일 뿐이에요
1j * 1j == -1 # => True
단순한 생각이지만, 흥미로운 결과로 이어져요.
실수와 정수에 쓰이는 operators 대부분은 복소수에도 그대로 쓸 수 있어요:
>>> z1 = (1.5+2j)
>>> z2 = (2+1.5j)
>>> z1 + z2 # addition
(3.5+3.5j)
>>> z1 - z2 # subtraction
(-0.5+0.5j)
>>> z1 * z2 # multiplication
6.25j
>>> z1 / z2 # division
(0.96+0.28j)
>>> z1 ** 2 # exponentiation
(-1.75+6j)
>>> 2 ** z1 # another exponentiation
(0.5188946835878313+2.7804223253571183j)
>>> 1j ** 2 # j * j == -1
(-1+0j)
복소수의 곱셈과 나눗셈 규칙을 설명하는 것은 이 개념의 범위를 벗어나요 (그리고 이런 연산을 "손으로" 직접 해야 할 일도 그리 자주 있지는 않아요).
복소수에 대한 수학 또는 전기공학 입문서라면 어디든 이 내용을 다뤄요. 더 깊이 파고들고 싶다면 그런 자료를 찾아보세요.
또는, Exercism에는 Complex Numbers 연습 문제가 있어서, 거기서 이런 연산들을 갖춘 복소수 클래스를 원리부터 직접 구현해 볼 수 있어요.
복소수에서는 정수 나눗셈이 ___불가능___해요. 따라서 //와 % 연산자, 그리고 divmod() 함수는 복소수 타입에서 실패해요.
숫자 타입에 구현되어 있어 복소수를 다룰 때 아주 유용한 함수가 두 개 있어요:
<complex number>.conjugate()는 복소수 허수부의 부호를 뒤집기만 해요 (+에서 -로, 또는 그 반대로).
abs(<complex number>)는 허수부가 없는 실수를 반환하는 것이 보장돼요.>>> z1
(1.5+2j)
>>> z1.conjugate() # flip the z1.imag sign
(1.5-2j)
>>> abs(z1) # sqrt(z1.real ** 2 + z1.imag ** 2)
2.5
cmath 모듈Python 표준 라이브러리에는 실수를 다루는 유용한 기능이 가득한 math 모듈이 있어요.
복소수를 다루기 위한, 그에 대응하는 cmath 모듈도 있어요.
모듈을 훑어보고 직접 실험해 보길 권해요. 주요 범주는 다음과 같아요:
몇 가지 상수를 사용한 예시예요:
>>> import cmath
>>> euler = cmath.exp(1j * cmath.pi) # Euler's equation
>>> euler.real
-1.0
>>> round(euler.imag, 15) # round to 15 decimal places
0.0
자연에서 가장 중요한 상수 세 개인 e, i(또는 j), pi로 이루어진 단순한 식이 -1이라는 결과를 내놓아요.
어떤 사람들은 이것이 수학 전체에서 가장 아름다운 결과라고 믿어요.
이 결과는 1740년경까지 거슬러 올라가요.
관심이 없다면 이 부분은 건너뛰어도 돼요.
16세기에는 이상하고 새로웠어요.
500년이 지난 지금은 대부분의 공학과 물리 과학의 중심에 있어요.
알고 보면 복소수는 회전하는 것, 또는 파동 같은 성질을 지닌 것을 설명하는 가장 단순한 방법이에요. 그래서 전기공학, 오디오 처리, 물리학, 컴퓨터 게임, 항법 등 여러 분야에서 폭넓게 쓰여요. 몇 가지만 꼽자면 그렇다는 거예요.
회전하는 것은 눈으로 볼 수 있어요. 복소수가 세상을 돌게 만들지는 못하겠지만, 세상이 돌아간 그 결과로 무슨 일이 일어나는지 설명하는 데는 탁월해요. 큰 폭풍의 위성 사진을 한번 보세요.
덜 뻔한 예를 들면, 소리는 파동 같고, 빛도 파동 같고, 전파 신호도 파동 같으며, 심지어 우리나라 경제도 적어도 일부분은 파동 같아요.
이런 파동 처리는 상당 부분 삼각 함수(sin()과 cos())로도 할 수 있지만, 금세 복잡해져요.
복소 지수는 다루기가 훨씬 쉬워요.
동굴에 살면서 먹을 것을 직접 구하러 다닌다면 그 말이 맞아요.
어떤 종류의 화면으로든 이 글을 읽고 있다면, 복소수를 활용해 이뤄낸 20세기의 유용한 발전들에 완전히 의존하고 있는 셈이에요.
반도체 칩.
고속 푸리에 변환(FFT) 알고리즘.
FFT는 복소수의 응용이고, 소리 전송, 오디오 처리, 사진, 영상과 관련된 _모든 것_에 들어 있어요.
MP3를 비롯한 여러 오디오 형식은 압축에 FFT를 사용해서, 더 많은 오디오를 더 작은 저장 공간에 담을 수 있게 해요.
JPEG 압축과 MP4 영상을 비롯한 수많은 이미지·영상 형식도 압축에 FTT를 사용해요.
FFT는 휴대폰 기지국이 내 개인 신호를 다른 사람들의 신호와 분리할 수 있게 해 주는 디지털 필터에도 쓰여요.
그래서 아마 초당 수천 번씩 복소수 계산에 의존하는 기술을 쓰고 있을 거예요.