Hoe je geen geld verliest door drijfvermogen: Sla prijzen op de juiste manier op
Geld vraagt om precisie. Zelfs een enkele misplaatste cent kan verwarring veroorzaken - of echte verliezen. Toch slaan veel projecten prijzen nog steeds op met het float type, wat onvermijdelijk leidt tot die "mystery cents" die uit het niets verschijnen.

Waarom je met zweeftabellen stilletjes geld verliest
Floating-point types (float, double) volgen de IEEE-754 standaard en slaan getallen binair op, niet decimaal. Veel decimale breuken (zoals 0.1) kunnen niet exact binair worden weergegeven, dus worden de waarden benaderd. Daarom zie je verrassingen zoals:
echo 0.1 + 0.2; // 0.30000000000000004
Dit is geen PHP bug - het is hoe floats werken in bijna elke taal (PHP, JS, Python, Java, C#, Go). Als deze kleine fouten zich opstapelen over kortingen, belastingen en factuurbedragen, begint je boekhouding "mysterieuze centen" te vertonen.
De oplossing: exacte numerieke typen
Stop met het opslaan van geld in binaire drijvende types. Stap over op exacte decimale of gehele weergaven die geen afrondingsfouten bevatten.
MySQL
- DECIMAL(p, s) - decimaal met vaste komma waarbij cijfers exact worden opgeslagen; geweldig voor boekhouding en rapporten (bijv.
DECIMAL(10,2)). - BIGINT - bedragen opslaan in kleine eenheden (centen, pence).
€12.99wordt1299. Snel, compact, precies. - FLOAT/DOUBLE - niet gebruiken voor prijzen; ze zijn bij benadering.
PostgreSQL
- NUMERIC(precision, scale) - exact decimaal (PostgreSQL's DECIMAL); flexibel en precies.
- BIGINT - dezelfde benadering met kleine eenheden als MySQL, ideaal voor transactionele gegevens.
- MONEY - plaatsafhankelijk en inflexibel; niet aanbevolen voor systemen met meerdere valuta.
Waarom BIGINT vaak de beste keuze is
BIGINT is een groot geheel getal met een enorm bereik (tot 9,22×1018). Als je prijzen in centen opslaat, is dat tot ~€92 biljoen met centprecisie - meer dan genoeg voor elk e-commerce of SaaS platform.
- Exact door constructie: gehele getallen hebben geen afrondingsruis. Wat je opslaat is wat je krijgt - zelfs na miljoenen bewerkingen.
- Prestaties: integer rekenen, vergelijkingen, indexeren, sorteren en aggregeren zijn goedkoper dan
DECIMALop grote datasets. - Portabiliteit: gehele getallen gedragen zich consistent in MySQL, PostgreSQL, SQLite en ORM's. Geen zorgen over mismatches tussen precisie en schaal.
- Voorspelbare wiskunde: BTW/kortingen toepassen, dan eenmaal afronden naar kleine eenheden; geen
0.00000001artefacten. - API afstemming: grote betalingsproviders (Stripe, Adyen, PayPal) gebruiken kleine eenheden in hun API's - door ze af te stemmen voorkom je afrondingsproblemen tussen systemen.
Exact rekenen in PHP (voorkom ook float tijdens runtime)
Zelfs als de DB correct is, kan je code opnieuw zweeffouten introduceren. Gebruik BCMath voor decimaal-veilige bewerkingen:
$total = bcadd('0.1', '0.2', 2); // "0.30"
$vat = bcmul('12.99', '0.20', 2); // "2.60"
$gross = bcadd('12.99', '2.60', 2); // "15.59"
bcadd, bcmul, bcdiv werken op tekenreeksen met een vaste schaal - dus "0,1 + 0,2" is altijd gelijk aan "0,3".
Laravel voorbeeld: kleine cast, grote veiligheid
Sla centen op in de DB en geef "12,99" weer in PHP. Een minimale cast doet de conversie in beide richtingen:
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class AsPrice implements CastsAttributes
{
public function get(Model $model, string $key, mixed $value, array $attributes): mixed
{
if (empty($value)) {
return 0;
}
return bcdiv((string) $value, '100', 2);
}
public function set(Model $model, string $key, mixed $value, array $attributes): mixed
{
if (empty($value)) {
return 0;
}
return bcmul((string) $value, '100', 0);
}
}
Bevestig de cast aan je model:
class Product extends \Illuminate\Database\Eloquent\Model
{
protected $casts = [
'price' => \App\Casts\AsPrice::class,
];
}
Schema met strikte valuta's
Ook al begin je met één valuta, voeg nu een valutakolom toe. Houd het strikt met ENUM voor EUR / USD / GBP:
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->bigInteger('price'); // stored in minor units (cents)
$table->enum('currency', ['EUR', 'USD', 'GBP'])->default('EUR');
$table->timestamps();
});
Belangrijkste opmerkingen
- Zwevingen zijn bij benadering (IEEE-754) en dat is universeel in alle talen - gebruik ze niet voor geld.
- Geef de voorkeur aan BIGINT (kleine eenheden) voor snelheid, veiligheid en draagbaarheid; gebruik DECIMAL/NUMERIC als rapportage decimalen vereist.
- Doe rekenwerk met BCMath in PHP om de resultaten exact te houden.
- Een kleine Laravel cast maakt prijzen ergonomisch in code terwijl je database nauwkeurig blijft.
- Sla valuta op als ENUM (
EUR,USD,GBP) om slechte gegevens te voorkomen.
Neem contact op
Een project in gedachten?
eel je context en gewenst resultaat. Binnen 1 werkdag sturen we de eenvoudigste volgende stap (tijdlijn, ruwe begroting of snelle audit).