TT Lab
はじめる
学ぶ 学習パス コース

銀行現場の言葉

合計が3ウォン合わないのに、どの行も間違っていない

TT Labで続きを見る

目標

通貨ごとに異なる最小単位を表にし、手数料元帳を整数の最小単位で再ロードし、丸めモードをポリシーとして確定し、余りの1まで合う按分と、換算の順序による差を、出力物として残します。

なぜ重要なのか

金額を実数で扱うと1行あたり1ずつずれ、その程度は単体テストでは捕まりません。合計が合わないという問い合わせが来ても、どの行が間違っているかを指せないので、「回し直せば合う」という運用手順ができます。 ここで決めるのは3つです。保存形式(整数の最小単位)、丸める桁と丸めモード(ポリシー)、余りの按分ルール(最大剰余法)。3つとも、技術が決めてくれるものではなく、人が決めて文書に書かなければならないものです。 採点ツールは、書かれた数字どうしを比較しません。ステップ1で作った元データから基準値を再計算して突き合わせます。そのため、ルールをそのとおりに守ることが重要です。

ステップ

  1. /root/money/gen_money.pyを作成して実行し、/root/money/currencies.json、/root/money/fees.csv(600行)、/root/money/splits.csv(30件)を作ってください。
  2. 加算率0.125を、実数経路と整数経路でそれぞれ計算し、/root/money/float_gap.jsonにずれた行数と差を書いてください。
  3. /root/money/money.dbにfee(整数の最小単位)とfee_real(REAL)の2つのテーブルとしてロードし、/root/money/store.jsonに通貨ごとの2つの合計を書いてください。
  4. /root/money/money_fmt.pyにパースと表示をまとめ、案内の入力12個を判定して、/root/money/format_check.jsonを作ってください。
  5. 同じ掛け算をROUND_HALF_UPとROUND_HALF_EVENで丸めて、/root/money/rounding.jsonに、分かれた行数と2つの合計を書いてください。
  6. splits.csvの30件を最大剰余法で按分して、/root/money/alloc.jsonに書いてください。合計は総額とぴったり同じでなければなりません。
  7. 案内の為替レート表で通貨5種をウォンに換算して、/root/money/fx.jsonに、行単位と総額単位の2つの値を書いてください。
  8. 前のステップの結果を/root/money/report.jsonの1つにまとめ、fees.csvのsha256もあわせて書いてください。

参考

通貨表と手数料元帳を作る

/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を案内の2つの経路で計算し、/root/money/float_gap.jsonにrate、rows、mismatch_rows、gap_minor_total、by_ccyを書いてください。

2つの経路の差は2か所から来ます。0.125は2進数で正確ですが、12.345のような金額はそうではなく、Python組み込みのround()は0.5を偶数側に寄せます。by_ccyは、通貨ごとにmismatch_rowsとgap_minorの2つの値を持つオブジェクトです。

整数の最小単位で再ロードする

/root/money/money.dbにfee(row_id, ccy, minor, exponent)とfee_real(row_id, ccy, amount REAL)の2つのテーブルを作って600行ずつ入れ、/root/money/store.jsonに通貨ごとのsum_minor、real_round_minor、equalを書いてください。

SQLiteのデータ型は、列ではなく値に付きます。INTEGERで宣言しても、実数を入れるとREALとして入るので、typeof()で確認してみてください。real_round_minorは、REALの合計をその通貨の小数桁数だけ掛けて丸めた整数です。equalがすべてtrueになることもあります。この規模では、REALの合計がたまたま同じ値に丸まるからで、だからこうした欠陥は長く生き残ります。見たとおりに書いてください。

パースと表示を1か所にまとめる

/root/money/money_fmt.pyにparseとfmtを作り、案内の入力12個を判定して、/root/money/format_check.jsonにokとrejectedで書いてください。

okの項目にはin、ccy、minor、round_tripを入れます。round_tripは最小単位をもう一度文字にした値なので、元の表記と同じでなければなりません。拒否理由は4つだけで、通貨自体を知らないことと、小数桁数を知らないことは、別の理由です。

丸めモードをポリシーとして確定する

同じ掛け算を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です。

掛け算は1回だけ行い、quantizeを2回行えばよいのです。0.125は2進数で正確な値なので、ちょうど半分の桁がよく出て、そこで2つのモードが分かれます。2つの合計が同じなら、片方が実際には適用されていません。

余りの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)を書いてください。

行ごとに換算してウォン単位に丸めて足した値と、先にすべて足してから1回だけ換算した値は違います。両方を計算して、差をそのまま書いてください。ウォンは小数桁数が0なので、丸める桁は1ウォンです。

6つのステップを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は、通貨ごとの最小単位の合計です。