---
title: "Beløb regnet i hele cent, og rabatten der forsvandt"
description: "Vi gav halvtreds cents rabat, og regnskabet noterede ingenting. Flydende tal var ikke engang synderen. En feltguide til at regne med penge i heltal: afgrænsning, fordeling og at få et regnskab af enhedspriser til at summe til den præcise total."
url: "https://bookatu.com/da-dk/blog/money-math-in-integer-cents"
language: "da"
section: "Blog"
category: "Teknik"
author: "Bookatus udviklingsteam"
published: "2026-07-13"
updated: "2026-07-13"
tags: ["beløb i cent", "flydende tal og valuta", "fordeling af rabat", "afrundingsfejl", "udvikling af betalinger"]
---

# Beløb regnet i hele cent, og rabatten der forsvandt

Vi gav halvtreds cents rabat, og regnskabet noterede ingenting. Flydende tal var ikke engang synderen. En feltguide til at regne med penge i heltal: afgrænsning, fordeling og at få et regnskab af enhedspriser til at summe til den præcise total.

> **Kort fortalt**
>
> - Gem og beregn beløb som hele cent. Flydende tal er den berømte fejl; afrundingspolitikken er den rigtige.
> - En fordelt rabat skal gå op: efter at hver linjes andel er rundet af, skubbes de resterende cent over på de linjer, der har plads, indtil summen passer præcist.
> - Gemmer et regnskab én enhedspris pr. linje, kan en rabatteret total, der ikke går lige op, ikke lade sig gengive. Del linjen i to priser med én cents forskel.
> - Afgræns alt i kanterne: rabatter til subtotalen, antal til lageret, totaler til nul.

Alle udviklere lærer tidligt, at 0,1 plus 0,2 ikke er 0,3, så vi gemmer alle sammen beløb som hele cent og føler os trygge. Så bygger man noget rigtigt, for eksempel en procentrabat på tværs af en kurv, og opdager, at heltal kun løser det første problem. Det andet problem er, at division er ligeglad med dit regnskab.

## De halvtreds cent, der forsvandt

Her er den opstilling, der bed os, skåret ind til benet. En kurv indeholder 100 stk. af en vare til én dollar. Kunden får halvtreds cents rabat på ordren. Subtotalen er 10.000 cent, rabatten er 50 cent, og den rabatterede total er 9.950 cent. Indtil videre kun heltal.

Bogfør så det salg i et regnskab, der gemmer en enhedspris pr. linje, sådan som regnskabssystemer godt kan lide. Den rabatterede enhedspris er 9.950 divideret med 100, altså 99,5 cent. Det er ikke et heltal, så det bliver rundet op til 100. Regnskabet siger nu 100 stk. til 100 cent: et salg til fuld pris. Rabatten blev ikke mindre. Den forsvandt, mens brugerfladen lovede kunden 9.950, og bøgerne sagde 10.000.

```js
// The trap: one rounded unit price cannot represent every total.
const subtotal = 100 * 100;              // 10000c
const discounted = subtotal - 50;        // 9950c
const unit = Math.round(discounted / 100); // 100c ... the discount is gone
console.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);   // 99c
const 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
```

Opdelingen ser pillen ud, indtil man ser, hvad den køber. Halvtreds stk. til 100 cent plus halvtreds stk. til 99 cent giver præcis 9.950. Hver række gemmer stadig en ærlig enhedspris i heltal, antallene går stadig op, og summen af regnskabet svarer til tallet på kvitteringen ned til sidste cent. Intet af det er muligt med én afrundet pris.

## Fordeling kræver en afstemning bagefter

Den samme sygdom dukker op et niveau højere oppe, når en rabat på hele ordren fordeles over flere linjer. Giv hver linje sin forholdsmæssige andel og rund af, og andelene vil summe til næsten rabatten. Nogle gange én cent over, nogle gange én under, en gang imellem lige præcis, hvilket er det værste udfald af dem alle, fordi det skjuler fejlen for overfladiske test.

```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 negative
for (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
```

To detaljer i den løkke har gjort sig fortjent på den hårde måde. Resten fordeles på enhver linje med plads, ikke bare den sidste linje, fordi den sidste linje kan være en vare til seks cent uden plads til at optage tre overskydende cent. Og hver andel bliver begrænset af sin egen linjesubtotal, fordi en rabat, der er større end linjen, betyder en negativ enhedspris, og negative enhedspriser er den måde, et regnskab begynder at lyve på i den anden retning.

![En kasseskærm med en subtotal, en rabat på ti procent og den præcise rabatterede total](https://bookatu.com/blog/shots/pos-discount-light.png)

*Den kedelige kvittering er hele pointen: subtotal, rabat og en total, som regnskabet gengiver ned til sidste cent.*

## Afgræns i hver kant

- Rabatter afgrænses til intervallet fra nul til subtotalen. Hundrede og ti procents rabat er en slåfejl, ikke en refusion.
- Læs indtastede beløb defensivt: tomt og ikke-numerisk betyder nul, aldrig NaN midt i et regnestykke.
- Antal afgrænses til det, der findes. Kode, der regner med penge, arver hver eneste lagerfejl længere oppe.
- Test de totaler, der ikke går lige op. Tre varer til ti dollars med en dollar i rabat er en bedre test end noget rundt tal.

Intet af det her er glamourøst, og det er præcis derfor, det slipper gennem review. Regnestykket ser rigtigt ud, fordi det er rigtigt, lige indtil heltalsdivision stille smider en rest væk, som nogen havde til gode. Skriv invarianten ned, summen af delene er lig med helheden, og lad en test holde døren.

> Heltal holder pengene ærlige. Afstemning holder heltallene ærlige.
