JavaScript Numbers and Math: Working With Numeric Values

JavaScript has one number type for everything — integers and decimals alike. Learn how numbers really work, the floating-point gotcha that surprises everyone, and the Number and Math tools you'll use to format, round, and calculate.

Published July 30, 202613 min readBy ACY Partner Indonesia
JavaScript numbers and math — calculating and formatting numeric values
300 × 250Ad Space AvailablePlace your ad here

Almost every program does arithmetic somewhere: a cart total, a percentage, a price with tax, a random pick from a list. JavaScript handles all of that with a single number type — and that one decision shapes how numbers behave, including one quirk that catches nearly everyone the first time they see it. In this article you’ll learn how numbers actually work under the hood, the floating-point trap and how to dodge it, and the built-in Number and Math tools you’ll reach for constantly.

You met numbers briefly back in the data types article. Now we’ll go deep enough that numeric bugs stop being mysterious.

One number type for everything

Most languages split numbers into separate types — integers, floats, doubles, and so on. JavaScript keeps it simple: there’s just one type, called number, and it covers whole numbers and decimals alike.

const age = 28;          // a whole number
const price = 19.99;     // a decimal
const negative = -7;     // negative is fine too
const temperature = -3.5;

You don’t declare a separate “integer” or “float” — you just write the value. Under the hood, every JavaScript number is a 64-bit floating-point value (the IEEE 754 “double” format, if you ever want to look it up). That’s convenient, but it’s also the root of the most famous gotcha in the language, which we’ll get to in a moment.

You can also write numbers in a few other notations when it’s handy:

const big = 1_000_000;      // underscores as digit separators (just for readability)
const hex = 0xff;           // hexadecimal → 255
const binary = 0b1010;      // binary → 10
const octal = 0o17;         // octal → 15
const scientific = 1.5e6;   // scientific notation → 1500000

The underscores are purely visual — JavaScript ignores them, so 1_000_000 and 1000000 are the exact same number. They just make long numbers easier to read.

The floating-point gotcha

Here’s the one that trips up every newcomer. Try this:

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

That’s not a bug in JavaScript — it happens in almost every language that uses the same floating-point standard (Python, Java, C, and more). The reason: computers store numbers in binary (base 2), and just like 1/3 can’t be written exactly as a decimal (0.3333…), the value 0.1 can’t be represented exactly in binary. The tiny rounding error is what leaks out at the far end of the decimal.

Never compare decimals with === directly

Because of floating-point rounding, 0.1 + 0.2 === 0.3 is false. Don’t compare decimal results with === and expect exact matches. When you need to check whether two decimals are “equal enough,” compare the difference against a tiny tolerance instead:

const a = 0.1 + 0.2;
const b = 0.3;
console.log(Math.abs(a - b) < Number.EPSILON);   // true

Number.EPSILON is the smallest meaningful gap between numbers JavaScript can tell apart — a ready-made tolerance for exactly this situation.

This matters most with money. Storing prices as floating-point decimals and adding them up can slowly accumulate rounding errors. A common professional trick is to work in the smallest unit instead — store amounts as whole cents (integers) rather than dollars with decimals, then divide by 100 only when you display the value. Integers don’t suffer the same rounding problem.

// Instead of dollars with decimals:
const total = 19.99 + 5.01;   // risk of a rounding tail

// Work in cents (integers), divide at the end:
const totalCents = 1999 + 501;        // 2500
const display = (totalCents / 100).toFixed(2);   // "25.00"

Useful Number methods

The number type comes with handy methods for formatting and inspecting values. The two you’ll use most are toFixed and toString.

toFixed — fix the number of decimals

toFixed(n) rounds a number to n decimal places and returns it as a string — perfect for displaying prices and percentages:

const price = 19.9;
console.log(price.toFixed(2));    // "19.90"  (a string)

const pi = 3.14159;
console.log(pi.toFixed(2));       // "3.14"
console.log((1234.5678).toFixed(0));  // "1235"  (rounds)

toFixed returns a string, not a number

This catches people often: (19.9).toFixed(2) gives you the string "19.90", not the number 19.9. That’s usually what you want for display — it keeps trailing zeros so a price shows as 19.90 and not 19.9. But if you try to do math on the result, JavaScript will treat the + as string joining: "19.90" + 1 becomes "19.901", not 20.9. Format for display after you finish calculating, never before.

