파이썬에서 수를 나타내는 데이터 유형에는 정수, 실수, 복소수 등 여러 가지가 있다. 이들은 모두 수에 속하지만, 표현할 수 있는 수의 종류와 범위가 다르다. 하나씩 살펴보자.

4.2.1 정수

정수(integer, 줄여서 int)는 자연수에 음양 부호(+, -)가 붙은 것이다. 비트 하나로 부호를 표현하고, 여러 개의 비트 묶음으로 자연수를 표현한다.

여러분은 이미 파이썬에서 정수를 여러 번 사용해 보았다. 파이썬에서 소수점 없이 입력된 수, 즉 0, -15 같은 표현은 모두 정수다. 다음은 대화식 셸에 정수를 입력해 본 것이다.

코드 4-1 정수의 예

>>> 1000   # 양의 정수
1000

>>> -1000  # 음의 정수
-1000

파이썬은 자릿수가 큰 수도 취급할 수 있다. 작은 수를 입력할 때와 마찬가지로 그냥 코드에 입력하면 된다.

코드 4-2 큰 정수

>>> 1000000000
1000000000

일상 생활에서는 큰 수를 알아보기 쉽게 세 자리마다 쉼표를 붙여 1,000,000,000 처럼 표기하곤 한다. 파이썬 코드로는 쉼표 대신 밑줄(_)을 하나씩 붙여 표기할 수 있다.

코드 4-3 큰 수를 알아보기 쉽게 표기하는 방법

>>> 1_000_000_000  # 1000000000과 동일하다
1000000000

팔진법과 십육진법

정수는 팔진법이나 십육진법으로도 표현할 수 있다. 팔진수는 0o로 시작하고, 십육진수는 0x로 시작한다. 0o77는 팔진수 77, 0xff는 십육진수 ff다.

코드 4-4 팔진법과 십육진법으로 정수 표현하기

>>> 0o77  # 팔진수 77
63

>>> 0xff  # 십육진수 ff
255

>>> 255 == 0xff  # 진법이 달라도 같은 정수다
True

컴퓨터 내부에서는 모든 수를 이진법으로 다룬다. 코드에서 어떤 진법으로 수를 표기하든 이진수가 되므로 컴퓨터에게는 차이가 없다. 그러니 가장 사용하기 편리한 십진법을 사용하면 된다. 십육진법과 팔진법은 비트를 직접 다루거나 색상 정보를 표현하는 등 특수한 경우에 가끔씩 사용된다. 다른 사람의 코드를 읽을 때 “아, 십육진수구나” 하고 알아볼 수 있으면 충분하다.

4.2.2 실수

실수는 -1.0, 3.1415처럼 소수점 아래까지 표현할 수 있는 수다. 1.0은 실수 유형 데이터로, 정수 유형 데이터인 1과는 구별된다.

코드 4-5 실수의 표현

>>> 3.1415
3.1415

파이썬에서는 부동소수점 수(floating point number, 줄여서 float)라는 데이터 유형으로 실수를 다룬다. 부동소수점 수는 ‘소수점이 움직이는 수’라는 뜻이다. 이게 무슨 뜻인지는 곧이어 설명한다. 편의상 부동소수점 수를 그냥 실수라고 부를 때가 많다.

과학 표기법

부동소수점 수를 이해하기 위해 과학 표기법에 관해 잠깐 알아보자. 과학 표기법이란 매우 작은 수와 매우 큰 수를 다루는 일이 많은 과학 분야에서 수를 표기하는 방법이다. 전자계산기에 매우 큰 수를 입력하면 화면에 ‘1.234e5’ 같은 수가 출력되는 것을 볼 수 있다. 과학 표기법으로 표기된 수다.

과학 표기법은 수를 가수와 지수로 나누어 적는 방법이다. 1.234e5에서 e를 중심으로 앞의 수(1.234)는 가수, 뒤의 수(5)는 지수다. 이 수가 실제로 나타내는 값은 가수의 소수점 위치를 지수만큼 오른쪽으로 옮겨 구할 수 있다. 1.234의 소수점을 다섯 칸 오른쪽으로 옮기면 123400.0이 된다. 지수가 음수라면 소수점을 왼쪽으로 옮긴다.

파이썬에서도 과학 표기법으로 실수를 나타낼 수 있다. e를 구분자로 하여 가수e지수로 표기하면 된다.

코드 4-6 과학 표기법으로 실수 표기

>>> 1.23e0   # 1.23 * 10 ^ 0
1.23

>>> 1.23e6   # 1.23 * 10 ^ 6
1230000.0

>>> 1.23e-4  # 1.23 * 10 ^ -4
0.000123

프로그램에 과학 표기법으로 수를 표기할 일이 많지는 않을 것이다. 하지만 연산 결과가 크거나 작을 때 과학 표기법으로 출력되는 경우가 종종 있으니 눈여겨 봐 두는 것도 좋겠다.

