TT Lab
Get started
Learn Learning paths Courses

The Language of Banking

Three Won Missing, Yet Every Row Checks Out

Continue in TT Lab

Goal

You build a table of each currency's different minor unit, reload the fee ledger as integer minor units, pin the rounding mode as policy, and leave as outputs an allocation that matches even the leftover 1 and the difference by conversion order.

Why it matters

If you handle amounts as real numbers, each line is off by 1, and unit tests cannot catch that. Even when an inquiry comes in saying the total does not match, you cannot point out which line is wrong, so an operating procedure arises: "if you rerun it, it matches." Three things are decided here. The storage format (integer minor units), the rounding position and rounding mode (policy), and the allocation rule for the remainder (the largest remainder method). All three are things that technology does not decide but people must decide and write in a document. The grader does not compare the numbers you wrote against each other. It recomputes the reference values from the raw data you made in step 1 and cross-checks. That is why following the rules exactly matters.

Steps

  1. Create and run /root/money/gen_money.py to make /root/money/currencies.json, /root/money/fees.csv (600 rows), and /root/money/splits.csv (30 cases).
  2. Compute a surcharge rate of 0.125 on the floating-point path and the integer path, and write the number of lines that diverge and the difference to /root/money/float_gap.json.
  3. Load into two tables in /root/money/money.db, fee (integer minor units) and fee_real (REAL), and write the two sums per currency to /root/money/store.json.
  4. Gather parsing and display in /root/money/money_fmt.py, and judge the 12 inputs in the guide to produce /root/money/format_check.json.
  5. Round the same multiplication with ROUND_HALF_UP and ROUND_HALF_EVEN and write the number of lines that split and the two sums to /root/money/rounding.json.
  6. Allocate the 30 cases in splits.csv with the largest remainder method and write them to /root/money/alloc.json. The sum must equal the total exactly.
  7. Convert 5 currencies to won with the exchange rate table in the guide and write two values, per-line and per-total, to /root/money/fx.json.
  8. Bundle the results of the earlier steps into a single /root/money/report.json and write the sha256 of fees.csv with it.

Notes

Build the currency table and the fee ledger

Create and run /root/money/gen_money.py to make /root/money/currencies.json, /root/money/fees.csv (600 rows), and /root/money/splits.csv (30 cases).

For the currency table's decimal places, use the List One values given in the reading as they are. For a currency whose decimal places are N.A., it is not a number, so it is null. amount_text is the text of the minor-unit integer, so a 0-decimal currency has no decimal point.

How many lines does the surcharge computed in real numbers diverge

Compute a surcharge rate of 0.125 with the two paths in the guide and write rate, rows, mismatch_rows, gap_minor_total, and by_ccy to /root/money/float_gap.json.

The difference between the two paths comes from two places. 0.125 is exact in binary, but an amount like 12.345 is not, and Python's built-in round() rounds 0.5 toward the even side. by_ccy is an object holding, for each currency, the two values mismatch_rows and gap_minor.

Reload in integer minor units

Create two tables in /root/money/money.db, fee (row_id, ccy, minor, exponent) and fee_real (row_id, ccy, amount REAL), put 600 rows in each, and write sum_minor, real_round_minor, and equal per currency to /root/money/store.json.

In SQLite, types attach to values, not columns. Even if you declare INTEGER, a real number goes in as REAL, so check with typeof(). real_round_minor is the integer obtained by multiplying the REAL sum by the currency's decimal places and rounding. equal may come out all true — at this scale the REAL sum happens to round to the same value, which is why such defects survive for a long time. Write down what you see.

Gather parsing and display into one gate

Build parse and fmt in /root/money/money_fmt.py, judge the 12 inputs in the guide, and write them as ok and rejected to /root/money/format_check.json.

Put in, ccy, minor, and round_trip in the ok entries. round_trip is the minor unit turned back into text, so it must be the same as the original notation. There are only four rejection reasons, and not knowing the currency at all is a different reason from not knowing its decimal places.

Pin the rounding mode as policy

Round the same multiplication with ROUND_HALF_UP and ROUND_HALF_EVEN and write policy, rate, diff_rows, half_up_total_minor, and half_even_total_minor to /root/money/rounding.json. This lab's policy is ROUND_HALF_UP.

Multiply once and quantize twice. 0.125 is an exact value in binary, so positions that are exactly half come up often, and that is where the two modes split. If the two sums are the same, one of them was not actually applied.

An allocation that matches even the leftover 1

Allocate the 30 cases in splits.csv to 3 slots with the largest remainder method and write cases, sum_ok, and remainder_total to /root/money/alloc.json.

Give the quotients rounded down and add 1 each only for as much as is left. Who receives is in order of the largest remainder (the remainder of the total times the weight divided by the sum of weights), and if equal, the earlier number first. remainder_to lists, in ascending order, the 0-based numbers of the slots that received the extra 1.

The conversion order changes the result

Convert 5 currencies to won with the exchange rate table in the guide and write rate_scale, round_mode, and by_ccy (rate, rows, row_then_sum_krw, sum_then_row_krw, gap_krw) to /root/money/fx.json.

The value obtained by converting per line and rounding to the won unit and then adding differs from the value obtained by adding everything first and converting once. Compute both and write the difference as it is. The won has 0 decimal places, so the rounding position is 1 won.

Bundle the six steps into one report

Write 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, and verdict to /root/money/report.json. verdict is integer-minor-units.

Gather by reading the outputs of the earlier steps, but recompute the hash now from fees.csv. If a report cannot point to what data it rested on, you cannot explain the numbers later. exact_total_minor is the per-currency minor-unit sum.