Magazín
Technika3 min čtení

Počítání peněz v celých centech a sleva, která zmizela

Dali jsme slevu 50 centů a účetní kniha z ní nezaznamenala nic. Plovoucí desetinná čárka v tom ani nebyla. Praktický průvodce počítáním peněz v celých číslech: ořezávání, rozpočítání a účetní kniha jednotkových cen, jejíž součet sedí přesně.

TTTechnický tým Bookatu

Každý vývojář se brzy naučí, že 0.1 plus 0.2 není 0.3, takže všichni ukládáme peníze jako celé centy a cítíme se v bezpečí. Pak postavíte něco skutečného, třeba procentní slevu přes celý košík, a zjistíte, že celá čísla řeší jen první problém. Ten druhý je, že dělení se o vaši účetní knihu nestará.

Padesát centů, které zmizely

Tady je situace, která nás kousla, osekaná na kost. V košíku je 100 kusů zboží po jednom dolaru. Zákazník dostane z objednávky slevu 50 centů. Mezisoučet je 10 000 centů, sleva 50 centů a zlevněný součet 9 950 centů. Zatím samá celá čísla.

Teď ten prodej zapište do účetní knihy, která ukládá jednotkovou cenu na řádek, jak to účetní systémy mají rády. Zlevněná jednotková cena je 9 950 děleno 100, tedy 99,5 centu. To není celé číslo, takže se zaokrouhlí na 100. Kniha teď říká 100 kusů po 100 centech: prodej za plnou cenu. Sleva se nezmenšila. Zmizela, zatímco rozhraní slíbilo zákazníkovi 9 950 a knihy říkaly 10 000.

js
// The trap: one rounded unit price cannot represent every total.const subtotal = 100 * 100;              // 10000cconst discounted = subtotal - 50;        // 9950cconst unit = Math.round(discounted / 100); // 100c ... the discount is goneconsole.log(unit * 100);                 // 10000c, not 9950c // The fix: split the line into two prices one cent apart.const qty = 100;const base = Math.floor(discounted / qty);   // 99cconst rem = discounted - base * qty;         // 50 units get one extra cent// rem units at (base + 1), the rest at base:// 50 * 100 + 50 * 99 === 9950  ✓ exact, and every price is an integer

Rozdělení vypadá puntičkářsky, dokud neuvidíte, co za to dostanete. Padesát kusů po 100 centech plus padesát kusů po 99 centech se rovná přesně 9 950. Každý řádek pořád ukládá poctivou celočíselnou jednotkovou cenu, počty pořád sedí a součet knihy se rovná číslu na účtence do centu. Nic z toho není s jedinou zaokrouhlenou cenou možné.

Rozpočítání potřebuje dorovnávací průchod

Stejná nemoc se objeví o patro výš, když se sleva na úrovni objednávky rozprostře přes několik řádků. Dejte každému řádku jeho poměrný podíl a zaokrouhlete, a součet podílů bude skoro roven slevě. Někdy o cent výš, někdy o cent níž, občas přesně, což je ze všeho nejhorší, protože to chybu před zběžným testováním schová.

js
// Prorate by share, then push the rounding leftover onto lines with room.let allocated = 0;for (const line of lines) {  line.discount = Math.min(    line.subtotal,    Math.round((discount * line.subtotal) / orderSubtotal),  );  allocated += line.discount;}let remainder = discount - allocated; // may be negativefor (const line of lines) {  if (remainder === 0) break;  const room = remainder > 0 ? line.subtotal - line.discount : line.discount;  const move = Math.min(Math.abs(remainder), room) * Math.sign(remainder);  line.discount += move;  remainder -= move;}// invariant: sum(line.discount) === discount, and no line goes negative

Dva detaily v té smyčce si své místo vydřely. Zbytek se rozprostírá na kterýkoli řádek s kapacitou, ne jen na poslední, protože poslední řádek může být šesticentová položka, do které tři zbylé centy nevejdou. A každý podíl je omezený mezisoučtem svého řádku, protože sleva větší než řádek znamená zápornou jednotkovou cenu, a záporné jednotkové ceny jsou způsob, jak kniha začne lhát na druhou stranu.

Obrazovka pokladny s mezisoučtem, desetiprocentní slevou a přesným zlevněným součtem
Nudná účtenka je celý smysl: mezisoučet, sleva a součet, který kniha zopakuje do centu.

Ořezávejte na každém okraji

  • Slevy ořezávejte do rozsahu od nuly po mezisoučet. Sto deset procent dolů je překlep, ne vratka.
  • Částky zadané uživatelem parsujte obezřetně: prázdné a nečíselné znamená nula, nikdy NaN uprostřed výpočtu.
  • Počty ořezávejte na to, co existuje. Kód okolo peněz dědí každou chybu skladu nad sebou.
  • Testujte součty, které nejdou vydělit beze zbytku. Tři položky po deseti dolarech s dolarem dolů jsou lepší test než jakékoli kulaté číslo.

Nic z toho není žádná sláva, a přesně proto to proklouzne revizí. Aritmetika vypadá správně, protože správná je, až do chvíle, kdy celočíselné dělení potichu zahodí zbytek, na který měl někdo nárok. Zapište si tu podmínku, součet částí se rovná celku, a nechte ji hlídat test.

Celá čísla drží peníze poctivé. Dorovnání drží poctivá celá čísla.

peníze v centechměna a plovoucí desetinná čárkarozpočítání slevychyby zaokrouhlenívývoj plateb
Chcete to uvést do praxe?

Bookatu vám dá rezervační stránku ve vašem stylu, zálohy, členství, dárkové poukazy a připomínky, s 0% provizí z vašich rezervací.

Začít zdarma