점수 평균을 정수로 보여 주려고 round(2.5)를 썼는데 3이 아니라 2가 나왔습니다. 소수 둘째 자리로 맞추려던 round(2.675, 2)도 2.68이 아니라 2.67입니다. 둘 다 버그처럼 보이지만 원인이 서로 다릅니다. 이 글을 읽고 나면 두 결과가 각각 왜 나오는지 설명할 수 있고, 화면 표시·금액 계산·통계 집계마다 어떤 반올림을 써야 하는지 고를 수 있습니다.
예제는 2026-10-11에 macOS 27.0.1에서 Python 3.12.14(uv로 설치한 빌드)로 실행했고, 같은 스크립트를 macOS에 들어 있는 Python 3.9.6에서도 돌려 같은 출력을 얻었습니다. 비교용 JavaScript는 Node.js v24.21.0에서 실행했습니다. 숫자는 모두 설명을 위해 고른 예시 값입니다.
2.5는 짝수 쪽으로 간다
학교에서 배운 반올림은 0.5를 만나면 위로 올립니다. Python의 내장 round()는 다르게 정해져 있습니다. 공식 문서는 두 후보가 똑같이 가까우면 짝수인 쪽을 고른다고 적고 있고, 예로 round(0.5)와 round(-0.5)가 모두 0, round(1.5)가 2라고 듭니다. 흔히 은행가 반올림(banker's rounding), 또는 짝수 쪽 반올림(round half to even)이라고 부르는 규칙입니다.
실제로 돌려 보면 round(0.5)는 0, round(1.5)는 2, round(2.5)는 2, round(3.5)는 4, round(-2.5)는 -2였습니다. 정확히 가운데에 있는 값만 이렇게 갈리고, 2.51처럼 한쪽이 더 가까우면 평소처럼 가까운 쪽으로 가서 round(2.51)은 3이었습니다. 문자열 서식 format(2.5, '.0f')도 두 Python 버전 모두 '2'를 돌려줬습니다.
이 규칙은 Python 3.0에서 바뀐 것입니다. 3.0 변경 사항 문서는 정확히 가운데인 값이 이제 0에서 먼 쪽이 아니라 가까운 짝수로 반올림되며, round(2.5)가 3이 아니라 2를 돌려준다고 적고 있습니다. 오래된 Python 2 예제나 다른 언어의 습관대로 기대하면 여기서 어긋납니다.
개발 노트
정확히 가운데인 값은 어디로 갈까
같은 2.5와 -2.5, 세 가지 규칙
Python round()
2.5 -> 2 · -2.5 -> -2- 짝수 쪽으로
- decimal 기본값도 같음
ROUND_HALF_UP
2.5 -> 3 · -2.5 -> -3- 0에서 먼 쪽으로
- 직접 지정해야 함
JavaScript Math.round()
2.5 -> 3 · -2.5 -> -2- +∞ 쪽으로
- 음수는 0 쪽이 됨
Python 3.12.14·3.9.6과 Node.js v24.21.0에서 2026-10-11 실행. ROUND_HALF_UP은 Decimal('2.5'), Decimal('-2.5')에 quantize로 적용했습니다.
왜 굳이 짝수일까요. 가운데 값을 늘 위로 올리면 많이 모았을 때 합계가 한쪽으로 쏠립니다. 0.5, 1.5, 2.5, 3.5를 더하면 8인데, 모두 위로 올리면 1, 2, 3, 4로 합이 10이 되고, 짝수 쪽으로 보내면 0, 2, 2, 4로 합이 8로 남았습니다. 올리는 경우와 내리는 경우가 반반으로 나뉘어 치우침이 상쇄되는 것입니다. 물론 이 네 값은 상쇄가 딱 맞아떨어지도록 고른 예이고, 실제 데이터에서 합계가 늘 정확히 같아지는 것은 아닙니다.
2.675는 처음부터 2.675가 아니다
round(2.675, 2)가 2.67인 것은 짝수 규칙 때문이 아닙니다. 둘째 자리 7은 홀수이니 짝수 규칙대로라면 오히려 2.68이 되어야 합니다. 원인은 값이 저장되는 방식에 있습니다. Python의 float는 2진 부동소수점이라 2.675 같은 10진 소수 대부분을 정확히 담지 못하고, 가장 가까운 2진 값으로 바꿔 저장합니다. 공식 문서의 round() 항목도 바로 이 예를 들어 버그가 아니라고 설명합니다.
저장된 값을 그대로 보려면 Decimal(2.675)처럼 float를 Decimal로 바꿔 보면 됩니다. float에서 만든 Decimal은 2진 값을 손실 없이 10진수로 옮기기 때문입니다. 출력은 2.67499999999999982236431605997495353221893310546875였습니다. 가운데인 2.675보다 아주 조금 작으니 동점이 아니고, 더 가까운 2.67로 가는 것이 맞습니다. round(1.005, 2)가 1.0인 것도 같은 이유로, 저장된 값이 1.00499999…였습니다.
반대로 2진수로 정확히 표현되는 값은 짝수 규칙이 그대로 보입니다. 0.125와 0.375는 저장된 값도 정확히 0.125, 0.375였고, round(0.125, 2)는 짝수 쪽인 0.12, round(0.375, 2)는 0.38이 나왔습니다. f-문자열 f'{2.675:.2f}'도 같은 저장값을 다루므로 2.67을 보여 줬습니다.
개발 노트
round(2.675, 2)는 왜 2.67일까
입력한 값과 저장된 값이 다르다
입력
round(2.675, 2)- 10진 소수 2.675
- 둘째 자리에서 동점처럼 보임
float로 저장
2.674999999999999822…- 가장 가까운 2진 값
- 2.675보다 조금 작음
반올림
-> 2.67- 동점이 아니므로
- 더 가까운 2.67
저장값은 Decimal(2.675)로 확인했습니다. 2진수로 정확한 0.125는 짝수 규칙대로 round(0.125, 2) -> 0.12였습니다.

금액은 문자열로 만든 Decimal로 반올림한다
가격이나 세금처럼 10진 자릿수 그대로 계산하고 정해진 규칙으로 반올림해야 한다면 decimal 모듈을 씁니다. 다만 두 가지를 알고 있어야 합니다.
첫째, decimal의 기본 반올림도 짝수 쪽입니다. 기본 컨텍스트의 rounding은 ROUND_HALF_EVEN이었고, Decimal('2.5').quantize(Decimal('1'))은 2였습니다. 학교식 반올림을 원하면 ROUND_HALF_UP을 직접 넘겨야 하며, 그러면 3이 됩니다. 문서에 따르면 ROUND_HALF_UP은 동점일 때 0에서 먼 쪽으로 갑니다.
둘째, Decimal은 문자열에서 만들어야 합니다. Decimal('2.675').quantize(Decimal('0.01'), ROUND_HALF_UP)은 2.68이었지만, 같은 식에 float를 넣은 Decimal(2.675)는 2.67이었습니다. float를 거치는 순간 이미 2.67499…가 되어 버리기 때문입니다. 값이 이미 float로 들어왔다면 Decimal(str(x))가 짧은 표기 '2.675'를 거쳐 2.68을 돌려줬지만, 이는 float의 짧은 표기가 원래 입력과 같을 때에만 통하는 방법입니다. 가능하면 입력을 처음부터 문자열이나 정수(원·센트 단위)로 받습니다.
from decimal import Decimal, ROUND_HALF_UP
price = Decimal("2.675") # float가 아니라 문자열
print(price.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)) # 2.68
어떤 반올림이 맞는지는 코드가 정할 일이 아닙니다. 세금, 정산, 포인트 적립은 법령이나 약관, 회사 정책이 반올림 방식과 자릿수를 정해 두는 경우가 많으니 그 규칙을 확인해 rounding 인자로 명시합니다.

