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
| Concept | Example | Note |
|---|---|---|
| Floating-point | 0.1 + 0.2 | Not exactly 0.3 |
| Epsilon | Number.EPSILON | ~2.22e-16 |
| Max value | Number.MAX_VALUE | ~1.79e+308 |
| Min value | Number.MIN_VALUE | 5e-324 |
| Safe integer | Number.MAX_SAFE_INTEGER | 9007199254740991 |
| Infinity | 1 / 0 | Positive infinity |
| NaN | 0 / 0 | Not a number |
| BigInt | 9007199254740993n | Arbitrary 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 limits —
MAX_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