계산 정확도
반올림은 출력 경계에서 한 번만
항목마다 반올림하면 합계에 오차가 쌓인다. 14,900원을 3명이 나눌 때 생기는 0.33원이 10년 뒤 57원 차이를 만든다.
0.33원이 57원이 되는 과정
구독료 계산기는 여러 구독 항목을 모아 월 합계를 내고, 그 돈을 적립했을 때의 복리 결과를 보여 줍니다. 항목마다 결제 주기가 다르고 가족과 나눠 쓰는 경우도 있어서 중간 계산이 여러 단계입니다.
여기서 반올림을 어디서 할지가 문제가 됩니다. 14,900원짜리 구독을 3명이 나누면 1인당 4,966.666...원입니다. 자연스럽게 4,967원으로 올리고 싶어집니다. 화면에 소수점을 보여 줄 것도 아니니까요.
그런데 그 0.33원이 10년 복리를 거치면 이렇게 됩니다.
| 방식 | 10년 뒤 결과 |
|---|---|
| 소수를 끝까지 유지하고 출력에서만 반올림 | 854,360원 |
| 항목별 월 환산에서 미리 반올림 | 854,417원 |
57원 차이입니다. 항목이 늘고 기간이 길어질수록 벌어집니다.
규칙은 하나다
반올림은 출력 경계에서 한 번만. 중간 계산은 소수를 그대로 들고 갑니다.
당연해 보이지만 실수하기 쉽습니다. 함수를 나누다 보면 "월 환산액을 구하는 함수"가 정수를 반환하는 게 깔끔해 보이거든요. 타입을 정수로 잡고 싶은 유혹도 있습니다. 그 순간 오차가 들어갑니다.
그래서 이걸 계약 문서에 못 박았습니다. 코어의 어느 단계까지가 소수이고 어디서 정수가 되는지 명시했습니다. 구현자가 바뀌어도 규칙이 남습니다.
테스트로 잠근다
문서에 적는 것만으로는 부족합니다. 정답표에 이 차이를 직접 검사하는 케이스를 넣었습니다. 14,900원을 3명이 나누는 상황을 그대로 재현해 기대값 854,360원과 비교합니다.
누군가 나중에 "여기서 반올림하면 코드가 깔끔한데"라며 고치면 그 테스트가 깨집니다.왜 깔끔하지 않게 짰는지를 테스트가 설명해 주는 셈입니다.
표시되는 숫자끼리도 맞아야 한다
비슷하지만 다른 문제도 있었습니다. 화면에는 원금, 수익분, 미래가치 세 숫자가 나란히 나옵니다. 사용자는 당연히 원금 + 수익분 = 미래가치를 기대합니다.
수익분을 round(미래가치 − 원금)으로 구하면 세 숫자가 1원 어긋날 수 있습니다.round(미래가치) − round(원금)으로 구하면 항상 맞습니다. 수학적으로는 전자가 더 정확하지만, 화면에서는 후자가 옳습니다.사용자가 검산했을 때 맞아떨어져야 하니까요.
이것도 계약에 적었습니다. 어느 쪽이 "맞는지"는 상황에 따라 다르고, 그 판단을 코드만 보고 복원할 수는 없기 때문입니다.