按重量卖的店 总有一天会对不上账。
一摞标签加起来,和收银总额差出几百块,找不到是哪儿出的。而且每天都出一点。
为什么会出
因为价格被算了 两次。
- 打标签的时候 —— 取整到客人能付的单位。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