Skip to main content

MY.L5.FLOAT_MONEY — Money in a floating-point column

  • Category: safety
  • Level: 5
  • Confidence: deterministic — the trigger is the column NAME, and the finding carries that caveat in its own text rather than in this field
  • Downtime class: none
  • Stability: stable
  • Suites: audit
  • Applies to: MySQL 8.4
  • Not transferable to MariaDB: this page describes MySQL 8.4 behavior. MariaDB answers the same driver and does not share these semantics, so SQLens refuses it outright rather than reasoning about it — see drivers/unsupported.

What goes wrong, and why nobody notices at the time

A binary float cannot represent 0.10 exactly. Add it ten times and the total is not 1.00 — it is near enough that every screen shows 1.00 and every comparison against 1.00 fails.

The error arrives as one cent on a statement, months after the schema decision, and whoever finds it has no route back to the column that caused it. That is the whole argument: the failure is small, late, and untraceable.

DECIMAL(19, 4) stores the digits, so the arithmetic is the arithmetic of the invoice.

Flagged

Schema::create('invoices', function (Blueprint $table) {
$table->id();
$table->double('invoice_total');
});

Preferred

Schema::create('invoices', function (Blueprint $table) {
$table->id();
$table->decimal('invoice_total', 19, 4);
});

Storing minor units in an integer — price_cents bigint — is the other correct answer, and is never reported.

This is a heuristic, and the finding says so

Nothing in a catalog says a column holds money. The only available signal is the name, which is a guess about intent — so the finding admits to being a guess rather than presenting itself as a fact.

The dictionary that backs it splits terms into two confidence levels, and the split is the point:

  • strongprice, invoice_total, unit_price, rechnungsbetrag, nettopreis. Money on its own.
  • weakrate, value, total, balance, wert, summe. Ordinary language that is sometimes money. rate is an interest rate and a frame rate; value names a hundred things. A rule that fired on those would be noise, and noise is how a team learns to skip a whole level band.

Matching is on whole underscore-separated segments, never substrings: rating is not rate, and pricing is not price.

What a weak term needs before anything is reported

Exactly two second signals count, and both are structural facts about the table rather than further guesses about the domain:

  1. A sibling currency column (currency, currency_code, waehrung). A table that stores a currency stores money in it — the strongest evidence a catalog can offer, because it is somebody else's explicit statement.
  2. A cents segment in the name. balance_cents is a minor-unit amount whatever balance means elsewhere.

A suffix of _amount is deliberately not a second signal, though it looks like one: amount is itself a weak term, so accepting it would let a weak term vouch for itself and the confidence split would stop meaning anything.

Tuning it for your schema

Both directions, without forking the package:

'audit' => [
'money_columns' => [
// Added terms count as STRONG — you have stated the intent the dictionary can only guess at.
'extra' => ['settlement', 'payout'],
// Removed from BOTH confidence levels, so an ignore keeps working if a term is reclassified.
'ignore' => ['rate'],
],
],

Changing the type rewrites the table

Converting to DECIMAL(19, 4) is not a metadata change — every row is rewritten. The lint suite classifies that operation and carries its downtime class; this audit finding carries none, because an observation about a state has no duration.

Sources

The fix material this rule carries

A finding from this rule carries a payload whose strategy is none: this rule has looked, and there is no safe standard sequence. That is a conclusion rather than an omission — a finding with no payload at all says only that nobody wrote one.

The payload is material for you or an agent to apply. SQLens writes no migration and runs no DDL. See the remediation payload for every field, the placeholder semantics, and the version rules a consumer has to follow.