量り売りの店は いつか締めが合わなくなる。
ラベルの束を足した額とレジ合計が数百円ずれ、どこで出たのか見つからない。しかも毎日少しずつ出る。
なぜ出るのか
値段が 二度 計算されるからだ。
- ラベルを打つとき — 客が払える単位に丸める。3,444.5 円のものを 3,440 円と打つ。
- 帳簿に入れるとき — 重さから計算し直す。3,444.5 円。
どちらも間違っていない。ところが違う。そしてその差は一件あたり 4〜5 円なので、一日経たないと見えない。
値段を作る場所をひとつに
このサンプルの答えは関数ひとつだ。
/// The only place a price is made.
///
/// Rounds to what somebody can pay, once. Every other number in this server
/// — the label, the ledger, the day total — comes from this and never from
/// a second pass over the weight.
static int priceFor(Product p, int grams) {
final exact = p.wonPer100g * grams / 100;
return (exact / _payableStep).round() * _payableStep;
}
そしてラベルを打つとき、その一度の結果がそのまま帳簿に入る。
// One call, one number. The label and the ledger row are the same
// integer, so there is nothing for them to disagree about.
final won = priceFor(_product, _grams);
_sales.add(Sale(_product.code, _product.name, _grams, won));
客が払った額が、そのまま帳簿に残る額だ。 帳簿が「本来は 3,444.5 円だが」と言う理由は無い — 客はそれを払っていない。
はかりの上

ラベルに 丸めがいくらだったかが書いてある。
pan: Pork belly 347 g — exact 10340.6 won, label 10340 won (rounded -0.6 won to the nearest 10)
// What the rounding actually did, in won, said out loud. A shop
// that rounds without showing it is a shop that argues later.
これは客のためではなく 店のためのものだ。丸めを見せなければ、後で「なぜこの計算になるのか」と聞かれて誰も答えられない。見せてあればその場で終わる。
五件、そして締め
label 1: 347 g -> 10340 won (exact 10340.6, rounded -0.6)
label 2: 512 g -> 21250 won (exact 21248.0, rounded +2.0)
label 3: 210 g -> 2600 won (exact 2604.0, rounded -4.0)
label 4: 1000 g -> 29800 won (exact 29800.0, rounded 0.0)
label 5: 83 g -> 3440 won (exact 3444.5, rounded -4.5)
切り上げも切り下げもぴったりもある。 わざとそう選んだ — 全部同じ方向に丸まるデータで試すと、この種のバグは捕まらない。

ledger says 5 labels, total 67430 won
harness added the labels itself: 67430 won
ledger total minus watched labels: 0 won