과학 표기법은 컴퓨터의 실수 표현 방식과도 관련이 있다. 컴퓨터는 실수를 기억할 때 수를 부호, 지수, 가수로 나누어 각각 비트 묶음에 담아 기억한다. 그렇게 기억해 둔 수에서, 가수를 지수만큼 소수점 이동시키면 실제 값을 구할 수 있다. 소수점의 위치가 움직이는 수라는 뜻의 ‘부동소수점 수’라는 이름도 이 방법에서 유래한 것이다.

실수의 정밀도 문제

수학의 세계에서는 정수가 실수에 포함되는 개념이다. 하지만 프로그래밍에서는 정수와 실수를 서로 다른 유형으로 구별한다. 정수도 실수로 통일해서 취급하면 편리할텐데, 왜 구별하는 것일까? 비트로 정수와 실수를 표현하는 방법이 다르기 때문이다.

정수는 비트를 이용해 정확하게 표현할 수 있다. 하지만 실수는 비트로 정확하게 표현하지 못한다. 이진법에서는 딱 나누어 떨어지는 소수가 별로 없기 때문이다. 0.1이라는 간단한 수조차도 나누어 떨어지지 않는다. (1을 2로 여러 번 나누어서 0.1을 만들려면 몇 번 나누어야 하는지 계산해 보라.) 이런 수는 이진수로는 순환소수가 되며, 비트로 정확하게 표현할 수 없으므로 아무리 정밀하게 표현하려 해도 오차가 있을 수밖에 없다. 다음 코드는 이 문제를 대화식 셸에서 확인해 본 것이다. 파이썬에서 실수를 연산하여 실수 연산 오차를 확인해 보자.

코드 4-7 파이썬의 실수 오차

>>> 1.1 - 1.0         # 계산 결과는 0.1이어야 하지만...
0.10000000000000009

1.1 - 1.0의 계산 결과는 0.1이어야 하지만 실제로는 0.10000000000000009가 나왔다. 매우 미세하지만 오차가 발생했다.

실수를 표현하는 데 사용되는 비트의 양을 늘릴 수록 오차의 크기는 작아진다. 하지만 용량을 아무리 늘려도 오차가 완전히 없어지지는 않는다. 무한히 반복되는 순환소수를 종이에 정확하게 적으려면 무한한 크기의 종이에 무한히 적어야 한다. 컴퓨터의 자원은 유한하기 때문에 실수를 아무리 정밀하게 표현하려 해도 근사치를 사용할 수밖에 없다.

그러면 프로그래밍 할 때 신경써야 할 것은 무엇일까? 1.1 - 1.0 == 0.1처럼 실수를 동등 연산자로 비교하지 말아야 한다. 0.00009 < 1.1 - 1.0 < 0.100001처럼 일정한 오차범위를 정해서 비교하는 것이 더 적절하다.

정수와 실수는 각자의 특징과 한계가 있기 때문에 대부분의 프로그래밍 언어에서 서로 다른 유형으로 구별되어 있다. 처리하려는 데이터에 올바른 유형이 정수인지 실수인지 잘 파악해야 한다.

무한대

실수 데이터에는 양의 무한대와 음의 무한대가 있다. 양의 무한대와 음의 무한대를 나타내는 데이터는 각각 float('inf')float('-inf')라는 표현을 이용해 만든다. 무한대는 다른 모든 수보다 크거나, 다른 모든 수보다 작은 수를 표현해야 할 때 사용할 수 있다.

코드 4-8 무한대

>>> float('inf')          # 양의 무한대
inf

>>> float('-inf')         # 음의 무한대
-inf

>>> 1e100 < float('inf')  # 무한대는 다른 모든 실수보다 크다
True

>>> 1e309                 # 너무 큰 실수는 무한대로 평가된다
inf

>>> 1e-324                # 너무 0에 가까운 실수는 0으로 평가된다
0.0

실수를 나타내는 데 사용하한 용량이 제한되어 있으므로, 너무 큰 실수는 무한대로 평가되어 버리고 너무 0에 가까운 수(무한소)는 0으로 평가되어 버린다. 곤란한 일이라고 생각할 수도 있겠지만 일반적인 프로그래밍 상황에서 이런 수를 취급할 일은 거의 없다고 봐도 무방하다. 관측할 수 있는 우주에 존재하는 모든 수소 원자의 수가 1e81개 이하라고 한다.

4.2.3 복소수

파이썬의 수치 데이터 유형에는 앞에서 설명한 정수와 부동소수점 수 외에 복소수(complex number, 줄여서 complex)가 있다. 복소수는 고등학교 수학에서 등장한다. 아직 복소수를 배우지 않았다면 이 절의 내용은 넘어가도 된다.

