合計が3ウォン合わないのに、どの行も間違っていない
한국어 원문으로 표시합니다.
목표
통화마다 다른 최소단위를 표로 만들고, 수수료 원장을 정수 최소단위로 다시 적재하고, 반올림 모드를 정책으로 못박고, 나머지 1까지 맞는 배분과 환산 순서에 따른 차이를 산출물로 남긴다.
왜 중요한가
금액을 실수로 다루면 한 줄에 1씩 어긋나고, 그 정도는 단위 시험이 잡지 못한다. 합계가 안 맞는다는 문의가 들어와도 어느 줄이 틀렸는지 짚을 수 없어서 "다시 돌리면 맞는다" 는 운영 절차가 생긴다. 여기서 정하는 것은 세 가지다. 저장 형식(정수 최소단위), 끊는 자리와 반올림 모드(정책), 나머지 배분 규칙(최대잉여법). 셋 다 기술이 정해 주지 않고 사람이 정해 문서에 적어야 하는 것이다. 채점기는 여러분이 적은 숫자끼리 비교하지 않는다. 1단계에서 만든 원자료에서 기준값을 다시 계산해 대조한다. 그래서 규칙을 그대로 따르는 것이 중요하다.
단계
- /root/money/gen_money.py 를 만들어 실행해 /root/money/currencies.json, /root/money/fees.csv(600행), /root/money/splits.csv(30건)를 만든다.
- 가산율 0.125 를 실수 경로와 정수 경로로 각각 계산해 /root/money/float_gap.json 에 어긋난 줄 수와 차이를 적는다.
- /root/money/money.db 에 fee(정수 최소단위)와 fee_real(REAL) 두 표로 적재하고 /root/money/store.json 에 통화별 두 합계를 적는다.
- /root/money/money_fmt.py 에 파싱과 표시를 모으고, 안내의 입력 12개를 판정해 /root/money/format_check.json 을 만든다.
- 같은 곱셈을 ROUND_HALF_UP 과 ROUND_HALF_EVEN 으로 끊어 /root/money/rounding.json 에 갈라진 줄 수와 두 합계를 적는다.
- splits.csv 의 30건을 최대잉여법으로 배분해 /root/money/alloc.json 에 적는다. 합계는 총액과 정확히 같아야 한다.
- 안내의 환율표로 통화 5종을 원화로 환산해 /root/money/fx.json 에 줄 단위와 총액 단위 두 값을 적는다.
- 앞 단계의 결과를 /root/money/report.json 하나로 묶고 fees.csv 의 sha256 을 함께 적는다.
참고
- 1단계 생성 규칙: 통화는 KRW,JPY,USD,KWD,BHD,CLP 를 이 순서로 돌려 쓰고(row_id 를 6으로 나눈 나머지), row_id 는 0 부터 599, 최소단위 금액은
1000 + (row_id * 37) % 9001, amount_text 는 그 금액을 통화의 소수 자리로 표시한 글자다. - 1단계 배분 규칙: case_id 는 0 부터 29, 통화는 KRW,USD,KWD 를 돌려 쓰고, total_minor 는
100000 + case_id * 7777, 가중치는1 + (case_id*3) % 9,1 + (case_id*5) % 7,1 + (case_id*7) % 5이다. - 2단계 두 경로: 실수 쪽은
int(round(round(float(amount_text) * 0.125, 소수자리) * 10 ** 소수자리)), 정수 쪽은Decimal(amount_text) * Decimal("0.125")를 소수 자리에서 ROUND_HALF_UP 으로 quantize 한 뒤 최소단위로 바꾼 값이다. - 4단계 입력 12개:
KRW 1234,JPY 0,USD 0.05,KWD 0.001,BHD 12.345,CLP 99,USD 1234567.89,USD 1.234,KRW 12.5,XAU 1.0,ZZZ 5,USD 1,234.56. 거절 사유는too_many_minor_digits,unknown_minor_units,unknown_currency,not_a_number네 가지만 쓴다. - 7단계 환율표(원화 기준, 소수 넷째 자리): JPY 9.0421 · USD 1381.5027 · KWD 4512.3007 · BHD 3665.1420 · CLP 1.4508. 이 숫자는 이 실습의 가정입니다 — 실제 고시환율이 아니라 소수 넷째 자리라는 계약을 보이려고 지어낸 값입니다. 통화별 소수 자리(최소단위)만 ISO 4217 의 실제 값입니다.
- 흔한 실수: 파이썬 내장
round()가 은행가 반올림이라는 것을 잊기, 엔화에 소수점 붙이기, 배분에서 나머지 버리기, 환산 순서를 섞기. - 확인:
python3 -c "import json;print(json.load(open('/root/money/float_gap.json'))['mismatch_rows'])"
통화표와 수수료 원장 만들기
/root/money/gen_money.py 를 만들어 실행해 /root/money/currencies.json, /root/money/fees.csv(600행), /root/money/splits.csv(30건)를 만드세요.
통화표의 소수 자리는 읽기에 적힌 List One 값을 그대로 씁니다. 소수 자리가 N.A. 인 통화는 숫자가 아니니 null 입니다. amount_text 는 최소단위 정수를 글자로 만든 것이라, 0 자리 통화에는 소수점이 없습니다.
실수로 계산한 가산금이 몇 줄이나 어긋나는가
가산율 0.125 를 안내의 두 경로로 계산해 /root/money/float_gap.json 에 rate, rows, mismatch_rows, gap_minor_total, by_ccy 를 적으세요.
두 경로의 차이는 두 군데서 옵니다. 0.125 는 이진수로 정확하지만 12.345 같은 금액은 그렇지 않고, 파이썬 내장 round() 는 0.5 를 짝수 쪽으로 붙입니다. by_ccy 는 통화마다 mismatch_rows 와 gap_minor 두 값을 담은 객체입니다.
정수 최소단위로 다시 적재하기
/root/money/money.db 에 fee(row_id, ccy, minor, exponent)와 fee_real(row_id, ccy, amount REAL) 두 표를 만들어 600행씩 넣고, /root/money/store.json 에 통화별 sum_minor, real_round_minor, equal 을 적으세요.
SQLite 의 자료형은 컬럼이 아니라 값에 붙습니다. INTEGER 로 선언해도 실수를 넣으면 REAL 로 들어가니, typeof() 로 확인해 보세요. real_round_minor 는 REAL 합계를 그 통화의 소수 자리만큼 곱해 반올림한 정수입니다. equal 이 전부 true 로 나올 수도 있습니다 — 이 규모에서는 REAL 합이 우연히 같은 값으로 반올림되기 때문이고, 그래서 이런 결함이 오래 살아남습니다. 본 대로 적으세요.
파싱과 표시를 한 문으로 모으기
/root/money/money_fmt.py 에 parse 와 fmt 를 만들고, 안내의 입력 12개를 판정해 /root/money/format_check.json 에 ok 와 rejected 로 적으세요.
ok 항목에는 in, ccy, minor, round_trip 을 담습니다. round_trip 은 최소단위를 다시 글자로 만든 값이라 원래 표기와 같아야 합니다. 거절 사유는 네 가지뿐이고, 통화 자체를 모르는 것과 소수 자리를 모르는 것은 다른 사유입니다.
반올림 모드를 정책으로 못박기
같은 곱셈을 ROUND_HALF_UP 과 ROUND_HALF_EVEN 으로 끊어 /root/money/rounding.json 에 policy, rate, diff_rows, half_up_total_minor, half_even_total_minor 를 적으세요. 이 실습의 정책은 ROUND_HALF_UP 입니다.
곱셈은 한 번만 하고 quantize 를 두 번 하면 됩니다. 0.125 는 이진수로 정확한 값이라 정확히 절반인 자리가 자주 나오고, 거기서 두 모드가 갈립니다. 두 합계가 같다면 한쪽이 실제로 적용되지 않은 것입니다.
나머지 1까지 맞는 배분
splits.csv 의 30건을 최대잉여법으로 3자리에 배분해 /root/money/alloc.json 에 cases, sum_ok, remainder_total 을 적으세요.
몫은 내림으로 주고 남은 만큼만 1씩 더 줍니다. 누가 받을지는 잉여(총액 곱하기 가중치를 가중치 합으로 나눈 나머지)가 큰 순서이고, 같으면 앞 번호가 먼저입니다. remainder_to 는 1을 더 받은 자리의 0부터 세는 번호를 오름차순으로 적습니다.
환산 순서가 결과를 바꾼다
안내의 환율표로 통화 5종을 원화로 환산해 /root/money/fx.json 에 rate_scale, round_mode, by_ccy(rate, rows, row_then_sum_krw, sum_then_row_krw, gap_krw)를 적으세요.
줄마다 환산해 원 단위로 끊고 더한 값과, 먼저 다 더한 뒤 한 번만 환산한 값은 다릅니다. 둘 다 계산해 차이를 그대로 적으세요. 원화는 소수 자리가 0 이라 끊는 자리는 1원입니다.
여섯 단계를 보고서 하나로 묶기
/root/money/report.json 에 fees_sha256, rows, currencies, exact_total_minor, float_gap_rows, float_gap_minor, rounding_policy, half_even_diff_rows, allocation_cases, allocation_remainder_total, fx_gap_krw, verdict 를 적으세요. verdict 는 integer-minor-units 입니다.
앞 단계의 산출물을 읽어 모으되, 해시는 지금 fees.csv 에서 다시 계산합니다. 보고서가 어떤 자료를 근거로 삼았는지 가리키지 못하면 나중에 숫자를 설명할 수 없습니다. exact_total_minor 는 통화별 최소단위 합계입니다.