- Published on
파이썬 몫과 나머지 제대로 쓰기 // 와 % 연산자 실전 가이드
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
파이썬에서 몫과 나머지를 구하는 //(몫, floor division)와 %(나머지, modulo)는 너무 기본이라 대충 알고 넘어가기 쉽습니다. 그런데 막상 음수 값이 섞이거나, float가 들어오거나, 인덱스/주기 계산처럼 실무 로직에 들어가면 “왜 결과가 이렇지?” 하는 순간이 자주 옵니다.
이 글에서는 //와 %의 정확한 정의부터, 파이썬 특유의 음수 처리 규칙, divmod() 활용, 실전 패턴(페이지네이션/시간 변환/원형 인덱스), 그리고 자주 만나는 트러블슈팅까지 한 번에 정리합니다.
1) // 와 % 는 무엇을 보장하나
파이썬에서 a // b는 몫을 내림(floor) 한 값이고, a % b는 나머지입니다. 중요한 건 이 둘이 항상 아래 관계를 만족하도록 정의되어 있다는 점입니다.
a == (a // b) * b + (a % b)
즉, //와 %는 한 쌍으로 움직입니다.
a, b = 17, 5
q = a // b
r = a % b
print(q, r) # 3 2
print(q * b + r) # 17
나머지의 부호 규칙
파이썬에서 %의 결과는 항상 나누는 수(b)의 부호를 따라갑니다.
b > 0이면a % b는0이상b미만b < 0이면a % b는b초과0이하
이 규칙이 음수에서의 “의외의 결과”를 만들어냅니다.
2) 음수에서 결과가 달라지는 이유
많은 언어/환경에서 정수 나눗셈은 “0으로 절삭(truncation)”처럼 보이기도 합니다. 하지만 파이썬의 //는 내림(floor) 입니다.
print(7 // 3) # 2
print(-7 // 3) # -3 (내림이므로 -2가 아니라 -3)
print(7 % 3) # 1
print(-7 % 3) # 2
왜 -7 % 3이 2일까요? 위의 항등식을 맞추기 위해서입니다.
-7 == (-3) * 3 + 2
즉, 파이썬은 //와 %를 서로 일관되게 정의합니다.
음수 모듈로가 필요한 실전 이유: 원형 인덱스
리스트에서 뒤로 한 칸 이동 같은 로직을 만들 때, 음수 인덱스가 섞여도 %가 안전하게 정규화해줍니다.
items = ["A", "B", "C", "D"]
idx = 0
idx = (idx - 1) % len(items)
print(idx, items[idx]) # 3 D
이 패턴은 원형 버퍼, 라운드 로빈, 주기적 스케줄링에서 매우 자주 등장합니다.
관련해서 인덱스를 다루는 기본기를 더 다지고 싶다면, 파이썬 리스트 슬라이싱 기초 시작 끝 간격으로 데이터 다루기도 함께 보면 좋습니다.
3) divmod()로 몫과 나머지를 한 번에
divmod(a, b)는 (a // b, a % b)를 동시에 반환합니다. 반복 계산을 줄이고 의도를 더 명확하게 표현할 수 있습니다.
q, r = divmod(17, 5)
print(q, r) # 3 2
실전 예: 초를 분/초로 변환
total_seconds = 367
minutes, seconds = divmod(total_seconds, 60)
print(minutes, seconds) # 6 7
divmod()는 시간 변환, 페이지네이션 계산, 좌표 변환(2D 인덱스) 등에서 특히 깔끔합니다.
4) 자주 쓰는 실전 패턴 5가지
4-1) 페이지네이션: offset 계산
페이지 번호가 1부터 시작한다고 하면:
page = 3
page_size = 20
offset = (page - 1) * page_size
print(offset) # 40
여기서 “몇 페이지인지”를 반대로 구할 때 //가 핵심입니다.
index = 40
page = index // page_size + 1
print(page) # 3
4-2) 짝수/홀수 판별
n = 42
if n % 2 == 0:
print("even")
else:
print("odd")
4-3) N개마다 끊기(배치 처리)
예: 10개씩 묶어서 처리할 때, 그룹 번호를 만들 수 있습니다.
batch_size = 10
for i in range(35):
batch_id = i // batch_size
# i=0~9 -> 0, 10~19 -> 1 ...
4-4) 1차원 인덱스를 2차원 좌표로
행렬을 1차원으로 펼쳤다가 다시 좌표로 복원할 때:
width = 5
index = 17
row, col = divmod(index, width)
print(row, col) # 3 2
4-5) 주기적 이벤트(매 N번째)
N = 7
for day in range(1, 31):
if day % N == 0:
print("weekly event on day", day)
5) 트러블슈팅: float, 음수, 0 나누기
5-1) float에 // 쓰면 결과도 float가 될 수 있음
print(5 // 2) # 2
print(5.0 // 2) # 2.0
정수 인덱스가 필요하다면 int() 캐스팅을 명시적으로 하거나, 애초에 정수 연산만 하도록 입력을 정규화하세요.
idx = int(5.0 // 2)
5-2) float는 이진 부동소수점 오차가 있다
print(0.3 % 0.1) # 0.09999999999999998 처럼 보일 수 있음
금액/정밀도가 중요한 값은 decimal.Decimal을 고려하거나, 정수 단위(예: 원, 센트, 밀리초)로 변환해서 %를 적용하는 것이 안전합니다.
5-3) 0으로 나누면 예외
# ZeroDivisionError
# q = 10 // 0
# r = 10 % 0
입력값 검증(guard)을 두는 게 Best Practice입니다.
def safe_divmod(a: int, b: int):
if b == 0:
raise ValueError("b must not be 0")
return divmod(a, b)
5-4) 음수 나눗셈을 ‘절삭’처럼 기대했다면
만약 어떤 로직이 “0 방향으로 절삭”을 원한다면 math.trunc(a / b) 같은 방식이 필요합니다.
import math
print(math.trunc(-7 / 3)) # -2
print(-7 // 3) # -3 (floor)
두 방식은 목적이 다르니, 요구사항에 맞게 선택해야 합니다.
6) Best Practice 정리
- 몫과 나머지는 한 쌍: 가능하면
divmod()로 의도를 드러내기 - 음수 규칙을 항상 염두:
%의 부호는 나누는 수를 따른다 - 인덱스/주기 처리에는
%가 강력:(i + k) % n패턴은 거의 정답 - float는 주의: 오차가 싫으면 정수 단위로 바꿔서 연산
- 0 나누기 방지: 입력 검증을 습관화
결론
파이썬의 //와 %는 단순 계산용 연산자가 아니라, 항등식 a = (a//b)*b + (a%b)를 항상 만족하도록 설계된 “규칙 기반 도구”입니다. 특히 음수에서 //가 내림(floor)이라는 점, %의 부호가 나누는 수를 따른다는 점을 이해하면 원형 인덱스, 주기 계산, 시간 변환 같은 실무 로직을 훨씬 안정적으로 작성할 수 있습니다.
실전에서는 divmod()를 적극 활용하고, float가 섞일 가능성이 있으면 정수 단위로 정규화하는 습관을 들이면 //와 %로 인한 버그를 크게 줄일 수 있습니다.