Data Bugs

Decimal Precision Lost on Save

A product price entered as 19.99 is stored and returned as 20.0 after being saved through the application. The database column is defined as FLOAT — an IEEE 754 approximate numeric type — rather than DECIMAL or NUMERIC, which are exact types. Because 19.99 cannot be represented exactly in binary floating-point, the stored value differs from the input.

MediumIntermediateAPI testingManual testingDatabase testing

// UNDERSTAND

// Symptoms

  • POST /api/products with price: 19.99 returns a record where price is 20.0 or 19.990000000000002
  • Monetary totals calculated from stored prices are wrong by small but consistent amounts
  • A price entered by the user does not round-trip correctly: the value shown after saving differs from what was submitted
  • Multiple small rounding errors accumulate in invoice or order total calculations

// Root Cause

  • The database column holding the price is defined as FLOAT or DOUBLE PRECISION — IEEE 754 binary floating-point types. These types cannot represent most decimal fractions exactly; 19.99 stored as a FLOAT is retrieved as the closest representable binary approximation, which differs from 19.99.
  • The application layer converts the incoming price string to a JavaScript Number (also an IEEE 754 double) before sending it to the database, compounding the precision loss before the value is even written.

// Where It Appears

  • Product and pricing tables where monetary values are stored as FLOAT instead of DECIMAL(10,2)
  • Order total, invoice, and tax calculation fields that accumulate floating-point rounding errors
  • Any column storing percentages, exchange rates, or measurement values that require exact decimal representation
  • APIs generated from an ORM where the column type was not explicitly set and defaulted to a floating-point type

// REPRODUCE & TEST

// How to Reproduce

  1. 01Send POST /api/products with body { "name": "Test Item", "price": 19.99 } and a valid bearer token; note the returned product ID (e.g. 301)
  2. 02Send GET /api/products/301 and read the price field in the response
  3. 03Compare the returned price against the submitted value 19.99 — any difference confirms precision loss

// Test Data Needed

  • Permission to create products via the API
  • A decimal value that cannot be represented exactly as IEEE 754 — 19.99, 0.1, 0.7, and 4.2 are reliable test cases

// Manual Testing Ideas

  • Enter a price of 19.99 in the product creation form, save it, then navigate to the product detail page and confirm the displayed price is exactly 19.99
  • Test with several decimal values known to be problematic in binary floating-point: 0.1, 0.7, 4.2, 10.05 — any that round-trip incorrectly confirm the column type issue
  • Create an order with multiple line items priced at values such as 0.10 each, check the computed total, and verify it equals the expected sum
  • Check the database schema directly (if accessible) to confirm the column type — FLOAT or DOUBLE indicates the bug; DECIMAL(10,2) or NUMERIC indicates the correct type

// API Testing Ideas

  • Send POST /api/products with body { "name": "Test Item", "price": 19.99 }; capture the returned product id (301)
  • Send GET /api/products/301 and assert that the price field equals exactly 19.99 — not 20.0 or 19.990000000000002
  • Repeat with price: 0.1 and assert GET returns exactly 0.1
  • Repeat with price: 10.05 and assert GET returns exactly 10.05
  • If any GET response returns a value that differs from the submitted value, the precision bug is confirmed

// Automation Idea

Send POST /api/products with each of the following prices: 19.99, 0.1, 0.7, 10.05. For each, capture the returned product ID and immediately send GET /api/products/{id}. Assert the price field in the GET response is strictly equal (===) to the submitted value. Any inequality confirms floating-point precision loss in storage or retrieval.

// Expected Result

A price of 19.99 submitted via POST /api/products is returned as exactly 19.99 by a subsequent GET /api/products/301, with no rounding or approximation.

// Actual Result (Example)

POST /api/products with { "price": 19.99 } returns id: 301. GET /api/products/301 returns { "price": 20.0 }. The submitted value 19.99 was rounded during storage because the database column is defined as FLOAT.

// REPORT IT

Example Bug Report

Title
Product price 19.99 stored and returned as 20.0 — FLOAT column causes precision loss
Severity
Medium
Environment
Staging environment Postman Valid bearer token Endpoint: POST /api/products and GET /api/products/301
Steps to Reproduce
  1. 01Send POST /api/products with body { "name": "Test Item", "price": 19.99 } and a valid bearer token; note the returned id (301)
  2. 02Send GET /api/products/301 and read the price field in the response
  3. 03Compare the returned value against the submitted value 19.99
Expected Result
GET /api/products/301 returns price: 19.99 — the exact submitted value.
Actual Result
GET /api/products/301 returns price: 20.0. The value 19.99 was rounded on save because the database column is typed as FLOAT.
Impact
Monetary values are stored and displayed incorrectly. Price discrepancies erode user trust and can cause billing errors. In financial applications, even small floating-point errors accumulate across millions of transactions.

// RELATED