# Decimal amounts stored as integers (cents), never floats

- **Pattern:** `ab-001438` (`finserv-form-validation.currency-amount.integer-cents-storage`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001438
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001438)

## Why it matters

IEEE 754 floating-point arithmetic cannot represent most decimal fractions exactly. Storing `19.99` as a float in PostgreSQL or MongoDB introduces rounding errors that compound across thousands of transactions — a classic fintech data-integrity failure cited under CWE-681 (Incorrect Conversion Between Numeric Types) and CWE-682 (Incorrect Calculation). At scale, accumulated errors produce off-by-one-cent discrepancies in statements, reconciliation failures, and potential regulatory exposure under GAAP precision requirements. Integer cent storage eliminates the issue entirely: `1999` is exact in every language and database type system.

## Severity rationale

Critical because floating-point accumulation in monetary storage produces unbounded rounding drift that corrupts financial records and triggers compliance failures.

## Remediation

Change all monetary database columns to integer types and add an explicit cents-conversion step in the API layer before any write. Reference `prisma/schema.prisma` for schema changes.

```prisma
// prisma/schema.prisma
model Transaction {
  id          String @id @default(cuid())
  amountCents Int    // never Float or Decimal
  currency    String
}
```

```ts
// src/lib/currency.ts
export function toCents(decimalString: string): number {
  return Math.round(parseFloat(decimalString) * 100);
}
export function fromCents(cents: number): string {
  return (cents / 100).toFixed(2);
}
```

## Detection

- **ID:** `integer-cents-storage`
- **Severity:** `critical`
- **What to look for:** Enumerate all database columns or document fields that store monetary values. For each, quote the column type from the schema file (e.g., `amount DECIMAL(10,2)` or `amountCents Int`). Count the total monetary columns and classify each as integer-safe (bigint, integer, int) or float-unsafe (float, double, decimal, numeric, real). Report the ratio: "X of Y monetary columns use integer storage."
- **Pass criteria:** At least 100% of monetary columns use integer types (bigint, integer, or int) storing values in cents. No more than 0 monetary columns use float, double, or decimal types. Form inputs accept decimal notation (e.g., "19.99") but a conversion to integer cents exists before storage. Report even on pass: "X of Y monetary columns store integer cents."
- **Fail criteria:** Any monetary column uses float, double, decimal, or numeric type, or no conversion from decimal input to integer cents exists in the API layer.
- **Do NOT pass when:** The schema uses `DECIMAL(10,2)` or `Decimal` — these still carry floating-point semantics in many ORMs. Only true integer types count as pass.
- **Skip (N/A) when:** The project has no financial data storage (no database schema found, or 0 monetary columns detected).
- **Detail on fail:** Identify the storage pattern. Example: `"Database schema stores amount as DECIMAL(10,2) — prone to floating-point rounding errors. Should be stored as INTEGER (cents)."` or `"API accepts '19.99' and stores it directly as float in MongoDB without converting to cents"`
- **Cross-reference:** The Database Schema & Migrations audit covers column type choices and migration safety that complement financial storage patterns.
- **Remediation:** Floating-point math causes unpredictable rounding errors in financial systems. Always store monetary amounts as integers representing the smallest currency unit. In `prisma/schema.prisma` or `src/db/schema.ts`, change column types:

  **Database schema (Prisma) in `prisma/schema.prisma`:**
  ```ts
  model Transaction {
    id      String   @id @default(cuid())
    amountCents Int  // Store as integer cents, never float
    currency String  // e.g., "USD", "EUR"
  }
  ```

  **Conversion logic in `src/lib/currency.ts`:**
  ```ts
  // Input: "19.99" (user-entered decimal string)
  const amountCents = Math.round(parseFloat(userInput) * 100);
  // Now store amountCents (1999) in database as integer

  // Display: Convert back to decimal when showing to user
  const displayAmount = (amountCents / 100).toFixed(2); // "19.99"
  ```

## External references

- cwe CWE-681
- cwe CWE-682

Taxons: data-integrity

HTML version: https://auditbuffet.com/patterns/ab-001438
