---
title: "Pengaräkning i heltalscent, och rabatten som försvann"
description: "Vi gav femtio cents rabatt och huvudboken registrerade inget av den. Flyttal var inte ens boven. En fältguide till pengaräkning med heltal: att begränsa värden, fördela rabatter proportionellt och få en huvudbok med styckpriser att summera till exakt totalsumman."
url: "https://bookatu.com/sv-se/blog/money-math-in-integer-cents"
language: "sv"
section: "Blogg"
category: "Teknik"
author: "Bookatus teknikteam"
published: "2026-07-13"
updated: "2026-07-13"
tags: ["pengar i cent", "flyttal och valuta", "proportionerad rabatt", "avrundningsfel", "betalningsteknik"]
---

# Pengaräkning i heltalscent, och rabatten som försvann

Vi gav femtio cents rabatt och huvudboken registrerade inget av den. Flyttal var inte ens boven. En fältguide till pengaräkning med heltal: att begränsa värden, fördela rabatter proportionellt och få en huvudbok med styckpriser att summera till exakt totalsumman.

> **Kort sagt**
>
> - Lagra och räkna pengar som heltalscent. Flyttal är den berömda buggen; avrundningspolicyn är den riktiga.
> - En proportionerad rabatt måste stämma: när varje rads andel har avrundats ska överskottet skjutas över på rader med utrymme tills summan är exakt.
> - Lagrar en huvudbok ett styckpris per rad går en rabatterad totalsumma som inte delar sig jämnt inte att representera. Dela raden i två priser med en cents mellanrum.
> - Begränsa allt i kanterna: rabatter till delsumman, antal till lagret, totaler till noll.

Varje utvecklare lär sig tidigt att 0,1 plus 0,2 inte är 0,3, så vi lagrar alla pengar som heltalscent och känner oss trygga. Sedan bygger man något på riktigt, som en procentrabatt över en varukorg, och upptäcker att heltal bara löser det första problemet. Det andra problemet är att division inte bryr sig om din huvudbok.

## De femtio cent som försvann

Här är uppställningen som bet oss, nedkokad till benet. En varukorg innehåller 100 enheter av en vara som kostar en dollar. Kunden får femtio cent av på ordern. Delsumman är 10 000 cent, rabatten är 50 cent, och den rabatterade totalsumman är 9 950 cent. Så långt inget annat än heltal.

Bokför nu den försäljningen i en huvudbok som lagrar ett styckpris per rad, så som redovisningssystem gillar. Det rabatterade styckpriset är 9 950 delat med 100, vilket är 99,5 cent. Det är inget heltal, så det avrundas till 100. Huvudboken säger nu 100 enheter à 100 cent: en försäljning till fullpris. Rabatten krympte inte. Den försvann, medan gränssnittet lovade kunden 9 950 och böckerna sa 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
```

Uppdelningen ser petig ut tills man ser vad den ger. Femtio enheter à 100 cent plus femtio enheter à 99 cent blir exakt 9 950. Varje rad lagrar fortfarande ett ärligt styckpris i heltal, antalen går fortfarande ihop, och summan av huvudboken är lika med siffran på kvittot ner till cent. Inget av det är möjligt med ett enda avrundat pris.

## Proportionering behöver en avstämningsrunda

Samma sjukdom dyker upp ett steg upp när en rabatt på ordernivå sprids över flera rader. Ge varje rad sin proportionella andel och avrunda, så summerar andelarna till nästan rabatten. Ibland en cent över, ibland en under, då och då precis rätt, vilket är det värsta utfallet av alla eftersom det döljer buggen för slentrianmässig testning.

```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
```

Två detaljer i den loopen förtjänade sin plats den hårda vägen. Överskottet sprids över vilken rad som helst med utrymme, inte bara den sista raden, eftersom den sista raden kan vara en vara på sex cent utan utrymme att svälja tre överblivna cent. Och varje andel begränsas av radens egen delsumma, eftersom en rabatt större än raden innebär ett negativt styckpris, och negativa styckpriser är hur en huvudbok börjar ljuga åt andra hållet.

![En kassaskärm som visar en delsumma, tio procents rabatt och den exakta rabatterade totalsumman](https://bookatu.com/blog/shots/pos-discount-light.png)

*Det tråkiga kvittot är hela poängen: delsumma, rabatt och en totalsumma som huvudboken återger ner till cent.*

## Begränsa i varje kant

- Rabatter begränsas till intervallet från noll till delsumman. Hundratio procent av är ett skrivfel, inte en återbetalning.
- Tolka belopp som användaren skrivit in defensivt: tomt och icke-numeriskt betyder noll, aldrig NaN mitt i en uträkning.
- Antal begränsas till det som finns. Pengakod ärver varje lagerbugg uppströms.
- Testa totalerna som inte delar sig jämnt. Tre varor à tio dollar med en dollar av är ett bättre test än något jämnt tal.

Inget av det här är glamoröst, vilket är precis därför det slinker igenom granskningen. Uträkningen ser korrekt ut eftersom den är korrekt, ända tills heltalsdivision tyst slänger bort en rest som någon hade rätt till. Skriv ner invarianten, summan av delarna är lika med helheten, och låt ett test hålla dörren.

> Heltal håller pengar ärliga. Avstämning håller heltalen ärliga.
