Five JavaScript Number Traps That Break Production Without a Single Error
These traps corrupt financial data, break API lookups, and produce wrong UI state without a single console error or exception. A developer who relies on toFixed for currency display or parses a 19-digit order ID as a number will ship broken behavior that only surfaces in production, often as incorrect charges or missing records.
`toFixed` rounds against the binary representation, not the decimal literal, so (1.005).toFixed(2) returns "1.00" instead of "1.01". JSON.parse silently corrupts integers beyond 2^53-1, rewriting the last digits of snowflake IDs with no warning. NaN's type is 'number', it is not equal to itself, and Number('') returns 0, turning empty form fields into zero-value calculations that cascade through multiplication.
parseInt(0.0000005) evaluates to 5 because the argument is first coerced to the string "5e-7", and parsing stops at 'e'. Floating-point errors accumulate across loops, so splitting 100 yuan three ways and summing the rounded parts yields 99.99, not 100. The root cause across all five traps is the same: JavaScript's IEEE 754 double-precision floats silently produce wrong results instead of throwing exceptions.
The fixes are concrete. Use Intl.NumberFormat for display rounding, not toFixed. Treat all IDs as strings end-to-end, or use BigInt with a custom JSON reviver. Validate numeric inputs with a safe-number function that explicitly handles empty strings. Store and calculate all monetary values as integer cents, distributing remainders explicitly. These three habits—string IDs, integer cents, validated inputs—eliminate the entire class of silent number bugs.
The common thread across all five traps is not floating-point imprecision itself, but JavaScript's design choice to silently produce wrong results instead of throwing errors at the boundaries.
toFixed is fundamentally broken for financial display because it operates on the stored binary value, not the decimal literal—a mismatch between the method's stated purpose and its actual behavior.
The parseInt(0.0000005) === 5 case reveals a deeper problem: implicit type coercion chains that are invisible during code review and produce results so counterintuitive they look like cosmic rays.
Integer-cents arithmetic is not a workaround but the correct domain model for money—floating-point was never the right representation, and the 'fix' is simply using the right type.
The fact that NaN !== NaN is specified by IEEE 754 and is not a JavaScript quirk, but its interaction with Number('') returning 0 creates a uniquely dangerous silent-failure path in form handling.