---
title: "Contas de dinheiro em cêntimos inteiros, e o desconto que desapareceu"
description: "Demos um desconto de cinquenta cêntimos e o registo não guardou nenhum. A vírgula flutuante nem sequer foi a culpada. Um guia de campo para fazer contas de dinheiro com inteiros: limites, rateio, e como fazer com que um registo de preços unitários some exatamente o total."
url: "https://bookatu.com/pt-pt/blog/money-math-in-integer-cents"
language: "pt"
section: "Blog"
category: "Engenharia"
author: "A equipa de engenharia do Bookatu"
published: "2026-07-13"
updated: "2026-07-13"
tags: ["dinheiro em cêntimos", "vírgula flutuante e moeda", "rateio de descontos", "erros de arredondamento", "engenharia de pagamentos"]
---

# Contas de dinheiro em cêntimos inteiros, e o desconto que desapareceu

Demos um desconto de cinquenta cêntimos e o registo não guardou nenhum. A vírgula flutuante nem sequer foi a culpada. Um guia de campo para fazer contas de dinheiro com inteiros: limites, rateio, e como fazer com que um registo de preços unitários some exatamente o total.

> **Em resumo**
>
> - Guarde e calcule dinheiro em cêntimos inteiros. Os números de vírgula flutuante são o erro famoso; a política de arredondamento é o erro a sério.
> - Um desconto rateado tem de fechar: depois de arredondar a parte de cada linha, empurre os cêntimos que sobram para as linhas que tenham folga até a soma ficar exata.
> - Se um registo guarda um preço unitário por linha, um total com desconto que não divide de forma exata não pode ser representado. Parta a linha em dois preços com um cêntimo de diferença.
> - Limite tudo nas pontas: os descontos ao subtotal, as quantidades ao stock, os totais a zero.

Todos os programadores aprendem cedo que 0,1 mais 0,2 não dá 0,3, por isso guardamos todos o dinheiro em cêntimos inteiros e ficamos descansados. Depois constrói-se uma coisa a sério, como um desconto em percentagem sobre um carrinho, e descobre-se que os inteiros só resolvem o primeiro problema. O segundo problema é que a divisão não quer saber do seu registo.

## Os cinquenta cêntimos que desapareceram

Eis a situação que nos apanhou, reduzida ao osso. Um carrinho tem 100 unidades de um artigo de um dólar. O cliente recebe cinquenta cêntimos de desconto na encomenda. O subtotal é 10 000 cêntimos, o desconto é 50 cêntimos, e o total com desconto é 9950 cêntimos. Até aqui, só inteiros.

Agora registe essa venda num livro que guarda um preço unitário por linha, como os sistemas de contabilidade gostam. O preço unitário com desconto é 9950 a dividir por 100, ou seja 99,5 cêntimos. Isso não é um inteiro, por isso é arredondado para 100. O registo passa a dizer 100 unidades a 100 cêntimos: uma venda a preço cheio. O desconto não encolheu. Desapareceu, enquanto a interface prometia 9950 ao cliente e as contas diziam 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
```

A divisão parece um preciosismo até se ver o que traz. Cinquenta unidades a 100 cêntimos mais cinquenta unidades a 99 cêntimos dá exatamente 9950. Cada linha continua a guardar um preço unitário inteiro e honesto, as quantidades continuam a bater certo, e a soma do registo iguala ao cêntimo o número do recibo. Nada disso é possível com um único preço arredondado.

## O rateio precisa de uma passagem de fecho

A mesma doença aparece um nível acima quando um desconto ao nível da encomenda se espalha por várias linhas. Dê a cada linha a parte proporcional e arredonde, e as partes vão somar quase o desconto. Às vezes um cêntimo a mais, às vezes um a menos, de vez em quando certinho, que é o pior resultado de todos porque esconde o erro de quem testa por alto.

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

Dois pormenores desse ciclo ganharam o seu lugar à força. O resto espalha-se por qualquer linha com capacidade, e não só pela última linha, porque a última linha pode ser um artigo de seis cêntimos sem folga para absorver três cêntimos que sobraram. E cada parte é limitada ao subtotal da própria linha, porque um desconto maior do que a linha implica um preço unitário negativo, e os preços unitários negativos são a forma de um registo começar a mentir no sentido contrário.

![Um ecrã de ponto de venda com um subtotal, um desconto de dez por cento e o total exato com desconto](https://bookatu.com/blog/shots/pos-discount-light.png)

*O recibo aborrecido é o objetivo todo: subtotal, desconto e um total que o registo reproduz ao cêntimo.*

## Limite em todas as pontas

- Os descontos ficam limitados ao intervalo entre zero e o subtotal. Cento e dez por cento de desconto é uma gralha, não um reembolso.
- Leia os valores introduzidos pelo utilizador com desconfiança: vazio e não numérico querem dizer zero, nunca NaN a meio de uma conta.
- As quantidades ficam limitadas ao que existe. O código do dinheiro herda todos os erros de inventário que vêm antes dele.
- Teste os totais que não dividem de forma exata. Três artigos a dez dólares com um dólar de desconto é um teste melhor do que qualquer número redondo.

Nada disto é glamoroso, e é exatamente por isso que passa na revisão. A aritmética parece certa porque está certa, até ao momento em que a divisão inteira deita fora em silêncio um resto a que alguém tinha direito. Escreva a invariante num papel, a soma das partes é igual ao todo, e ponha um teste a segurar a porta.

> Os inteiros mantêm o dinheiro honesto. O fecho mantém os inteiros honestos.