JavaScript에서 옮겨 왔다면
같은 값을 JavaScript에서 돌리면 또 다릅니다. Node.js에서 Math.round(2.5)는 3, Math.round(0.5)는 1, Math.round(-2.5)는 -2였습니다. MDN은 소수 부분이 정확히 0.5이면 +∞ 방향의 다음 정수로 간다고 설명합니다. 그래서 양수에서는 학교식과 같고, 음수에서는 0 쪽으로 갑니다. 반면 (2.675).toFixed(2)는 '2.67', (1.005).toFixed(2)는 '1.00'으로, 2진 저장값 문제는 JavaScript에서도 같았습니다.
정리하면 세 가지 규칙이 있습니다. Python round()와 decimal 기본값은 짝수 쪽, ROUND_HALF_UP은 0에서 먼 쪽, JavaScript Math.round()는 +∞ 쪽입니다. 두 언어로 같은 숫자를 다루는 서비스라면 서버와 화면이 다른 값을 보여 줄 수 있으니, 한쪽에서 반올림을 끝낸 값을 문자열로 넘기는 편이 안전합니다.
개발 노트
어떤 반올림을 쓸까
값의 쓰임새와 들어온 형태에 따라
화면 표시
f'{x:.2f}'- 기본 round()나 서식
- 2진 저장값 기준임을 기억
통계·집계
round(x)- 짝수 쪽 반올림이 기본
- 합계 치우침이 적음
금액·세금
Decimal('2.675').quantize(…, ROUND_HALF_UP)- 문자열로 만든 Decimal
- 정해진 규칙을 명시
JavaScript와 함께
Math.round(-2.5) -> -2- Math.round()는 +∞ 쪽
- 한쪽에서 반올림해 문자열로
금액의 반올림 방식과 자릿수는 법령·약관·정책이 정하는 경우가 많습니다. 코드는 그 규칙을 rounding 인자로 드러내면 됩니다.
코드에서 round(를 보면 두 가지를 물어보면 됩니다. 이 값이 정확히 가운데일 때 어느 쪽으로 가야 하는가, 그리고 이 값은 float로 들어왔는가. 화면 표시나 통계라면 기본 round()로 충분하고, 금액처럼 규칙이 정해진 값이라면 문자열로 만든 Decimal에 반올림 방식을 명시합니다.
이 기록에 대화를 더해 주세요.
궁금한 점, 다른 접근, 직접 해 본 결과를 나눠 주세요.
로그인 상태 확인 중…
댓글을 불러오는 중…