Float Equality

ID

javascript.float_equality

Severity

low

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

JavaScript

Tags

reliability, suspicious-code

Description

Reports an equality test — ==, !=, === or !== — with a floating-point literal as one of its operands.

The rule fires only when a fractional literal is written out. JavaScript has one numeric type, so nothing in a === b says the values are fractional; an operand-typed trigger would need type inference the engine does not have for most JavaScript, and guessing would report every equality in the language. A literal spelled with a decimal point, or with a negative exponent, is the one form that says in the source itself that a fractional value is being compared.

The operand has to be the literal, optionally signed: x === -0.5 is reported, x === scale(0.5) is not — there the literal is an argument and the compared value is whatever the call returns. 1e3 is an integer written compactly and is not reported; 1e-3 is.

Rationale

// Bad
if (0.1 + 0.2 === 0.3) {   // never true
  applyDiscount();
}

if (total === 1.0) {       // false when total is 0.3 + 0.7
  finish();
}
  • Most decimal fractions have no exact binary representation, so a value that "should" be 0.3 after arithmetic usually is not. 0.1 + 0.2 is 0.30000000000000004, and 0.3 + 0.7 is 0.9999999999999999.

  • The comparison reads as an equality but is really a test for one particular accumulation of rounding error. It can pass on one input scale and fail on another with no change to the code.

  • Integer-valued float literals like 1.0 are reported too. They are exactly representable themselves, but the accumulated value on the other side of the operator is what carries the error — and writing the decimal point is the author saying the quantity is fractional.

Remediation

Compare the absolute difference against a tolerance chosen for the quantity being measured.

// Good
const TOLERANCE = 1e-9;

if (Math.abs(0.1 + 0.2 - 0.3) < TOLERANCE) {
  applyDiscount();
}

Number.EPSILON is the right tolerance only for values near 1: it is the gap between 1 and the next representable double, so it is far too small for large magnitudes and needlessly tight for small ones. For a relative comparison, scale it: Math.abs(a - b) ⇐ Number.EPSILON * Math.max(Math.abs(a), Math.abs(b)).

When the quantity is really discrete — money, counts, percentages in basis points — the better fix is to stop using floats for it. Hold the value as an integer of the smallest unit (cents rather than euros) and the equality becomes exact.

© 2026 Xygeni. All rights reserved