toString — convert to other bases

toString() turns a number into a string, and you can pass a base (radix) to convert between number systems:

const n = 255;
console.log(n.toString());     // "255"  (base 10, the default)
console.log(n.toString(16));   // "ff"   (hexadecimal)
console.log(n.toString(2));    // "11111111"  (binary)

This is genuinely useful — converting a number to base 16 is exactly how you’d build a hex color code from RGB values, for example.

toLocaleString — format for humans

toLocaleString() formats a number the way a reader in a given region expects, with the right grouping separators:

const amount = 1234567.89;

console.log(amount.toLocaleString("en-US"));   // "1,234,567.89"
console.log(amount.toLocaleString("id-ID"));   // "1.234.567,89"

// Even better — format as currency directly:
console.log(amount.toLocaleString("id-ID", {
  style: "currency",
  currency: "IDR",
}));   // "Rp1.234.567,89"

This is the right tool for showing money and large counts to users. It handles thousand separators, decimal commas vs. dots, and currency symbols automatically, based on the locale — far better than gluing commas in by hand.

Checking numbers: NaN and Infinity

Numbers have two special values worth knowing, because they show up when math goes sideways.

NaN — Not a Number

NaN means “Not a Number.” You get it when an operation that expects numbers can’t produce a sensible numeric result:

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

NaN has a famously weird property: it is not equal to anything, including itself.

console.log(NaN === NaN);   // false  (!)

So you can’t check for it with ===. Instead, use Number.isNaN():

const result = Number("abc");   // NaN
console.log(Number.isNaN(result));   // true

Use Number.isNaN, not the old global isNaN

There’s an older global function called isNaN() (without the Number. prefix). Avoid it — it first tries to convert its argument to a number, so isNaN("hello") returns true even though the string was never a number to begin with. Number.isNaN() does no conversion: it returns true only for a value that is genuinely the NaN number. Reach for Number.isNaN() and you’ll dodge a class of subtle bugs.

Infinity

Dividing by zero doesn’t crash in JavaScript — it gives you Infinity (or -Infinity):

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

To confirm a value is a real, usable number — not NaN, not Infinity — use Number.isFinite():

console.log(Number.isFinite(42));         // true
console.log(Number.isFinite(Infinity));   // false
console.log(Number.isFinite(NaN));        // false

This is a great guard before trusting the result of a calculation or a parsed value.

Turning strings into numbers

Data from forms, URLs, and APIs often arrives as text — "42", not 42. You’ll constantly need to convert. There are three common ways, and they behave differently.

// 1. Number() — strict, whole-string conversion
console.log(Number("42"));      // 42
console.log(Number("42px"));    // NaN  (the "px" ruins it)
console.log(Number("3.14"));    // 3.14

// 2. parseInt() — reads an integer from the FRONT, stops at junk
console.log(parseInt("42px"));  // 42   (ignores "px")
console.log(parseInt("3.14"));  // 3    (stops at the dot)

// 3. parseFloat() — like parseInt but keeps the decimal
console.log(parseFloat("3.14")); // 3.14
console.log(parseFloat("3.14m"));// 3.14 (ignores "m")

The difference matters. Number() is all-or-nothing: the whole string must look like a number or you get NaN. parseInt() and parseFloat() are more forgiving — they read as much of a number as they can from the start and quietly stop at the first character that doesn’t fit. That’s exactly what you want for something like "42px" (where you mean 42), and exactly what you don’t want when you’d rather catch bad input.

Always pass a radix to parseInt

Make a habit of giving parseInt a second argument — the base — like parseInt("42", 10). Without it, certain inputs (historically, strings starting with 0) could be interpreted in an unexpected base. Modern engines default to base 10 in most cases, but writing parseInt(value, 10) makes your intent explicit and removes any doubt. It costs you four characters and removes a whole category of surprise.

A quick shortcut you’ll see in real code: the unary + operator converts a string to a number, the same way Number() does.

const text = "100";
const num = +text;      // 100, as a number
console.log(num + 5);   // 105

The Math object

For anything beyond basic arithmetic, JavaScript gives you the built-in Math object — a collection of math constants and functions. You don’t create it; it’s always there. You call its methods directly off Math.

Rounding

The most-used Math methods round numbers in different ways:

