Asher Cohen
Back to posts

Floating-Point Numbers in JavaScript

Complete guide to floating-point arithmetic, precision issues, and best practices in JavaScript

Introduction

Floating-point numbers are how JavaScript represents decimal values. Understanding how they work — and their limitations — is essential for avoiding subtle bugs in calculations, especially when dealing with money, scientific data, or precise measurements.

What Are Floating-Point Numbers?

A floating-point number includes a decimal point followed by at least one number:

let f1 = 12.5;
let f2 = 0.3;   // Preferred over .3
let f3 = 200.00; // Interpreted as integer 200

JavaScript uses the IEEE 754 double-precision (64-bit) standard for all numbers. This means every number in JavaScript is a floating-point number under the hood — there are no true integers.

Why 200.00 Becomes 200

JavaScript converts floating-point numbers to integers when the value is a whole number. This saves memory since floating-point values use twice as much memory as integer values.

console.log(200.00);        // 200
console.log(typeof 200.00); // "number"
console.log(Number.isInteger(200.00)); // true

Scientific Notation (e-notation)

JavaScript supports e-notation for very large or small numbers:

let large = 2.17e6;   // 2,170,000
let small = 2.17e-6;  // 0.00000217
let avogadro = 6.022e23; // 6.022 × 10²³

console.log(2.17e6);  // 2170000
console.log(2.17e-6); // 0.00000217

Number Limits

JavaScript provides constants for the minimum and maximum representable values:

console.log(Number.MAX_VALUE); // 1.7976931348623157e+308
console.log(Number.MIN_VALUE); // 5e-324 (smallest positive number)

// Beyond these limits
console.log(Number.MAX_VALUE + Number.MAX_VALUE); // Infinity
console.log(-Number.MAX_VALUE - Number.MAX_VALUE); // -Infinity

Safe Integer Range

For integer operations, JavaScript has safe limits:

console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 (2⁵³ - 1)
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991

// Beyond safe integers, precision is lost
console.log(9007199254740992 === 9007199254740993); // true!

The Famous 0.1 + 0.2 Problem

The most well-known floating-point issue in JavaScript:

console.log(0.1 + 0.2);        // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false

Why This Happens

In binary (base-2), numbers like 0.1 and 0.2 are repeating fractions — just like 1/3 is in decimal (0.333...). The computer can only store a finite number of bits, so these values are approximations.

// What's actually stored
console.log(0.1.toFixed(20)); // "0.10000000000000000555"
console.log(0.2.toFixed(20)); // "0.20000000000000001110"

Solutions

// ✅ Use toFixed() for display
console.log((0.1 + 0.2).toFixed(1)); // "0.3"

// ✅ Use epsilon for comparisons
const epsilon = Number.EPSILON; // ~2.22e-16
console.log(Math.abs(0.1 + 0.2 - 0.3) < epsilon); // true

// ✅ Work with integers (multiply by 100 for cents)
console.log((10 + 20) / 100); // 0.3

// ✅ Use libraries for precise math
// decimal.js, big.js, bignumber.js

Precision and Rounding

toFixed()

Rounds to a specified number of decimal places, returns a string:

console.log((1.2345).toFixed(2)); // "1.23"
console.log((1.2399).toFixed(2)); // "1.24"
console.log((1.5).toFixed(0));    // "2" (rounds up)

toPrecision()

Formats a number to a specified total length:

console.log((123.456).toPrecision(4)); // "123.5"
console.log((0.000123).toPrecision(2)); // "0.00012"
console.log((123456).toPrecision(3));   // "1.23e+5"

Math.round(), Math.floor(), Math.ceil()

console.log(Math.round(1.4));  // 1
console.log(Math.round(1.5));  // 2
console.log(Math.floor(1.9));  // 1
console.log(Math.ceil(1.1));   // 2

// Round to specific decimal places
const roundTo = (num, decimals) => {
  const factor = 10 ** decimals;
  return Math.round(num * factor) / factor;
};

console.log(roundTo(1.2345, 2)); // 1.23

Special Number Values

Infinity and -Infinity

console.log(1 / 0);          // Infinity
console.log(-1 / 0);         // -Infinity
console.log(Infinity > 999); // true
console.log(Infinity + 1);   // Infinity

// Check for infinity
console.log(isFinite(Infinity));  // false
console.log(isFinite(100));       // true

NaN (Not a Number)

console.log(0 / 0);           // NaN
console.log("hello" * 3);     // NaN
console.log(Math.sqrt(-1));   // NaN

// NaN is not equal to anything, including itself
console.log(NaN === NaN);     // false

// ✅ Use Number.isNaN() to check
console.log(Number.isNaN(NaN));      // true
console.log(Number.isNaN("hello"));  // false (not NaN, just a string)