수학의 표기법에서 복소수는 ‘1+2i’와 같이 실수부와 허수부를 나누어 표기한다. 이 때 ‘i’는 제곱해서 -1이 되는 가상의 상수이며 복소수의 허수부를 나타내기 위한 기호로 사용된다.

파이썬에서 복소수를 표현할 때는 1+2j처럼 i만 j로 바꾸어 표기하면 된다.

코드 4-9 복소수의 표현과 연산

>>> 1-2j         # 실수부가 1, 허수부가 -2인 복소수
(1-2j)

>>> (1-2j).real  # 복소수의 실수부
1.0

>>> (1-2j).imag  # 복소수의 허수부
-2.0

복소수에 .real을 계산하면 실수부를, .imag를 계산하면 허수부를 구할 수 있다. 단, 소수점과 구별하기 위해 수를 괄호로 감싸주어야 한다.

4.2.4 수의 연산

데이터는 유형에 따라 가능한 연산이 정해져 있다. 정수, 실수, 복소수는 데이터 유형이 서로 다르지만, 넓은 의미에서 모두 수이기 때문에 연산을 서로 공유한다.

수로 할 수 있는 연산은 대부분 2장에서 살펴보았다. 수는 사칙연산, 거듭제곱, 몫과 나머지 계산, 반올림 계산, 절대값 계산 등이 가능하다. 그리고 방금 배운 복소수의 실수부와 허수부를 구하는 연산도 있다. 이 연산들은 정수, 실수, 복소수 모두 공통적으로 사용 가능하다. 심지어 정수를 반올림하거나 정수의 실수부를 구하는 것도 할 수 있다. 다음 예제를 보자.

코드 4-10 수 유형을 위한 연산은 통용된다

>>> round(10)  # 실수와 마찬가지로 정수도 반올림이 된다
10 

>>> (10).real  # 정수에서 복소수의 실수부를 구하는 연산도 가능하다
10

정수를 반올림하거나 정수의 실수부를 구하는 연산은 쓸모없어 보일 수 있다. 하지만 정수는 실수에 포함되고 실수는 복소수에 포함되므로 수를 위한 연산이 공통적으로 가능한 것은 논리적으로 타당하다. 어떤 변수에 저장된 수가 정수인지 실수인지 복소수인지 모른 채 연산을 해야 한다면 이런 특성이 유용할 것이다.

정수와 실수를 더하거나 실수와 복소수를 곱하는 등 서로 다른 유형의 수를 함께 계산하는 것도 가능하다. 이 때 연산 결과는 더 넓은 범위의 수 유형으로 계산된다. 정수와 실수를 계산하면 실수가 되고, 실수와 복소수를 계산하면 복소수가 된다.

코드 4-11 서로 다른 유형의 수를 함께 연산하기

>>> 10 + 0.5    # 정수와 실수의 연산 -> 실수
10.5

>>> 0.5 * 1+2j  # 실수와 복소수의 연산 -> 복소수
(0.5+2j)

그렇다면 수 데이터에 적용할 수 없는 연산에는 무엇이 있을까? 아직 소개한 적은 없지만 곧 배우게 될 텍스트 데이터의 대문자 변경 연산(upper())을 수에 적용해 보자.

코드 4-12 수에 적용할 수 없는 연산의 예

>>> (10).upper()  # 텍스트를 위한 연산은 오류를 발생시킨다
AttributeError: 'int' object has no attribute 'upper'

(10).upper()10이라는 수를 대문자로 바꾸는 연산이다. 수를 대문자로 바꾼다는 것은 논리적으로도 말이 안 되고, 수 데이터 유형에서 이 연산을 지원하지도 않는다. 그래서 오류가 발생한 것이다.

연습문제

연습문제 4-1 프로그래머의 나이 표기

프로그래머 세 사람이 자신의 나이를 말하고 있다.

프로그래머 A: “저는 0x7d0”년에 태어났습니다. 올해로 0x12살이 되었네요.” 프로그래머 B: “그러시군요. 저는 올해 0o22세입니다.” 프로그래머 C: “저는 18살입니다.”

세 사람의 출생년은 각각 언제인가?

힌트: 대화식 셸에 팔진수 또는 십육진수를 입력하면 십진수로 출력된다.

연습문제 4-2 과학 표기법

-252.87을 과학 표기법으로 나타내 보아라.

연습문제 4-3 융통성 있는 실수 비교 함수

두 실수가 거의 같은지 검사하는 함수 almost_equal()을 정의하라. 이 함수는 실수 두 개를 입력받아 두 실수의 차이(오차허용범위)가 0.0001 미만이면 True를 그렇지 않으면 False를 반환한다. 또, 할 수 있다면 함수를 호출할 때 오차허용범위를 지정하도록 정의해 보라.