Skip to content
This page has been auto-translated and may contain errors.View in English

숫자와 산술 연산

숫자는 거의 모든 프로그램에서 등장합니다. 장바구니에서 가격을 합산하고, 게임에서 점수를 갱신하며, 스크립트로 어떤 일이 몇 번 일어났는지 셉니다. Python은 종이에 계산하듯 작동하는 산술 연산자를 제공하며, 처음부터 알아둘 가치가 있는 몇 가지가 더 있습니다.

Python의 산술 연산자는 표준 연산자에 정수 나눗셈, 모듈로, 거듭제곱을 더한 형태입니다. 일부 동작은 다른 언어와 다르며 실무에서 중요한 차이가 있습니다: /는 항상 float를 반환하고, 정수 나눗셈은 음의 무한대 방향으로 내림되며, 모듈로는 진짜 모듈로 의미론을 따릅니다.

Python의 수치 체계: int (임의 정밀도), float (IEEE 754 binary64), complex (여기서는 다루지 않음). 산술 연산자는 C 관례보다 수학적 정의를 따릅니다: //는 내림 나눗셈(음의 무한대 방향), %는 제수의 부호를 따르며, 둘 다 함께 모든 정수 입력에 대해 항등식 a == (a // b) * b + (a % b)를 만족합니다.

연산자

수학의 네 가지 연산자(+, -, *, /)는 기대하는 그대로 작동합니다. Python은 여러분이 끊임없이 사용하게 될 세 가지를 추가합니다: 정수 나눗셈, 나머지, 거듭제곱.

표준 네 가지 연산자는 예상대로 작동하지만 한 가지 주목할 규칙이 있습니다: /는 결과가 정수일 때도 항상 float를 반환합니다. 추가된 세 가지 연산자는 별도 작업 없이 표현할 수 있는 것을 확장해 줍니다.

일곱 가지 연산자는 모두 던더 메서드로 매핑됩니다: +__add__, //__floordiv__, %__mod__, **__pow__ 등입니다. int/float 혼합 연산은 float로 확장됩니다. /는 피연산자 타입과 무관하게 항상 float를 반환합니다.

python
price    = 12.99
quantity = 3

print(price * quantity)   # 38.97
print(price + 2)          # 14.99
print(price - 1.00)       # 11.99
연산자이름예시결과
+덧셈5 + 38
-뺄셈5 - 32
*곱셈5 * 315
/나눗셈5 / 31.6666...
//정수 나눗셈5 // 31
%나머지5 % 32
**거듭제곱5 ** 3125

나눗셈: ///

/는 결과가 정수라도 항상 정확한 소수 결과를 반환합니다. //는 정수 부분만 반환하고 소수점 이하는 잘라냅니다. 반올림이 아니라 잘라내기입니다:

/는 입력이 정수든 아니든 항상 float를 반환합니다. //는 결과의 내림값, 즉 실제 결과 이하의 가장 큰 정수를 반환합니다. 양수의 경우는 단순 절단과 같지만, 음수의 경우는 다릅니다:

/는 참 나눗셈으로 항상 float를 반환합니다. //는 내림 나눗셈으로, 참 몫에 math.floor()를 적용하여 0이 아닌 음의 무한대 방향으로 항상 내림합니다. 이는 정수 나눗셈이 0 방향으로 절단되는 C와 Java와 다릅니다. 수학적 이점: Python의 //%는 음수를 포함한 모든 정수 입력에 대해 항등식 a == (a // b) * b + (a % b)를 만족합니다. C의 절단 나눗셈은 음수에 대해 이 항등식을 깨뜨립니다.

python
10 / 2     # 5.0   (정확히 나누어 떨어져도 항상 float)
10 / 3     # 3.3333333333333335

10 // 3    # 3
7  // 2    # 3
-7 // 2    # -4    (0이 아닌 음의 무한대 방향으로 내림)

-7 // 2 결과는 사람들을 놀라게 합니다. 보통은 음수가 나오지 않는 양수 상황에서 //를 사용하게 됩니다. 음수가 등장할 때를 위해 머릿속에 두기만 하면 됩니다.

Python에서는 이를 **내림 나눗셈(floor division)**이라고 부르는데, 수학적 내림 함수를 적용하기 때문입니다. 다른 언어들은 0 방향으로 절단하여 음수에 대해 다른 결과를 냅니다. //라는 이름이 힌트입니다: 나누고, 그다음 내림.

//는 절단이 아니라 floor(a / b)를 구현합니다. Python에서는 항등식 a == (a // b) * b + (a % b)가 모든 정수 입력에 대해 성립합니다. /가 0 방향으로 절단되는 C와 Java에서는 이 항등식이 음수 값에 대해 실패하며, %는 진짜 모듈로(제수의 부호를 따름)가 아니라 나머지(피제수의 부호를 따름)로 작동합니다.

