✏️ Explanatory Question

The FLOAT money bug — why 0.1 + 0.2 isn't 0.3 and invoices show wrong totals

👁 6 Views
📘 Detailed Answer
🟢 Easy
💡

Answer with Explanation

120

The FLOAT money bug — why 0.1 + 0.2 isn't 0.3 and invoices show wrong totals

Level: Hard — A financial-grade bug; storing money as FLOAT causes rounding errors that fail audits and lose real money.

Scenario: An invoice system stores prices as FLOAT. A customer buys three items at 0.10 each, but the invoice shows 0.30000000000000004. Worse, summing thousands of orders drifts by a few paise, and the finance team's totals never reconcile. Why does this happen, and how do you store money correctly?

The Root Cause — Binary Floating-Point

FLOAT and DOUBLE store numbers in binary (base-2), following the IEEE 754 standard. Many decimal fractions — like 0.1 — cannot be represented exactly in binary, just as 1/3 cannot be written exactly in decimal. So the stored value is a tiny bit off, and those errors accumulate across calculations.

$$ 0.1_{10} = 0.0001100110011001100..._{2} \text{ (repeating, never exact)} $$

The golden rule for money: Never use FLOAT or DOUBLE for currency. Use DECIMAL(p, s) (also called NUMERIC) — a fixed-point, exact type that stores the value precisely, digit for digit, with no binary approximation.

Predict the Output

ExpressionFLOAT/DOUBLE resultDECIMAL result
0.1 + 0.20.300000000000000040.3
Sum of 10,000 x 0.0199.99999999 drift100.00
Equality: 0.1+0.2 = 0.3FALSETRUE

The Bug and the Fix

-- THE BUG: money stored as FLOAT drifts
CREATE TABLE invoices_bad (
    id     INT PRIMARY KEY,
    amount FLOAT
);
INSERT INTO invoices_bad VALUES (1, 0.1), (2, 0.2);
SELECT SUM(amount) FROM invoices_bad;   -- 0.30000000000000004

-- Equality comparison fails unexpectedly
SELECT 0.1 + 0.2 = 0.3;   -- returns 0 (FALSE) with float math!

-- THE FIX: DECIMAL for exact money
CREATE TABLE invoices (
    id     INT PRIMARY KEY,
    amount DECIMAL(12, 2)   -- 12 total digits, 2 after the decimal
);
INSERT INTO invoices VALUES (1, 0.10), (2, 0.20);
SELECT SUM(amount) FROM invoices;   -- exactly 0.30

Choosing DECIMAL Precision and Scale

DECIMAL(p, s): p = total digits (precision), s = digits after the decimal (scale).

Use CaseTypeMax Value
Standard currencyDECIMAL(12, 2)9,999,999,999.99
High-value / enterpriseDECIMAL(19, 4)Large + 4 dp precision
Crypto / fractional unitsDECIMAL(30, 18)18 decimal places

The integer-cents alternative: Some systems store money as an integer number of the smallest unit (e.g., paise/cents) in a BIGINT — so 100.50 is stored as 10050. This is also exact and avoids decimals entirely; you divide by 100 only at display time. Both DECIMAL and integer-cents are valid; FLOAT is not.

Safe Rounding for Money

-- DECIMAL rounds predictably; always ROUND to the currency's scale
SELECT ROUND(amount * 1.18, 2) AS with_tax   -- 18% GST, rounded to paise
FROM invoices;

-- Avoid dividing then multiplying with FLOAT (introduces drift)
-- Do the math in DECIMAL context
SELECT CAST(total AS DECIMAL(12,2)) / 3 AS split_share FROM orders;

Interviewer follow-up: "When is FLOAT/DOUBLE actually the right choice?" → For scientific or statistical data where approximate values and huge ranges are fine — sensor readings, measurements, ML features, geo-coordinates. The rule is simple: exact values (money, counts) -> DECIMAL/INTEGER; approximate values (measurements) -> FLOAT/DOUBLE.

Key Takeaway — Advanced Real-World Pack

These 40 advanced questions (81-120) go beyond definitions into what real project interviews test: predicting tricky output, debugging slow production queries, writing complex analytics SQL, taming transactions and locks, designing schemas that scale, and dodging silent data-integrity disasters. The pattern across every answer is the same: understand WHY the database behaves as it does, name the trade-off, and back it with a concrete fix. That reasoning is what marks a senior engineer.