56. decimal 내부 표현과 double 차이(재화·정산 정확성)
난이도 중내 리뷰 · C#
해설 · C#
해설 — decimal 내부 표현과 double 차이(재화·정산 정확성)
난이도: 중
예측 출력
True
False
1.10
True
2
3
4
요약
decimal 은 96비트 정수 계수(mantissa)와 0~28 범위의 10진 스케일(그리고 부호)로 이뤄진 10진 부동소수점이다. 값 = 계수 × 10^(−스케일). 그래서 0.1, 0.2, 0.3 같은 10진 소수를 오차 없이 표현한다. 반면 double 은 IEEE-754 2진 부동소수점이라 0.1 을 유한 2진수로 표현할 수 없어 근사값을 저장한다.
핵심 개념
- decimal 구조: 128비트 = 부호 1비트 + 스케일 8비트(하위 5비트 유효, 0~28) + 96비트 정수 계수.
decimal.GetBits로 이 네 정수(lo/mid/hi/flags)를 볼 수 있다. 유효 자릿수는 28~29 자리. - double 구조: 64비트 = 부호 1 + 지수 11 + 가수 52비트. 2진 분수의 합으로만 값을 만들 수 있어, 분모가 2의 거듭제곱이 아닌 10진 소수(0.1=1/10)는 정확히 표현 불가.
단계별 내부 동작
0.1m + 0.2m == 0.3m→ True. 세 값 모두 decimal 로 정확하고, 0.1+0.2 의 정확한 합이 0.3 과 같다.0.1 + 0.2 == 0.3(double) → False. 0.1, 0.2 의 2진 근사 오차가 누적되어 합이0.30000000000000004…가 되고, 0.3 의 근사값과 다르다. 부동소수점 상등 비교의 전형적 함정.(1.10m).ToString()→ "1.10". decimal 은 스케일을 값의 일부로 보존한다.1.10m은 계수 110, 스케일 2 로 저장되어 후행 0 이 유지된다(리터럴이 스케일을 결정).1.1m == 1.10m→ True. 상등은 스케일이 아니라 수치 값으로 비교한다. 1.1(계수 11, 스케일 1)과 1.10(계수 110, 스케일 2)은 값이 같다. 즉 ToString 은 다르지만(1.1 vs 1.10) 상등은 True — 표현과 값이 분리돼 있음을 보여준다.Math.Round(2.5m)→ 2.Math.Round의 decimal 오버로드 기본은 MidpointRounding.ToEven(은행가 반올림). 2.5 는 가장 가까운 짝수 2 로.Math.Round(2.5m, MidpointRounding.AwayFromZero)→ 3. 0에서 먼 쪽으로 반올림하면 3.Math.Round(3.5m)→ 4. ToEven 이라 3.5 는 짝수 4 로. (5→ToEven 이 짝수로 가므로 2.5→2, 3.5→4)
흔한 오해·함정
- 재화에 double 사용: 골드·현금·환율을 double 로 다루면 반복 연산에서 오차가 누적되어 잔액 불일치·판정 오류가 난다. 금액은 decimal(또는 정수 최소단위) 로.
- ToEven 을 모른 채 반올림: 기본이 은행가 반올림이라 "2.5 가 왜 2냐"에 당황한다. 정산 규칙에 맞는 MidpointRounding 을 명시해야 한다.
- 나눗셈 반올림:
1m/3m은 28~29 유효자리에서 반올림된0.3333…3이다. decimal 도 무한 10진 소수(1/3)는 정확히 못 담는다. 비율·이자 계산은 반올림 정책을 명시. - 성능/범위: decimal 산술은 하드웨어 부동소수점보다 느리고, 범위(±7.9e28)와 유효자리(28~29)가 double(±1.8e308)보다 좁다. 좌표·물리 같은 고성능·광범위 수치는 double/float 가 맞다.
면접 포인트
- "0.1+0.2 != 0.3" 이 double 에서 참인 이유(2진 근사)와, decimal 에서 거짓인 이유(10진 계수+스케일)를 구조로 설명할 수 있어야 한다.
- decimal 의 상등은 값 기준이지만 ToString/스케일은 보존된다 — 표현과 값을 분리해 이해하는 것이 포인트.
- 재화·정산은 decimal 또는 정수 최소단위, 좌표·물리는 float/double. 도메인에 맞는 수치 타입 선택이 핵심.