round(4.015, 2)를 계산했는데 4.02가 아니라 4.01이 나오면, 마지막 자리 5만 보고 반올림 규칙이 틀렸다고 느끼기 쉬워요. 여기서는 float에 담긴 실제 값이 정확한 4.015보다 조금 작다는 점을 먼저 확인해야 해요. 중간값을 어느 쪽으로 보낼지 정하는 규칙과, 그 중간값이 실제로 입력됐는지는 따로 볼 문제예요.
같은 화면의 숫자라도 문자열에서 바로 만든 Decimal인지, 이미 float를 거친 값인지에 따라 결과가 달라져요. 자체 숫자 몇 개로 두 차이를 나누어 확인해 볼게요. 아래 코드는 Python 3.12.14에서 실행했어요.
정확히 가운데라면 짝수 쪽을 택해요
for value in (3.5, 4.5, -3.5, -4.5):
print(f"{value}: {round(value)}")
3.5: 4
4.5: 4
-3.5: -4
-4.5: -4
3.5는 3과 4의 중간, 4.5는 4와 5의 중간이에요. 기본 숫자형의 round는 두 후보가 똑같이 가까우면 짝수인 쪽을 택해요. 그래서 두 값 모두 4가 돼요. 무조건 작은 쪽으로 버리거나 항상 0에서 멀어지는 쪽으로 가는 규칙은 아니에요.
이 네 값은 .5를 정확히 표현할 수 있어서 중간값 규칙을 보기 좋아요. 그렇다면 4.015를 소수 둘째 자리로 줄일 때도 정확히 중간일까요? 이번에는 값이 만들어지는 단계부터 따라가 보세요.
Decimal로 옮겨도 float의 입력값은 그대로 따라와요
from decimal import Decimal, ROUND_HALF_UP
value = 4.015
print(round(value, 2))
print(Decimal(value))
print(Decimal(value).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))
4.01
4.01499999999999968025576890795491635799407958984375
4.01
두 번째 줄은 float에 들어 있던 값을 Decimal로 정확히 옮겨 본 결과예요. 4.015보다 아주 조금 작죠. 소수 둘째 자리에서 고를 두 후보 4.01과 4.02 가운데 4.01 쪽이 더 가까워요. 이 경우를 정확한 중간값에서 짝수를 택한 결과라고만 설명하면 원인을 놓쳐요.
마지막 줄은 ROUND_HALF_UP을 지정해도 4.01이에요. 이 모드는 가까운 값으로 반올림하되 정확히 중간이면 0에서 멀어지는 쪽을 택해요. 현재 입력은 그 중간보다 조금 아래에 있으니, 모드만 바꿔도 원래 10진 문자열의 값이 되지는 않아요.
원래 10진 문자열이 있다면 그 입력에서 시작해요
from decimal import Decimal, ROUND_HALF_EVEN, ROUND_HALF_UP
unit = Decimal("0.01")
for text in ("4.015", "4.025", "-4.025"):
value = Decimal(text)
even = value.quantize(unit, rounding=ROUND_HALF_EVEN)
half_up = value.quantize(unit, rounding=ROUND_HALF_UP)
print(text, even, half_up)
4.015 4.02 4.02
4.025 4.02 4.03
-4.025 -4.02 -4.03
출력은 원래 문자열, HALF_EVEN 결과, HALF_UP 결과 순서예요. 이번에는 문자열 4.025를 그대로 Decimal에 넣었으므로 소수 둘째 자리의 두 후보 4.02와 4.03 사이를 정확히 표현해요. 모드가 서로 다른 선택을 하는 이유를 비교할 수 있어요.
음수 줄도 같이 보세요. HALF_UP의 UP은 숫자가 항상 커진다는 뜻이 아니에요. 정확히 중간일 때 0에서 멀어지므로 -4.025는 -4.03이 돼요. 양수 한 건만 맞춰 보고 반올림 방식을 결정하지 않는 이유예요.
| 4.015를 만드는 경로 | 소수 둘째 자리 결과 | 이 예제에서 볼 점 |
| float에 넣고 round | 4.01 | 표시된 소수와 실제 저장값 차이 |
| float를 Decimal로 옮겨 HALF_UP | 4.01 | float 입력값을 정확히 이어받음 |
| 문자열을 Decimal로 만들어 HALF_UP | 4.02 | 원래 10진 입력에서 시작함 |
반올림 전 입력과 반올림 시점을 같이 남겨요
작업 중 숫자가 달라지면 원래 받은 문자열, 최초 숫자 변환, 계산 결과, 마지막 자리 조정을 나란히 적어 보세요. 화면에 표시하는 단계만 바꾼 것인지, 실제 저장할 값까지 바꾸려는 것인지도 구분해 두면 원인을 찾기 쉬워요.
금액이나 측정값의 처리는 업무에서 정한 단위와 모드를 따라야 해요. 이 예제가 HALF_UP을 모든 회계·세금 계산의 정답으로 정해 주지는 않아요. 항목마다 먼저 반올림할지, 합계를 낸 뒤 한 번 반올림할지도 별도 계약이에요.
또 이미 float로 바뀐 값에 str을 붙인다고 잃어버린 원문이 반드시 복구되지는 않아요. 필요한 것은 보기 좋은 문자열을 나중에 만드는 일이 아니라, 실제로 받은 10진 문자열을 필요한 단계까지 보존하는 일이에요. 4.015가 4.01로 보일 때는 5를 올리는 코드부터 덧붙이기 전에 그 입력 경로부터 확인해 보세요.
참고 자료 · 2026-10-28 확인
'IT·개발' 카테고리의 다른 글
| Python lstrip으로 접두어 지웠는데 숫자까지 사라져요: removeprefix의 차이 (0) | 2026.10.29 |
|---|---|
| map(parseInt)에서 12가 1이 됐어요: 배열 인덱스가 진법으로 들어가요 (0) | 2026.10.28 |
| URLSearchParams에서 +가 공백이 돼요: 문자열 조립과 set의 입력이 달라요 (0) | 2026.10.28 |
| JSON.parse 뒤 주문번호 끝자리가 바뀌어요: 큰 정수 ID는 문자열 계약부터 확인하세요 (0) | 2026.10.27 |
| Promise.all이 실패했는데 다른 작업은 계속돼요: catch와 취소는 달라요 (0) | 2026.10.27 |