Decimals, Rounding and Precision

JavaScript ES2024 · ✓ verified by execution on 2026-08-07

JavaScript has one Number type rather than separate integer and float types (BigInt is the one exception, added later for arbitrarily large whole numbers). Every value of that type is a double-precision 64-bit float, as defined by the IEEE 754 standard — the same representation most languages use for their double.

That single choice explains nearly everything surprising about numbers in JavaScript, starting with the most notorious example.

javascript · visualize
console.log(typeof 42);
console.log(typeof 3.14);
number
number

The 0.1 + 0.2 Problem

In base 10, 0.1 and 0.2 are exact and their sum is obviously 0.3. Computers store numbers in base 2, where neither value terminates — and the result is not what school arithmetic promises.

Predict the output javascript

Read the code. What exactly will it print? Commit to an answer before you look.

console.log(0.1 + 0.2);
Output
0.30000000000000004

This is not a bug in JavaScript! It is a fundamental property of how computers handle floating point math natively in base-2 (binary). Just as the fraction 1/3 creates an infinite repeating decimal (0.3333...) in base-10, the fractions 1/10 and 1/5 create infinite repeating decimals in binary mathematics.

Because the system only has 64 bits to store the number, it is eventually forced to literally cut the repeating string off and round the final binary digit, which leaves behind a microscopic precision error. When you add the two rounded approximations together, the tiny error becomes visible.

Base-10 (Human Math) 1/10 = 0.1 (Clean ending) 1/3 = 0.33333... (Repeating) Base-2 (Computer Math) 1/2 = 0.1 (Clean ending) 1/10 = 0.000110011... (Repeating) Computers cut off the repeating binary string, losing precision.
Why 0.1 cannot be represented perfectly in memory

Formatting Currency with toFixed()

When dealing with user interfaces, you frequently need to artificially hide these rounding errors and display numbers with a fixed number of decimal places. The .toFixed() method is perfect for this task. However, a major trap exists here: it always returns a formatted string, never a number.

javascript
const num = 0.1 + 0.2;
console.log(typeof num.toFixed(2));
Output
string

If you accidentally execute toFixed() too early in your calculation pipeline, you will unintentionally start concatenating strings instead of performing mathematical addition!

Safe Currency Calculations

Integer arithmetic is exact, up to a limit. Because integers do not have fractional components, they do not suffer from the repeating binary decimal issue.

A common pattern for handling financial currency safely in JavaScript is to scale the monetary amounts up to integers (e.g., converting dollars to cents), safely perform the math, heavily round it, and then confidently divide back down to decimals just before presentation.

javascript
console.log(Math.round((19.99 + 9.99) * 100) / 100);
Output
29.98

Comparing Floats Safely

Because of these microscopic precision errors, you should absolutely never directly compare two computed floating point numbers for strict equality using ===. Instead, best practice dictates checking if the absolute difference between them is smaller than Number.EPSILON, which serves as a standard acceptable margin of error.

javascript
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON);
Output
true

The Maximum Safe Integer

While integers perfectly avoid floating point rounding errors, this mathematical safety boundary eventually ends. Because the 64-bit float format must reserve some of its bits for tracking the exponent and sign, JavaScript physically cannot safely represent integers arbitrarily large.

The strict boundary is precisely Number.MAX_SAFE_INTEGER (which equals exactly $2^53 - 1$). The moment you cross beyond this mathematical boundary, even whole integers start unpredictably losing precision.

javascript
console.log(Number.isSafeInteger(Math.pow(2, 53) - 1));
console.log(Number.isSafeInteger(Math.pow(2, 53)));
Output
true
false

Check yourself

What is the actual behavior regarding this belief: '0.1 + 0.2 evaluates to exactly 0.3 in JavaScript.'?

Reveal answer

Because of IEEE 754 binary floating-point representation, the exact binary representation has repeating decimals. — Because of IEEE 754 binary floating-point representation, the exact binary representation has repeating decimals.

What is the actual behavior regarding this belief: 'toFixed() returns a number rounded to the specified decimal places.'?

Reveal answer

toFixed() returns a string, which can cause unexpected concatenation. — toFixed() returns a string, which can cause unexpected concatenation.

What is the actual behavior regarding this belief: 'JavaScript has a separate Integer and Float type.'?

Reveal answer

All numbers (except BigInt) are double-precision floating point numbers. — All numbers (except BigInt) are double-precision floating point numbers.

What is the actual behavior regarding this belief: 'Math.round(1.005 * 100) / 100 correctly rounds to 1.01.'?

Reveal answer

It rounds to 1.00 because 1.005 in binary is slightly less than 1.005. — It rounds to 1.00 because 1.005 in binary is slightly less than 1.005.

Challenges

Challenge 1 +10 XP

Fix the function ormatPrice so that it returns a string with exactly two decimal places.

javascript
  • Test 1 — expects "29.98\n15.00"
Need a hint? (−25% XP)

Use the oFixed() method.

Show solution (0 XP)
function formatPrice(amount) {
  return amount.toFixed(2);
}
console.log(formatPrice(19.99 + 9.99));
console.log(formatPrice(15));

Challenge 2 +15 XP

A developer used oFixed(2) to format some prices, but now it concatenates them. Fix it.

javascript
  • Test 1 — expects "29.979999999999997"
Need a hint? (−25% XP)

Keep them as raw numbers during calculations.

Show solution (0 XP)
const total = 19.99 + 9.99;
console.log(total);