// ❌ Avoid global isNaN() - it coerces
console.log(isNaN("hello"));  // true (misleading!)

Negative Zero

console.log(-0);          // -0
console.log(-0 === 0);    // true
console.log(1 / -0);      // -Infinity
console.log(1 / 0);       // Infinity

// Object.is() can distinguish -0 from 0
console.log(Object.is(-0, 0)); // false

Working with Money

Never use floating-point for financial calculations:

// ❌ Floating-point money - dangerous!
let balance = 0.10;
balance += 0.20;
console.log(balance); // 0.30000000000000004

// ✅ Work in cents (integers)
let balanceCents = 10;  // $0.10
balanceCents += 20;     // $0.20
console.log(balanceCents / 100); // 0.3

// ✅ Use Intl.NumberFormat for display
const formatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
});
console.log(formatter.format(balanceCents / 100)); // "$0.30"

BigInt for Large Integers

For integers beyond Number.MAX_SAFE_INTEGER, use BigInt:

// Create BigInt with n suffix
const big = 9007199254740993n;
console.log(big); // 9007199254740993n

// Operations with BigInt
console.log(big + 1n); // 9007199254740994n

// Cannot mix BigInt and Number
// console.log(big + 1); // TypeError

// Convert between types
console.log(Number(big)); // 9007199254740993 (may lose precision)
console.log(BigInt(100)); // 100n

Common Pitfalls

1. Equality Comparisons

// ❌ Direct equality with floating-point
if (0.1 + 0.2 === 0.3) {
  // Never executes!
}

// ✅ Use epsilon comparison
const areEqual = (a, b, epsilon = Number.EPSILON) => {
  return Math.abs(a - b) < epsilon;
};

console.log(areEqual(0.1 + 0.2, 0.3)); // true

2. parseInt() with Large Numbers

// ❌ parseInt loses precision on large numbers
console.log(parseInt("9007199254740993")); // 9007199254740992 (wrong!)

// ✅ Use BigInt
console.log(BigInt("9007199254740993")); // 9007199254740993n

3. Accumulating Small Values

// ❌ Accumulating floating-point errors
let sum = 0;
for (let i = 0; i < 1000; i++) {
  sum += 0.001;
}
console.log(sum); // 1.0000000000000007 (not exactly 1)

// ✅ Accumulate as integers, divide at the end
let sumCents = 0;
for (let i = 0; i < 1000; i++) {
  sumCents += 1; // 0.001 × 1000
}
console.log(sumCents / 1000); // 1

4. parseInt() with Leading Zeros

// ❌ parseInt with radix issues
console.log(parseInt("08")); // 8 (but was 0 in old engines!)

// ✅ Always specify radix
console.log(parseInt("08", 10)); // 8
console.log(parseInt("0xFF", 16)); // 255

Best Practices

Do's

// ✅ Use Number.EPSILON for comparisons
const isClose = (a, b) => Math.abs(a - b) < Number.EPSILON;

// ✅ Use integers for money (cents)
const price = 1999; // $19.99

// ✅ Use toFixed() for display only
console.log(price.toFixed(2)); // "1999.00"

// ✅ Use BigInt for large integers
const bigId = 9007199254740993n;

// ✅ Use Number.isFinite() and Number.isNaN()
Number.isFinite(value);
Number.isNaN(value);

Don'ts

// ❌ Don't use == with floating-point
if (result == 0.3) { /* ... */ }

// ❌ Don't use floating-point for money
let price = 19.99;

// ❌ Don't use parseInt without radix
parseInt("08");

// ❌ Don't use global isNaN()
isNaN("hello"); // true (misleading)

// ❌ Don't mix BigInt and Number
bigInt + number; // TypeError

Summary Table

ConceptExampleNote
Floating-point0.1 + 0.2Not exactly 0.3
EpsilonNumber.EPSILON~2.22e-16
Max valueNumber.MAX_VALUE~1.79e+308
Min valueNumber.MIN_VALUE5e-324
Safe integerNumber.MAX_SAFE_INTEGER9007199254740991
Infinity1 / 0Positive infinity
NaN0 / 0Not a number
BigInt9007199254740993nArbitrary precision integers

Conclusion

Floating-point numbers are powerful but require careful handling:

  • Never use them for money — work in cents (integers)
  • Use epsilon comparisons — never === for floating-point results
  • Use BigInt for integers beyond 2⁵³ - 1
  • Use toFixed() for display, not for calculations
  • Understand the limitsMAX_VALUE, MIN_VALUE, MAX_SAFE_INTEGER

Master these concepts and you'll avoid the most common numerical bugs in JavaScript.

#javascript #numbers #floating-point #precision #math #best-practices