Javascript Numbers 101: From Basics to Precision Without the Headaches

Table of contents

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
0
s and1
s, prefixed with0b
:
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 with0x
:
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 by1
.Pre-increment (
++x
): increment by1
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'sNaN
:
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 exactlyNaN
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!β‘
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