나머지 연산자 %

%는 정수 나눗셈 후 남은 값을 반환합니다. 10 // 33이면(3이 10에 세 번 들어가므로), 10 % 31입니다(3 × 3 = 9, 10 - 9 = 1). 가장 흔한 용도는 숫자가 짝수인지 홀수인지 확인하는 것입니다.

%는 모듈로 연산자입니다. 짝수/홀수 확인이 가장 명백한 용도이지만, 모든 순환이나 감싸기 문제로 일반화됩니다: 범위 내에서 카운터 유지, 그룹 간 항목 분배, 시퀀스 반복. 패턴은 항상 value % limit이며, 0limit - 1 사이의 값을 반환합니다.

Python의 %는 진짜 모듈로입니다: 결과는 항상 제수의 부호를 따릅니다. 이는 %가 나머지 연산자이고 피제수의 부호를 따르는 C와 Java와 다릅니다. Python에서는 -7 % 3-1이 아닌 2인데, 이는 모듈로가 a - (a // b) * b로 정의되고 //가 음의 무한대 방향으로 내림되기 때문입니다. 이러한 일관된 부호 동작 덕분에 %는 음수 입력에 대한 순환과 감싸기에 신뢰할 수 있습니다.

python
10 % 3    # 1
10 % 2    # 0  (정확히 나누어 떨어짐)
10 % 7    # 3

6 % 2     # 0  (짝수)
7 % 2     # 1  (홀수)

거듭제곱 **

**는 숫자를 거듭제곱합니다. 별표 두 개를 사용하며, ^ 기호(Python에서 다른 의미)는 사용하지 않습니다:

**는 거듭제곱입니다. float와도 작동하므로, 별도 함수 호출 대신 분수 지수로 거듭제곱근을 표현할 수 있습니다:

**__pow__를 호출합니다. 두 int 피연산자에는 int를 반환하고, float 피연산자가 하나라도 있으면 float를 반환합니다. 한 가지 우선순위 함정: -2 ** 2**가 단항 마이너스보다 더 강하게 결합하기 때문에 -(2 ** 2)로 파싱되어 4가 아닌 -4를 반환합니다. 괄호를 사용하세요: (-2) ** 2.

python
2 ** 10    # 1024
3 ** 3     # 27
9 ** 0.5   # 3.0  (제곱근: 0.5 지수로 거듭제곱)

연산자 우선순위

Python은 표준 수학 순서를 따릅니다: 먼저 거듭제곱, 그다음 곱셈과 나눗셈, 그다음 덧셈과 뺄셈. 확실하지 않을 때는 괄호를 사용하세요. 의도를 명확하게 만들어 주고 비용도 없습니다:

Python은 표준 PEMDAS/BODMAS 순서를 따릅니다. 사람들이 걸려 넘어지는 부분은: /, //, %는 모두 같은 우선순위 수준을 공유하며 혼합되어 있을 때 왼쪽에서 오른쪽으로 평가됩니다. 괄호는 무료입니다; 한눈에 순서가 명확하지 않을 때마다 사용하세요:

산술 연산자 중 우선순위는 높음에서 낮음 순으로: **, 그다음 단항 -, 그다음 * / // %(같은 우선순위에서 왼쪽에서 오른쪽), 그다음 + -입니다. 단항 마이너스와 **의 상호작용은 미묘한 함정입니다: -2 ** 2는 단항 마이너스가 **보다 낮게 결합되기 때문에 -(2 ** 2) = -4입니다. 음수화와 거듭제곱을 결합할 때는 항상 괄호를 쓰세요.

python
2 + 3 * 4      # 14, 20이 아님
2 ** 3 + 1     # 9,  512가 아님
10 - 4 / 2     # 8.0, 3.0이 아님

(2 + 3) * 4    # 20
10 / (2 + 3)   # 2.0

intfloat의 상호작용

Python에는 일관된 규칙이 있습니다: /는 항상 소수를 반환하고(4 / 22.0을 반환), 정수와 소수를 혼합한 연산은 소수를 반환합니다. 정수가 필요할 때는 //를 사용하거나 int()로 변환하세요.

타입 규칙은 예측 가능합니다: /는 항상 float를 반환합니다. 두 정수에 대한 //%int를 반환합니다. intfloat를 혼합한 모든 연산은 float를 반환합니다. 즉, 4 / 22가 아닌 2.0이며, 이는 정수가 필요할 때(예: 인덱스로 사용할 때) 중요합니다.

타입 강제 변환은 고정된 계층을 따릅니다: 혼합 연산에서 intfloat로 확장됩니다. /__truediv__로 매핑되며 항상 float를 반환합니다. //__floordiv__로 매핑됩니다: 두 int 피연산자에는 int를 반환하고, float 피연산자가 하나라도 있으면 float를 반환합니다. 이러한 규칙들은 일관되고 예측 가능합니다; 유일한 놀라움은 /4 / 2에 대해서도 결코 int를 반환하지 않는다는 점입니다.

python
4 / 2      # 2.0   (항상 float)
4 // 2     # 2     (int)
4 + 2      # 6     (int)
4 + 2.0    # 6.0   (float)
4 * 0.5    # 2.0   (float)

Float 정밀도

거의 모든 사람을 어느 시점에 놀라게 하는 함정이 있습니다:

python
0.1 + 0.2   # 0.30000000000000004

이 미세한 오차는 Python 버그가 아닙니다. 컴퓨터는 소수를 이진수로 저장하는데, 0.1 같은 일부 값은 정확하게 표현할 수 없습니다. 1/3을 정확한 소수로 쓸 수 없는 것과 비슷합니다. 대부분의 일상적인 계산에서는 문제가 되지 않습니다. 돈을 표시할 때는 round():.2f 형식 지정자로 출력을 깔끔하게 유지할 수 있습니다.

Python의 float는 IEEE 754 binary64입니다: 64비트로 약 15-16자리 유효 십진수의 정밀도. 어떤 분수는 이진수로 정확하게 표현될 수 없기 때문에 부정확성이 드러납니다. 0.1 + 0.20.30000000000000004를 생성합니다. 이 편차는 원시 값을 검사할 때만 나타납니다; :.2fround()로 포맷하면 출력에서는 숨겨집니다.

센트 단위의 분수가 누적되는 금융 작업의 경우, Python은 표준 라이브러리에서 정확한 십진수 산술을 제공하는 decimal.Decimal을 제공합니다. 모듈 장에서 다룹니다.

float는 IEEE 754 binary64입니다: 53비트 가수로 부호 × 가수 × 2^지수이며, 상대 정밀도는 2^-52 ≈ 2.2e-16입니다. 분모에 2 이외의 소수 인수를 갖는 분수(예: 1/10 = 1/(2×5))는 무한 이진 분수이며 정확하게 저장될 수 없습니다. 오차는 작지만 반복적인 산술에서 누적됩니다.

정확한 십진수 산술을 위해 Python의 decimal.Decimal은 내부적으로 임의 정밀도 십진법을 사용합니다. 반올림이 전혀 없는 정확한 유리수 산술을 위해서는 fractions.Fraction이 분자/분모 쌍을 저장합니다. 둘 다 표준 라이브러리에 있으며, 모듈 장에서 다룹니다.

가독성 좋은 숫자 리터럴

Python은 큰 숫자를 읽기 쉽게 하기 위해 숫자 리터럴에 밑줄을 넣을 수 있습니다. Python은 이를 완전히 무시합니다; 단지 여러분을 위한 것입니다:

밑줄은 숫자 리터럴 어디에나 유효하며 파싱 중에 제거되어 값에 영향을 주지 않습니다. 상수의 천 단위 구분자, 그리고 이진수나 16진수 리터럴에서 자릿수 그룹화에 유용합니다:

숫자 리터럴의 밑줄은 토크나이저 기능입니다: 어휘 분석 중에 제거되어 결과 값에 영향을 주지 않습니다. 정수, float, 진수 리터럴(0xFF_FF, 0b1010_0001, 1_234.567_890)에서 유효합니다. 유일한 제한 사항: 시작, 끝, 또는 소수점이나 지수 표시자에 인접하여 나타날 수 없습니다.

python
population  = 8_100_000_000
distance_km = 384_400
pi_approx   = 3.141_592_653

유용한 내장 함수

abs()

abs()는 절대값을 반환합니다: 입력의 부호와 관계없이 항상 양수입니다. 어느 방향이 아니라 숫자가 0에서 얼마나 떨어져 있는지 신경 쓸 때 사용하세요.

abs()는 숫자의 크기를 반환합니다. 정수와 float 모두에서 작동합니다. 거리 계산, 오차 한계, 그리고 방향이 무관하고 값의 크기만 필요한 모든 상황에서 유용합니다.

abs()는 피연산자에 대해 __abs__를 호출합니다. intfloat에 대해서는 같은 타입을 반환합니다. complex에 대해서는 크기(원점으로부터의 유클리드 거리)를 float로 반환합니다. 실수의 경우 반환 타입은 입력 타입과 일치합니다.

python
abs(-5)     # 5
abs(3.7)    # 3.7
abs(-0.5)   # 0.5

round()

round()는 기본적으로 가장 가까운 정수로 반올림합니다. 특정 소수점 자릿수를 유지하려면 두 번째 인수를 전달하세요:

python
round(3.7)          # 4
round(3.2)          # 3
round(3.14159, 2)   # 3.14

알아둘 만한 한 가지: round(2.5)3이 아닌 2를 반환합니다. Python은 값이 두 옵션의 정확히 중간일 때 가장 가까운 짝수로 반올림합니다.

round()는 **은행원 반올림(banker's rounding)**을 사용합니다: 값이 정확히 중간일 때, 항상 올림하는 대신 가장 가까운 짝수로 반올림합니다. 이는 통계 작업에서 누적 오차를 최소화하지만, 0.5가 항상 올림될 것으로 예상하면 놀랄 수 있습니다:

python
round(2.5)   # 2  (가장 가까운 짝수로 반올림)
round(3.5)   # 4
round(4.5)   # 4  (5가 아님)
round(3.14159, 2)   # 3.14

round()는 IEEE 754 round-half-to-even(은행원 반올림)을 구현합니다: 중간값은 가장 가까운 짝수 정수로 반올림됩니다. 이는 "round half up" 관례와 다릅니다. ndigits 인수가 있으면 round()는 객체에 대해 __round__를 호출합니다; 사용자 정의 타입은 반올림 동작을 재정의할 수 있습니다. 참고: float는 정확하지 않기 때문에 round(2.5) 같은 "중간값"이 실제로는 이진수에서 정확히 0.5에 도달하지 않을 수 있어, 일관성 없어 보이는 결과를 낼 수 있습니다.

python
round(2.5)   # 2
round(3.5)   # 4
round(4.5)   # 4

divmod()

divmod()는 한 번의 호출로 몫과 나머지를 모두 반환합니다. 두 이름에 한 번에 할당할 수 있는 값 쌍을 반환합니다:

divmod(a, b)(a // b, a % b)와 동등하지만 한 단계로 계산됩니다. 어차피 두 값이 모두 필요할 때 사용하세요: 페이지네이션, 시간 변환, 또는 항목을 그룹으로 분배할 때.

divmod()는 왼쪽 피연산자에 대해 __divmod__를 호출합니다. 나눗셈을 한 번 수행하고 내림 몫과 모듈로 나머지를 모두 반환하여 //%를 별도로 호출하는 중복 계산을 피합니다. 결과는 Python의 내림 나눗셈 의미론으로 모든 정수 입력에 대해 a == divmod(a, b)[0] * b + divmod(a, b)[1]을 만족합니다.

python
divmod(10, 3)   # (3, 1): 몫 3, 나머지 1
divmod(7, 2)    # (3, 1)
divmod(9, 3)    # (3, 0)

quotient, remainder = divmod(10, 3)
print(quotient)    # 3
print(remainder)   # 1

(3, 1)이라는 건 뭔가요?

이는 **튜플(tuple)**입니다: 함께 반환되는 고정된 값 쌍. 튜플은 별도 장에서 다룹니다. 지금은 위에 보인 것처럼 두 이름에 한 번에 할당하여 두 값을 분리하면 됩니다.

실전에서

팁 계산기:

python
bill     = 45.50
tip_rate = 0.18
tip      = round(bill * tip_rate, 2)
total    = round(bill + tip, 2)

print(f"Bill:  ${bill}")
print(f"Tip:   ${tip}")
print(f"Total: ${total}")

round()는 긴 소수점 자릿수 대신 출력이 돈처럼 보이게 유지합니다.

페이지네이션을 위한 페이지 수 계산과 진행률을 백분율로 추적:

python
total_items    = 153
items_per_page = 10

full_pages, leftover = divmod(total_items, items_per_page)
total_pages = (total_items + items_per_page - 1) // items_per_page

print(f"Full pages: {full_pages}, leftover: {leftover}")
print(f"Total pages needed: {total_pages}")   # 16
python
total_files     = 847
processed_files = 312

percent = round(processed_files / total_files * 100, 1)
print(f"Progress: {processed_files}/{total_files} ({percent}%)")

올림 나눗셈 공식 (n + d - 1) // d는 float로 변환하지 않고 올림하기 위한 표준 정수 트릭입니다.

최소-최대 정규화와 백분율 변화: 데이터 작업에서 끊임없이 나타나는 두 가지 패턴:

python
# 최소-최대 정규화: 값을 0.0에서 1.0 범위로 스케일링
value   = 75
minimum = 0
maximum = 100

normalised = (value - minimum) / (maximum - minimum)
print(f"Normalised: {normalised:.2f}")   # 0.75

# 두 측정값 사이의 백분율 변화
before = 1_200
after  = 1_380

change = (after - before) / before * 100
print(f"Change: {change:.1f}%")          # 15.0%

두 패턴 모두 비율로 환원됩니다: 기준 범위 또는 기준 크기에 대한 상대적인 값. float의 정밀도는 대부분의 분석 작업에 충분합니다; 누적 오차는 계산이 수십 개의 연산을 연쇄하거나 자릿수 차이가 매우 큰 값들을 다룰 때만 중요합니다.