console.log(Math.round(4.5));   // 5   (nearest integer)
console.log(Math.round(4.4));   // 4
console.log(Math.ceil(4.1));    // 5   (always rounds UP)
console.log(Math.floor(4.9));   // 4   (always rounds DOWN)
console.log(Math.trunc(4.9));   // 4   (just drops the decimal)
console.log(Math.trunc(-4.9));  // -4  (toward zero)

It’s worth keeping these straight: round goes to the nearest, ceil always goes up, floor always goes down, and trunc simply chops off the decimal part without rounding at all. For negative numbers, floor and trunc differ — Math.floor(-4.9) is -5, but Math.trunc(-4.9) is -4.

Common calculations

A handful of Math methods come up again and again:

console.log(Math.abs(-7));        // 7    (absolute value)
console.log(Math.pow(2, 10));     // 1024 (2 to the 10th)
console.log(2 ** 10);             // 1024 (the ** operator — same thing)
console.log(Math.sqrt(144));      // 12   (square root)
console.log(Math.max(3, 9, 1));   // 9    (largest)
console.log(Math.min(3, 9, 1));   // 1    (smallest)

The ** exponent operator does the same job as Math.pow and reads a little cleaner, so you’ll often prefer it. And Math.max/Math.min take any number of arguments — handy for finding the highest score or the lowest price in a set.

Find the max of an array with the spread operator

Math.max takes a list of numbers, not an array. If you already have an array, spread it in with ...:

const scores = [82, 91, 67, 100, 73];
console.log(Math.max(...scores));   // 100
console.log(Math.min(...scores));   // 67

The ... “spreads” the array’s items into separate arguments, as if you’d typed Math.max(82, 91, 67, 100, 73). It’s the standard way to get the biggest or smallest value out of an array.

Math constants

Math also holds a few useful constants:

console.log(Math.PI);   // 3.141592653589793
console.log(Math.E);    // 2.718281828459045

// Area of a circle with radius 5:
const radius = 5;
const area = Math.PI * radius ** 2;
console.log(area.toFixed(2));   // "78.54"

Random numbers

Math.random() returns a random decimal between 0 (inclusive) and 1 (exclusive) — so somewhere from 0 up to, but never reaching, 1:

console.log(Math.random());   // e.g. 0.7263… (different every time)

On its own that’s rarely what you want. The common task is a random integer in a range, and there’s a standard recipe for it:

// Random integer from min to max (both inclusive):
function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

console.log(randomInt(1, 6));    // a dice roll: 1–6
console.log(randomInt(1, 100));  // 1–100

Read it from the inside out: Math.random() gives a fraction, multiplying scales it up to the size of your range, Math.floor chops it down to a whole number, and adding min shifts it to start at the right place. Picking a random item from an array uses the same idea:

const colors = ["red", "green", "blue"];
const pick = colors[Math.floor(Math.random() * colors.length)];
console.log(pick);   // one of the three, at random

Math.random is not secure

Math.random() is fine for games, shuffles, and picking a random tip to show — anything where unpredictability is “nice to have.” It is not cryptographically secure, so never use it to generate passwords, session tokens, or anything security-sensitive. For that, browsers provide crypto.getRandomValues(), which is built for unpredictability. Match the tool to the stakes.

When integers aren’t enough: BigInt

Regular numbers are safe and exact only up to a limit — Number.MAX_SAFE_INTEGER, which is 9007199254740991 (just over 9 quadrillion). Past that point, integers can quietly lose precision:

console.log(Number.MAX_SAFE_INTEGER);   // 9007199254740991
console.log(9007199254740991 + 1);      // 9007199254740992  (ok)
console.log(9007199254740991 + 2);      // 9007199254740992  (wrong! should be ...993)

For the rare cases where you genuinely need huge whole numbers — large database IDs, certain financial or scientific work — JavaScript has BigInt. You make one by adding n to the end of an integer literal:

const huge = 9007199254740991n;
console.log(huge + 2n);   // 9007199254740993n  (correct!)

The catch: you can’t mix BigInt and regular numbers in the same operation (10n + 5 throws an error — you’d write 10n + 5n), and BigInt can’t hold decimals. For everyday work, stick with normal numbers; reach for BigInt only when you actually bump into the safe-integer limit.

A realistic example

Let’s pull several of these together into something you might actually write — a small order summary:

const order = {
  customer: "John Doe",
  items: [
    { name: "Keyboard", price: 250000, qty: 1 },
    { name: "Mouse", price: 120000, qty: 2 },
  ],
};

// Sum each item's price × quantity
let subtotal = 0;
for (const item of order.items) {
  subtotal += item.price * item.qty;
}

const taxRate = 0.11;                  // 11% tax
const tax = subtotal * taxRate;
const total = subtotal + tax;

console.log("Subtotal:", subtotal.toLocaleString("id-ID"));   // 490.000
console.log("Tax:", Math.round(tax).toLocaleString("id-ID")); // 53.900
console.log("Total:", Math.round(total).toLocaleString("id-ID")); // 543.900

This is numbers doing real work: a loop to add things up (the for...of from the loops article), arithmetic for the tax, Math.round to clean up the result, and toLocaleString to present it the way a person expects to read it.

Wrapping up

You now have a solid grip on how numbers behave in JavaScript and the tools that go with them:

  • JavaScript has one number type for integers and decimals alike. Under the hood it’s 64-bit floating-point — convenient, but the source of one classic quirk.
  • Decimal math isn’t exact (0.1 + 0.2 !== 0.3). Don’t compare decimals with ===; for money, work in the smallest unit (cents) as integers.
  • Number methods format and convert: toFixed(n) for fixed decimals (returns a string), toString(radix) for other bases, toLocaleString() for human-friendly and currency formatting.
  • NaN means “not a number” and equals nothing, even itself — check with Number.isNaN(). Dividing by zero gives Infinity — guard real results with Number.isFinite().
  • Convert strings with Number() (strict), parseInt(str, 10), or parseFloat() (lenient). Always pass a radix to parseInt.
  • The Math object handles the rest: round/ceil/floor/trunc for rounding, abs/sqrt/max/min/** for calculation, and Math.random() for randomness (not for security).
  • For whole numbers beyond Number.MAX_SAFE_INTEGER, reach for BigInt (123n).

Numbers and text are the two raw materials almost every program is built from. You’ve now covered both the numeric side and, in the basics of strings and data types, the textual side. Next up, we’ll look at dates and times — which, as it happens, are stored as numbers too, so everything you just learned carries straight over.

Tags:javascriptfrontendnumbersmathintermediate
728 × 90Ad Space AvailablePlace your ad here

Related Articles

See All Articles

You Might Also Like

Frontend best practices overview cover
Frontend / Fundamentals

Frontend Best Practices: An Overview

A friendly map of what good frontend really means — semantic HTML, clean CSS, unobtrusive JavaScript, responsive layouts, performance, and progressive enhancement, with pointers to go deeper.

Sep 20, 20268 min read
A Frontend Developer Roadmap cover with the path HTML to CSS to JavaScript
Frontend / Fundamentals

A Frontend Developer Roadmap: What to Learn, and in What Order

A calm, step-by-step learning path for beginners: HTML, CSS, JavaScript, developer tools, responsive and accessible design, a framework, TypeScript, and deployment, with the reasoning behind each step.

Sep 20, 202611 min read
Title card reading Styling and Interactivity over a dark blue ACY Partner background
Frontend / Fundamentals

How Styling and Interactivity Work

A beginner-friendly look at how CSS turns plain HTML into a designed layout, and how JavaScript adds behavior — the structure, style, behavior mental model for building web pages.

Sep 20, 20269 min read
Three front-end layers combining on one web page
Frontend / Fundamentals

How HTML, CSS, and JavaScript Work Together

A hands-on walkthrough for beginners: build one small button, give it structure with HTML, looks with CSS, and behavior with JavaScript, and watch the three layers cooperate on a real page.

Sep 20, 20268 min read
The Frontend Developer Toolkit cover with editor, browser, and CLI motifs
Frontend / Fundamentals

The Frontend Developer Toolkit

A friendly tour of the everyday tools a frontend developer relies on — code editor, browser and DevTools, terminal, version control, package managers, and a dev server — and what each one is actually for.

Sep 20, 202610 min read
Cover illustration for What Frontend Developers Do
Frontend / Fundamentals

What Frontend Developers Do: A Beginner's Guide to the Role

A clear, beginner-friendly look at what frontend developers actually do every day: turning designs into working interfaces, building reusable UI, and caring about responsiveness, accessibility, and speed.

Sep 20, 202610 min read