Javascript Numbers 101: From Basics to Precision Without the Headaches

Sangy KSangy K
9 min read

We've already met numbers in Javascript when we first dabbled with arithmetic and variables β€” performing simple calculations, checking types, and bumping into the occasional quirky behaviour πŸ™ƒ.

Now, it's time to dive deeper into the world of numbers in Javascript 🀿.

Numbers in Javascript can feel simple at first glance β€” until 0.1 + 0.2 throws you a curveball 🀾.

In this article, we'll recap what we know and explore the essential concepts behind Javascript numbers β€” how they work, why they sometimes misbehave, and what we can do to keep our math clean and reliable.

Don't worry, by the end of this article, we'll know exactly what's going on under the hood, and we'll be well-prepared to avoid the common gotchas that trip most of us up.

Let's dig in! πŸš€

πŸ”’ One Number Type to Rule Them All

Javascript treats numbers differently from other languages.

Many programming languages categorise numbers into distinct types β€” such as int, float, and double β€” among others.

Javascript keeps things refreshingly simple. There's just one unified Number type β€” no separate integers (whole numbers) or floating-point (decimal) types to juggle 🀹.

No need to declare types separately β€” Javascript decides at runtime.

This means whether we write:

let count = 42; // integer
let price = 19.99; // floating-point number
let negative = -42; // negative number

Under the hood, Javascript uses the IEEE 754 double-precision 64-bit floating-point format for all numbers.

That's a fancy way of saying we can represent a wide range of values β€” from tiny decimals to gigantic numbers β€” with about 15–17 digits of precision. No need to declare types separately β€” Javascript decides at runtime. πŸ’¨

Why does this matter?

Because Javascript stores all numbers as 64‑bit floating‑point values, we get a more straightforward and flexible system β€” we don't have to worry about picking between int, float, and double like in other languages. This gives us more control over our code. πŸŽ›οΈ

Coding becomes faster, but there's a catch.

Floating‑point numbers can't always represent decimals exactly. For example:

console.log(0.1 + 0.2); // 0.30000000000000004 😬

This happens because binary floating‑point can't store every decimal number with perfect accuracy.

There's another consequence: since Javascript uses this single type for all numbers, it can't safely represent integers beyond a certain range β€” specifically anything larger than Number.MAX_SAFE_INTEGER (Β±9,007,199,254,740,991).

Go beyond that, and your calculations may lose precision:

console.log(9007199254740991 + 1); // 9007199254740992 βœ…
console.log(9007199254740991 + 2); // 9007199254740992 ❌

We'll delve deeper into precision issues and safe integers later in the article, along with an explanation of how BigInt helps when we require even larger numbers.

Declaring and Using Numbers

We declare numbers the same way we declare other variables β€” with let, const, or var:

let age = 25; // integer
const pi = 3.14159; // constant value
var score = 100; // old-school var

Numeric Literals

In Javascript, we can write the same number in different bases (number systems):

  • Decimal – the regular numbers we use every day, like 42.

  • Binary – base-2 numbers, uses only 0s and 1s, prefixed with 0b:

let bin = 0b101010; // 42 in binary
  • Octal – base‑8 numbers, prefixed with 0o:
let oct = 0o52; // 42 in octal
  • Hexadecimal – base‑16 numbers (0‑9 + A‑F), prefixed with 0x:
let hex = 0x2A; // 42 in hex

Most of the time, we stick with decimals. The others come in handy for tasks such as working with colors (0xff00ff), binary data, or low-level programming.

Readability with underscores

Starting with ES2015, we can make long numbers more readable using underscores:

let population = 7_900_000_000; // easier to read than 7900000000

Underscores have no effect on the actual value β€” they're just for our eyes.

Doing Math: Arithmetic Operations

We can use all the usual math operators:

  • + (addition)

  • - (subtraction)

  • * (multiplication)

  • / (division)

  • % (modulo β€” gives the remainder)

  • ** (exponentiation)

let price = 50;
let taxRate = 0.2;
let total = price + (price * taxRate); // 60
let remainder = 10 % 3; // 1
let squared = 5 ** 2; // 25

Increment and Decrement

++ and -- adjust a number by 1.

  • Post-increment (x++): return the current value, then increment by 1.

  • Pre-increment (++x): increment by 1 first, then return the new value.

let x = 5;
console.log(x++); // 5 (returns old value)
console.log(x); // 6
console.log(++x); // 7 (increments first)

Floating-Point Precision: The Infamous 0.1 + 0.2 Problem

Here's one of Javascript's most famous quirks:

console.log(0.1 + 0.2 === 0.3); // false πŸ˜…

Why does this happen?

We have already had a glimpse of it in the article, "Why Understanding Javascript's Type System Early Will Save You Later".

Javascript stores all numbers as 64-bit binary floating-point values (IEEE 754 standard).

The catch? Some numbers that are simple in decimal β€” like 0.1 or 0.2 β€” can't be written exactly in binary.

Think of it like trying to write 1/3 in decimal:

1 Γ· 3 = 0.3333... // (and it never ends!)

In binary, 0.1 and 0.2 also become endless fractions. When we add them, the tiny rounding errors add up, and the result is not exactly 0.3, but rather something like 0.30000000000000004.

How do we deal with it?

  • Format for display:

let sum = 0.1 + 0.2;
console.log(sum.toFixed(2)); // "0.30"

toFixed(2) rounds the number for display purposes.

  • Rounding correctly:

Math.round((0.1 + 0.2) * 100) / 100; // 0.3
  • Compare with tolerance:

Instead of checking if two numbers are exactly equal, allow for a tiny error margin using Number.EPSILON:

function isEqual(a, b) {
  return Math.abs(a - b) < Number.EPSILON;
}

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

Takeaway:

This isn't a bug β€” it's how computers represent numbers.

Whenever accuracy is critical (like in currency calculations), we must handle numbers carefully, often by rounding or working in integers (e.g., cents instead of dollars).

Special Numeric Values

Javascript's Number type comes with three special values that pop up more often than we'd expect:

  • Infinity β†’ A number too big to handle, usually when we go beyond the maximum limit or divide by zero.

  • -Infinity β†’ The negative version of Infinity.

  • NaN (Not-a-Number) β†’ The result of a calculation that doesn't make sense numerically.

Examples:

console.log(1 / 0); // Infinity β†’ division by zero
console.log(-1 / 0); // -Infinity β†’ negative division by zero
console.log(0 / 0); // NaN β†’ "undefined" math
console.log(Math.sqrt(-1)); // NaN β†’ no real square root for negative numbers

Why do these exist?

Javascript uses the IEEE 754 standard(huh, again! 🀨), which defines these values to handle "edge cases" gracefully without crashing the program. Instead of throwing an error, it gives a value that indicates what went wrong.

Where do we see them?

  • Infinity often appears when working with large loops, complex calculations, or when dividing by zero accidentally.

  • NaN shows up when we do operations with invalid data, like:

console.log("hello" * 2); // NaN

Here, multiplying a string that isn't a number gives NaN.

Pro Tip:

We can check for NaN using Number.isNaN(), as NaN === NaN is false (another fun quirk).

We'll encounter these often when performing math-intensive operations or parsing input.

Type Checking Numbers

Javascript provides us with a few tools to check if something is a number, but they behave differently, so it's important to know when to use which.

typeof

The quickest way:

console.log(typeof 42); // "number"
console.log(typeof NaN); // "number" 🀯

Yes, NaN (Not-a-Number) is still of type "number".

That's because NaN is a special numeric value representing an invalid number β€” but it's still technically part of the Number type.

We've already covered typeof in our introduction to Data Types, so let's move on to more specific checks.

isNaN() vs Number.isNaN()

This is where many beginners get tripped up.

  • isNaN() tries to convert whatever we pass into a number first, then checks if it's NaN:
console.log(isNaN("hello")); // true 😬

Why true? Because "hello" can't be converted to a number, so isNaN() says, "Yep, that's NaN."

  • Number.isNaN() is stricter β€” it checks if the value is exactly NaN without converting it:
console.log(Number.isNaN("hello")); // false βœ…
console.log(Number.isNaN(NaN)); // true

We prefer Number.isNaN() because it doesn't perform any unexpected type conversions. It provides reliable results when we need to check for NaN values.

Converting to and from Strings

We often pull numeric values from inputs or APIs as strings. To work with them, we usually need to convert between strings and numbers:

From string to number:

let str = "42";
let num1 = Number(str); // 42
let num2 = parseInt("42px"); // 42
let num3 = parseFloat("3.14"); // 3.14

parseInt() and parseFloat() are forgiving (they'll stop parsing at the first non-numeric character), whereas Number() requires the entire string to be numeric.

From number to string:

let n = 123;
console.log(n.toString()); // "123"
console.log(n.toString(16)); // "7b" (hexadecimal)

More on strings in "Javascript Strings 101: How to Declare, Create, and Work with Text" and its methods in "Essential String Methods in Javascript: Trimming, Searching, Combining Text and Many More".

Number Properties Worth Knowing

Just like strings have a String wrapper, numbers in Javascript also have a Number wrapper object. It provides constants (fixed values) and helper methods we can use when working with numbers. Some of the most important properties are:

Number.MAX_VALUE

The largest positive number Javascript can represent:

console.log(Number.MAX_VALUE); // 1.7976931348623157e+308

Go beyond this, and the result turns into Infinity.

Number.MIN_VALUE

The smallest positive number (closest to zero) that Javascript can represent:

console.log(Number.MIN_VALUE); // 5e-324

Anything smaller underflows to 0.

Number.EPSILON

The smallest difference Javascript can distinguish between two numbers:

console.log(Number.EPSILON); // 2.220446049250313e-16

We often use this to check for "close enough" equality with floating-point numbers.

Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER

The safe range for integers where Javascript guarantees exact precision:

console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 (2^53 - 1)
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991

Outside this range, numbers can't be stored precisely, and comparisons may fail.

Numeric Coercion

Javascript sometimes automatically converts values to numbers when performing operations β€” this is called type coercion.

Example with multiplication:

console.log("5" * 2); // 10 β€” "5" is converted to number

Example with addition:

console.log("5" + 2); // "52" β€” "+" does string concatenation if one side is a string

We can force a value into a number using unary plus (+):

let str = "42";
console.log(+str); // 42 (number)

⚠️ Gotcha: If conversion fails, we get NaN:

console.log(+"hello"); // NaN

Final Thoughts

We've covered:

  • The one Number type in Javascript

  • Declaring and using numbers in various formats

  • Floating-point quirks and safe handling

  • Type checking, conversions, and special values

Numbers are the foundation in most programming languages. Now that we understand them, we can start building more reliable and accurate logic in our code.

Next Up: Deep Dive into Javascript Numbers

In the following article, we'll delve into advanced number handling, including precision tricks, Math and Date objects, currency calculations, performance optimisations, and more.

Stay tuned β€” we're just getting started with the real power of numbers!⚑

0
Subscribe to my newsletter

Read articles from Sangy K directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sangy K
Sangy K

An Introvert Coder | Trying to be a Full-stack Developer | A Wannabe Content Creator