Cheondi
개발 · Unity

다중 통화 소수점 처리 규칙

여러 통화의 금액을 표시하면서 계산값과 표시값, 반올림 규칙을 분리해서 봐야 했던 기록입니다.

  • #unity
  • #decimal
  • #formatting

처음 금액 표시를 수정할 때는 ToString("N2")면 충분하다고 생각했다. 대부분의 예제에서 소수점 두 자리로 보여 줬고 테스트 데이터도 그럴듯하게 나왔다. 그런데 여러 통화를 사용하는 계좌를 붙이자 어떤 값은 자릿수가 부족했고, 어떤 값은 불필요한 0이 붙었다.

더 큰 문제는 화면을 맞추려고 원본 값을 먼저 반올림했을 때였다. 표시에는 문제가 없어 보였지만 합계와 상세 값이 아주 조금 달라졌다. 소수점 한 자리 차이가 단순한 디자인 문제가 아니라 계산 결과의 신뢰 문제로 이어질 수 있었다.

계산값과 표시값의 분리

당시 코드를 따라가 보면 숫자가 문자열로 바뀌었다가 다시 숫자로 변환되는 구간이 있었다. 화면 문구를 만들기 위한 포맷과 계산을 위한 값이 섞여 있으니 어디에서 정밀도가 사라졌는지 찾기 어려웠다.

// 계산에는 원본 decimal 값을 유지한다.
decimal total = rows.Sum(row => row.amount);

// 화면에 보여 줄 때만 통화 규칙을 적용한다.
string text = total.ToString($"N{currency.decimalPlaces}");

핵심은 어려운 수학이 아니었다. 원본 값은 끝까지 숫자로 유지하고, 사용자에게 보여 주는 경계에서만 문자열로 바꾸는 것이었다.

통화별 처리 규칙

모든 통화가 같은 자릿수를 쓰지 않았고, 상품에 따라 최소 변화 단위도 달랐다. 그래서 화면마다 임의로 N2, N3을 쓰는 대신 통화 정보가 포맷 규칙을 제공하도록 보는 편이 안전했다.

sealed class CurrencyRule
{
    public string Code;
    public int DecimalPlaces;
}

이렇게 단순한 규칙 객체라도 있으면 새 통화가 추가됐을 때 여러 UI 스크립트를 찾아다닐 필요가 줄었다. 어디에서 잘못된 자릿수를 정했는지도 한곳에서 확인할 수 있었다.

0값의 다양한 표현

값이 정말 0인 경우, 아직 값이 오지 않은 경우, 계산할 수 없는 경우가 화면에서는 모두 0.00으로 보이기도 했다. 사용자는 결과가 0이라고 이해하지만 실제로는 로딩 중일 수 있었다. 숫자와 상태를 따로 들고 있어야 했다.

if (!result.hasValue)
    amountText.text = "-";
else
    amountText.text = Format(result.value, rule);

null이나 별도 플래그를 어떻게 쓸지는 프로젝트마다 다르지만, 값이 없다는 상태를 임의의 숫자로 대신하지 않는다는 원칙은 기억에 남았다.

표시 규칙과 사용자 신뢰

사용자는 내부 계산 과정을 보지 못한다. 화면에 나온 숫자만 보고 결과를 판단한다. 그래서 한 자리의 반올림, 마이너스 부호, 천 단위 구분도 기능의 일부였다.

이후 숫자 표시 오류를 만났을 때 폰트나 텍스트 컴포넌트부터 보지 않는다. 원본 자료형, 계산 시점, 반올림 시점, 통화별 표시 규칙을 차례대로 확인한다. 당시에는 소수점 수정 하나가 왜 오래 걸리는지 답답했지만, 작은 값일수록 경계를 명확히 해야 한다는 걸 배운 작업이었다.

기술 문서 기반의 책임 경계

나중에 같은 증상을 다시 만나지 않으려고 문서에 적힌 기준으로 구현을 한 번 더 정리했다.

ECMA-334에서 decimal은 10진 스케일을 가진 값으로 정의된다. 0.1 같은 10진 소수를 그대로 다뤄야 하는 금액에는 이 성질이 유리하지만, 타입만 바꾼다고 통화 정책이 생기는 것은 아니다. 통화별 자릿수, 반올림 방식, 입력 단위와 전송 단위를 따로 정해야 한다.

특히 표시를 위해 반올림한 문자열을 다음 계산의 입력으로 다시 쓰면 작은 오차가 정책 오류로 바뀐다. 계산 값과 표시 값은 끝까지 분리하는 편이 낫다.

다음에 다중 통화 소수점 처리 규칙 같은 문제를 보면 아래 순서부터 확인하려고 한다.

  • 통화별 최소 단위와 표시 자릿수
  • 계산·전송·표시 값의 타입
  • 반올림 모드와 적용 시점
  • 음수·0·최대 경계값

서버와 클라이언트가 같은 반올림 모드와 최소 단위를 쓰는지 예제 값으로 맞추지 않으면 경계 금액에서 서로 다른 결과가 난다.

공식 참고 자료