Hoe je geen geld verliest door floats: 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 floats je stilletjes geld kosten
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.30000000000000004Dit 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 transactiedata.
- 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-integraties — 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 float-fouten 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".
BCMath houdt je berekeningen exact. Voor meer tips over schone PHP-code, bekijk Een minimale set PHP-praktijken voor leesbare code.
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
- Floats 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 ongeldige data te voorkomen.
Is het veilig om float te gebruiken voor geld in een database?
Nee. Floating-point types (float, double) volgen de IEEE-754 standaard, die getallen binair opslaat. Decimale breuken zoals 0.1 kunnen niet exact worden weergegeven, wat kleine afrondingsfouten veroorzaakt. Die stapelen zich op over kortingen, belastingen en facturen — en leiden uiteindelijk tot onverklaarbare centverschillen. Gebruik in plaats daarvan DECIMAL of BIGINT.
Hoe slaan Stripe en andere betalingsproviders bedragen op?
Stripe, Adyen en PayPal gebruiken allemaal kleine eenheden — gehele getallen die de kleinste valuta-eenheid vertegenwoordigen. Dus €12,99 wordt verstuurd als 1299, $50,00 als 5000. Als je database ook prijzen in kleine eenheden opslaat (BIGINT), voorkom je afrondingsfouten bij de integratiegrens. Geen float, geen afwijking.
DECIMAL of BIGINT — wat is beter voor het opslaan van prijzen?
Beide zijn exact en veilig voor geld. BIGINT slaat prijzen op in kleine eenheden (centen), wat sneller is voor berekeningen, indexering en sortering — en overeenkomt met hoe Stripe, Adyen en PayPal bedragen in hun API's versturen. DECIMAL slaat leesbare waarden (12,99) direct op, wat handig kan zijn voor SQL-rapporten. Voor de meeste webapplicaties en e-commerce platforms is BIGINT de praktische standaardkeuze.
Hoe voorkom ik float-fouten in PHP-berekeningen?
Gebruik de BCMath-extensie. Functies zoals bcadd(), bcmul() en bcdiv() werken op stringrepresentaties met een vaste decimale schaal. Daardoor is 0,1 + 0,2 altijd gelijk aan 0,3 — zonder IEEE-754-verrassingen. Gebruik nooit de standaard PHP-operators (+, *, /) voor geldbedragen.
Neem contact op
Een projectin gedachten?
Deel je context en gewenst resultaat. Binnen 1 werkdag sturen we de eenvoudigste volgende stap (tijdlijn, ruwe begroting of snelle audit).