Skip to main content

How to Work with Numbers in JavaScript

Numbers are one of the most fundamental data types in any programming language, and JavaScript handles them in a way that is both powerful and occasionally surprising. Unlike languages such as Java or C that have separate types for integers and floating-point numbers, JavaScript uses a single numeric type for everything. Whether you are working with the integer 42, the decimal 3.14, or the astronomical 9007199254740991, they are all stored the same way internally.

This unified approach simplifies many things, but it also introduces behaviors that catch developers off guard: precision errors with decimal math, the mysterious NaN value, and the limits of what numbers JavaScript can safely represent. This guide covers everything you need to know about working with numbers in JavaScript, from how they are stored in memory to practical techniques for rounding, parsing, random number generation, and avoiding the classic floating-point pitfalls.

Integer and Floating-Point Representation (IEEE 754)

JavaScript stores all regular numbers using the IEEE 754 double-precision 64-bit floating-point format. This is the same format used by double in Java and C/C++.

What 64 Bits Means

Each number occupies exactly 64 bits (8 bytes) in memory, divided into three parts:

PartBitsPurpose
Sign1 bitPositive (0) or negative (1)
Exponent11 bitsThe magnitude (scale) of the number
Mantissa (Fraction)52 bitsThe significant digits of the number

This format can represent an enormous range of values, from incredibly tiny decimals to astronomically large numbers.

Practical Consequences

Integers are stored precisely up to a certain limit. Because the mantissa has 52 bits, integers up to 2^53 - 1 (which is 9007199254740991) can be represented exactly:

console.log(9007199254740991);     // 9007199254740991 (exact)
console.log(9007199254740992); // 9007199254740992 (exact (even, so representable))
console.log(9007199254740993); // 9007199254740992 (WRONG! Rounded to nearest representable)

Output:

9007199254740991
9007199254740992
9007199254740992

Beyond this limit, JavaScript cannot distinguish between consecutive integers. The number 9007199254740993 simply cannot be stored in 64-bit floating-point format, so it gets rounded to the nearest representable value.

Floating-point numbers can represent values with decimal points, but with limited precision, typically about 15 to 17 significant decimal digits:

console.log(0.1 + 0.2);           // 0.30000000000000004  (not exactly 0.3!)
console.log(1/3); // 0.3333333333333333 (finite representation)

Special Numeric Values

IEEE 754 also defines three special values:

console.log(Infinity);            // Infinity   (larger than any number)
console.log(-Infinity); // -Infinity (smaller than any number)
console.log(NaN); // NaN ("Not a Number")

console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
console.log("hello" * 2); // NaN
console.log(Math.sqrt(-1)); // NaN

// typeof for all of these is still "number"
console.log(typeof Infinity); // "number"
console.log(typeof NaN); // "number"
BigInt for Larger Integers

If you need integers larger than 2^53 - 1, JavaScript provides the BigInt type, which can represent integers of arbitrary size. Regular numbers and BigInt cannot be mixed in arithmetic operations without explicit conversion. BigInt is covered in its own dedicated article.

Numeric Literals: Hex, Octal, Binary, Exponential, Underscores

JavaScript supports several ways to write numeric values directly in your code.

Decimal (Base 10)

The most common format, with optional decimal points:

let integer = 42;
let decimal = 3.14;
let negative = -7;
let leadingZero = 0.5; // Same as .5, but clearer
let shorthand = .5; // Valid, but less readable

Exponential Notation

For very large or very small numbers, use e (or E) followed by the power of 10:

let billion = 1e9;            // 1 × 10^9 = 1,000,000,000
let micro = 1e-6; // 1 × 10^-6 = 0.000001

console.log(1e9); // 1000000000
console.log(7.3e5); // 730000
console.log(2.5e-3); // 0.0025

// Useful for readability
let distanceToSun = 1.496e8; // km
let electronMass = 9.109e-31; // kg

console.log(distanceToSun) // 149600000
console.log(electronMass) // 9.109e-31

Hexadecimal (Base 16)

Prefix with 0x or 0X. Common for colors, bitwise operations, and low-level values:

let hex = 0xFF;              // 255
let color = 0x00FF00; // 65280 (green in RGB)

console.log(0xFF); // 255
console.log(0x1A); // 26
console.log(0xCAFE); // 51966

Octal (Base 8)

Prefix with 0o or 0O:

let octal = 0o77;            // 63
let permissions = 0o755; // 493 (Unix file permissions)

console.log(0o77); // 63
console.log(0o10); // 8
caution

In non-strict mode, a leading 0 (like 0777) also creates an octal number, which is a common source of bugs. Always use the explicit 0o prefix and strict mode to avoid ambiguity.

Binary (Base 2)

Prefix with 0b or 0B:

let binary = 0b11111111;     // 255
let flags = 0b1010; // 10

console.log(0b11111111); // 255
console.log(0b1010); // 10
console.log(0b100); // 4

Numeric Separators (Underscores)

ES2021 introduced underscores (_) as visual separators in numeric literals. They have no effect on the value and exist purely for readability:

let billion = 1_000_000_000;        // 1000000000
let budget = 12_500_000; // 12500000
let hex = 0xFF_EC_D5_9C; // 4293713308
let binary = 0b1010_0001_1000_0101; // 41349
let fraction = 0.000_001; // 0.000001

console.log(1_000_000_000); // 1000000000
console.log(1_000 === 1000); // true (identical values)

Rules for underscores:

  • Cannot be at the start or end of a number: _100 and 100_ are invalid
  • Cannot be adjacent to another underscore: 1__000 is invalid
  • Cannot be adjacent to the decimal point: 3_.14 and 3._14 are invalid
  • Can be used in any base (decimal, hex, octal, binary)

toString(base): Converting Numbers to Different Bases

The toString(base) method converts a number to a string representation in the specified base (from 2 to 36):

let num = 255;

console.log(num.toString(16)); // "ff" (hexadecimal)
console.log(num.toString(8)); // "377" (octal)
console.log(num.toString(2)); // "11111111" (binary)
console.log(num.toString(10)); // "255" (decimal, default)
console.log(num.toString(36)); // "73" (base 36)

Base 36

Base 36 is the maximum base and uses digits 0-9 plus letters a-z. It is sometimes used to create compact representations of large numbers:

let timestamp = Date.now();
console.log(timestamp); // 1772563794137
console.log(timestamp.toString(36)); // "mmaypsyh" (much shorter)

// Useful for generating short IDs
let shortId = Math.random().toString(36).slice(2, 10);
console.log(shortId); // e.g., "hgk8hi5i"

Calling toString on Numeric Literals

When calling toString() directly on a number literal, you need to handle the dot ambiguity:

// ❌ SyntaxError: the dot is interpreted as a decimal point
// 123.toString(16);

// ✅ Solution 1: Use parentheses
console.log((123).toString(16)); // "7b"

// ✅ Solution 2: Use two dots (first is decimal, second is method access)
console.log(123..toString(16)); // "7b"

// ✅ Solution 3: Use a variable
let n = 123;
console.log(n.toString(16)); // "7b"

Rounding: Math.floor, ceil, round, trunc, toFixed

JavaScript provides several built-in functions for rounding numbers, each with different behavior.

Math.floor(): Round Down

Always rounds toward negative infinity (down):

console.log(Math.floor(3.7));   // 3
console.log(Math.floor(3.1)); // 3
console.log(Math.floor(3.0)); // 3
console.log(Math.floor(-1.1)); // -2 (toward -infinity, not toward zero!)
console.log(Math.floor(-1.9)); // -2

Math.ceil(): Round Up

Always rounds toward positive infinity (up):

console.log(Math.ceil(3.1));    // 4
console.log(Math.ceil(3.7)); // 4
console.log(Math.ceil(3.0)); // 3 (already an integer)
console.log(Math.ceil(-1.1)); // -1 (toward +infinity)
console.log(Math.ceil(-1.9)); // -1

Math.round(): Round to Nearest

Rounds to the nearest integer. For .5 values, it rounds up:

console.log(Math.round(3.5));   // 4
console.log(Math.round(3.4)); // 3
console.log(Math.round(3.6)); // 4
console.log(Math.round(-1.5)); // -1 (rounds toward +infinity)
console.log(Math.round(-1.6)); // -2

Math.trunc(): Remove Decimal Part

Removes the fractional part entirely, rounding toward zero:

console.log(Math.trunc(3.7));   // 3
console.log(Math.trunc(3.1)); // 3
console.log(Math.trunc(-1.1)); // -1 (toward zero, not -infinity)
console.log(Math.trunc(-1.9)); // -1

Comparison Table

Valuefloorceilroundtrunc
3.13433
3.53443
3.93443
-1.1-2-1-1-1
-1.5-2-1-1-1
-1.9-2-1-2-1

The key difference is with negative numbers: floor rounds toward negative infinity, trunc rounds toward zero.

toFixed(n): Round to n Decimal Places

The toFixed() method rounds a number to n decimal places and returns a string:

let price = 19.956;

console.log(price.toFixed(2)); // "19.96"
console.log(price.toFixed(0)); // "20"
console.log(price.toFixed(5)); // "19.95600" (pads with zeros)

console.log(typeof price.toFixed(2)); // "string" (NOT a number!)

Since toFixed() returns a string, you often need to convert the result back to a number:

let rounded = +price.toFixed(2);          // 19.96 (number)
let rounded2 = Number(price.toFixed(2)); // 19.96 (number)

console.log(typeof rounded); // "number"

Rounding to a Specific Decimal Place (Custom)

To round to n decimal places and get a number (not a string), you can use the multiply-round-divide technique:

// Round to 2 decimal places
let num = 3.14159;

let rounded = Math.round(num * 100) / 100;
console.log(rounded); // 3.14

// Round to 3 decimal places
let rounded3 = Math.round(num * 1000) / 1000;
console.log(rounded3); // 3.142
toFixed Rounding Quirk

toFixed() uses "round half to even" (banker's rounding) in some edge cases, which can produce unexpected results:

console.log((1.255).toFixed(2));  // "1.25" (you might expect "1.26"!)
console.log((1.245).toFixed(2)); // "1.25" (expected "1.25", correct)

This happens because 1.255 cannot be represented exactly in binary floating-point. Internally, it is stored as something slightly less than 1.255, so it rounds down. For critical financial calculations, use specialized decimal libraries.

Imprecise Calculations: Why 0.1 + 0.2 ≠ 0.3

This is perhaps the most famous quirk of JavaScript (and every other language using IEEE 754 floating-point).

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

Output:

0.30000000000000004
false

Why This Happens

The number 0.1 cannot be represented exactly in binary, just as 1/3 cannot be represented exactly in decimal (0.3333...). In binary, 0.1 is an infinitely repeating fraction:

0.1 (decimal) = 0.0001100110011001100110011... (binary, repeating)

Since JavaScript stores numbers in 64 bits, this infinite binary representation is truncated, introducing a tiny rounding error. When you add two imprecise representations (0.1 and 0.2), the errors accumulate and produce a result that is not exactly 0.3.

This is not a JavaScript bug. It is a fundamental limitation of binary floating-point arithmetic. The same behavior occurs in Python, Java, C++, Ruby, and virtually every programming language.

How to Handle It

Approach 1: Round the result

let sum = 0.1 + 0.2;
let rounded = +sum.toFixed(2); // 0.3

console.log(rounded); // 0.3
console.log(rounded === 0.3); // true

Approach 2: Compare with a tolerance (epsilon)

Instead of checking for exact equality, check if the difference is smaller than a very small number:

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

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

Number.EPSILON is the smallest difference between two representable numbers (2.220446049250313e-16). For practical purposes, you might want a larger tolerance:

function nearlyEqual(a, b, tolerance = 1e-10) {
return Math.abs(a - b) < tolerance;
}

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

Approach 3: Work in integers (cents instead of dollars)

For financial calculations, avoid floating-point entirely by working in the smallest unit:

// ❌ WRONG: Floating-point money
let price = 0.1 + 0.2;
console.log(price); // 0.30000000000000004

// ✅ CORRECT: Work in cents
let priceCents = 10 + 20; // 30 cents
let priceDisplay = priceCents / 100; // Convert only for display
console.log(priceDisplay); // 0.3

More Examples of Floating-Point Surprises

console.log(0.1 + 0.2);           // 0.30000000000000004
console.log(0.1 + 0.7); // 0.7999999999999999
console.log(1.005 * 100); // 100.49999999999999 (not 100.5!)
console.log(9999999999999999); // 10000000000000000 (loses last digit)

isNaN() vs. Number.isNaN(): The Critical Difference

NaN (Not a Number) is a special value that represents a failed numeric operation. It has unique behavior: NaN is not equal to anything, including itself.

console.log(NaN === NaN);      // false!
console.log(NaN == NaN); // false!
console.log(NaN !== NaN); // true (the only value not equal to itself)

Because you cannot use === to check for NaN, JavaScript provides two functions for this purpose. Understanding their difference is critical.

The Global isNaN() Function

The global isNaN() function first converts its argument to a number, then checks if the result is NaN:

console.log(isNaN(NaN));         // true ✓
console.log(isNaN(undefined)); // true (because Number(undefined) is NaN)
console.log(isNaN("hello")); // true (because Number("hello") is NaN)
console.log(isNaN("")); // false (because Number("") is 0)
console.log(isNaN(true)); // false (because Number(true) is 1)
console.log(isNaN(null)); // false (because Number(null) is 0)
console.log(isNaN("123")); // false (because Number("123") is 123)

The problem is clear: isNaN("hello") returns true, but "hello" is not NaN. It is a string. The function is really asking "would this produce NaN if converted to a number?" rather than "is this value actually NaN?"

Number.isNaN() The Reliable Version

Number.isNaN() does not convert its argument. It returns true only if the value is already the actual NaN value:

console.log(Number.isNaN(NaN));         // true ✓
console.log(Number.isNaN(undefined)); // false (undefined is not NaN9
console.log(Number.isNaN("hello")); // false ("hello" is not NaN9
console.log(Number.isNaN("")); // false
console.log(Number.isNaN(true)); // false
console.log(Number.isNaN(null)); // false
console.log(Number.isNaN(123)); // false
console.log(Number.isNaN(0 / 0)); // true (0/0 produces actual NaN)

Comparison Table

ValueisNaN()Number.isNaN()
NaNtruetrue
undefinedtruefalse
"hello"truefalse
{}truefalse
""falsefalse
nullfalsefalse
truefalsefalse
123falsefalse
Always Use Number.isNaN()

The global isNaN() function is unreliable because it coerces its argument. Always use Number.isNaN() for accurate NaN detection. The global version exists for historical reasons but should be avoided in modern code.

isFinite() vs. Number.isFinite()

Similar to the isNaN pair, JavaScript has two versions of isFinite. A finite number is any regular number that is not Infinity, -Infinity, or NaN.

The Global isFinite()

Converts its argument to a number first, then checks:

console.log(isFinite(42));          // true
console.log(isFinite(Infinity)); // false
console.log(isFinite(-Infinity)); // false
console.log(isFinite(NaN)); // false
console.log(isFinite("123")); // true (converts string to number 123)
console.log(isFinite("")); // true (converts empty string to 0)
console.log(isFinite(null)); // true (converts null to 0)
console.log(isFinite("hello")); // false (converts to NaN, which is not finite)

Number.isFinite(): The Reliable Version

No type conversion. Returns true only for actual finite numbers:

console.log(Number.isFinite(42));          // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isFinite(-Infinity)); // false
console.log(Number.isFinite(NaN)); // false
console.log(Number.isFinite("123")); // false (string, not a number)
console.log(Number.isFinite("")); // false
console.log(Number.isFinite(null)); // false
console.log(Number.isFinite(true)); // false (boolean, not a number)

Practical Use: Validating Numeric Input

Number.isFinite() is excellent for validating that a value is a real, usable number:

function processPrice(input) {
let price = Number(input);

if (!Number.isFinite(price)) {
throw new Error(`Invalid price: "${input}"`);
}

return price.toFixed(2);
}

console.log(processPrice("19.99")); // "19.99"
console.log(processPrice(42)); // "42.00"
// processPrice("abc"); // Error: Invalid price: "abc"
// processPrice(Infinity); // Error: Invalid price: "Infinity"

parseInt() and parseFloat(): Parsing Numbers from Strings

While Number() converts an entire string to a number (returning NaN if the string is not entirely numeric), parseInt() and parseFloat() extract numbers from the beginning of a string, ignoring trailing non-numeric characters.

parseInt()

Parses an integer from the beginning of a string:

console.log(parseInt("100px"));      // 100
console.log(parseInt("12.5em")); // 12 (stops at the decimal point)
console.log(parseInt(" 42 ")); // 42 (trims whitespace)
console.log(parseInt("0xFF")); // 255 (recognizes hex prefix)
console.log(parseInt("abc")); // NaN (no leading digits)
console.log(parseInt("")); // NaN

// Compare with Number():
console.log(Number("100px")); // NaN (Number requires the entire string to be numeric)
console.log(Number(" 42 ")); // 42 (Number does trim whitespace)

parseInt with Radix

parseInt accepts an optional second argument specifying the base (radix):

console.log(parseInt("ff", 16));     // 255
console.log(parseInt("11", 2)); // 3
console.log(parseInt("77", 8)); // 63
console.log(parseInt("10", 10)); // 10

// Without radix, parseInt tries to guess:
console.log(parseInt("0xFF")); // 255 (recognizes 0x prefix)
console.log(parseInt("077")); // 77 (in modern JS, NOT octal!)
Always Specify the Radix

Always pass the radix argument to parseInt() to avoid ambiguity:

// ✅ Always specify base 10 for decimal parsing
parseInt("042", 10); // 42 (clear and unambiguous)

parseFloat()

Parses a floating-point number from the beginning of a string:

console.log(parseFloat("12.5em"));   // 12.5
console.log(parseFloat("3.14")); // 3.14
console.log(parseFloat("0.5px")); // 0.5
console.log(parseFloat(".5")); // 0.5
console.log(parseFloat("12.34.56")); // 12.34 (stops at second dot)
console.log(parseFloat("abc")); // NaN

parseFloat does not accept a radix argument. It always parses in base 10.

Practical Comparison: Number() vs. parseInt() vs. parseFloat()

InputNumber()parseInt()parseFloat()
"42"424242
"42px"NaN4242
"3.14"3.1433.14
"3.14px"NaN33.14
""0NaNNaN
" "0NaNNaN
null0NaNNaN
true1NaNNaN
"0xFF"2552550

Choose based on your needs:

  • Number(): Strict conversion. Use when the entire value should be numeric.
  • parseInt(): Use when extracting an integer from a string with trailing text (like CSS values).
  • parseFloat(): Use when extracting a decimal from a string with trailing text.

Number.MAX_SAFE_INTEGER, Number.EPSILON, and Numeric Limits

JavaScript provides constants that describe the boundaries and precision of its numeric system.

Safe Integer Range

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

// Checking if an integer is safe
console.log(Number.isSafeInteger(9007199254740991)); // true
console.log(Number.isSafeInteger(9007199254740992)); // false (beyond safe range)

// Arithmetic beyond safe range is unreliable
console.log(9007199254740991 + 1); // 9007199254740992 (correct)
console.log(9007199254740991 + 2); // 9007199254740992 (WRONG! Should be ...993)
console.log(9007199254740991 + 3); // 9007199254740994 (correct (by coincidence))

Number.EPSILON

The smallest difference between two representable floating-point numbers. Useful for comparing floating-point results:

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

// Using EPSILON for comparison
let a = 0.1 + 0.2;
let b = 0.3;

console.log(a === b); // false
console.log(Math.abs(a - b) < Number.EPSILON); // true (within floating-point precision)

Maximum and Minimum Values

console.log(Number.MAX_VALUE);         // 1.7976931348623157e+308 (largest representable number)
console.log(Number.MIN_VALUE); // 5e-324 (smallest positive number (closest to 0))

console.log(Number.POSITIVE_INFINITY); // Infinity
console.log(Number.NEGATIVE_INFINITY); // -Infinity

// Exceeding MAX_VALUE
console.log(Number.MAX_VALUE * 2); // Infinity

Complete Reference Table

ConstantValueDescription
Number.MAX_SAFE_INTEGER9007199254740991Largest safe integer (2^53 - 1)
Number.MIN_SAFE_INTEGER-9007199254740991Smallest safe integer -(2^53 - 1)
Number.MAX_VALUE~1.8e+308Largest representable number
Number.MIN_VALUE~5e-324Smallest positive number
Number.EPSILON~2.2e-16Smallest representable difference
Number.POSITIVE_INFINITYInfinityPositive infinity
Number.NEGATIVE_INFINITY-InfinityNegative infinity
Number.NaNNaNNot a Number

The Math Object: Common Methods

The Math object is a built-in namespace containing mathematical constants and functions. It is not a constructor and cannot be instantiated.

Constants

console.log(Math.PI);      // 3.141592653589793
console.log(Math.E); // 2.718281828459045
console.log(Math.LN2); // 0.6931471805599453
console.log(Math.LN10); // 2.302585092994046
console.log(Math.SQRT2); // 1.4142135623730951
console.log(Math.LOG2E); // 1.4426950408889634

Math.max() and Math.min()

Return the largest or smallest of the given numbers:

console.log(Math.max(1, 5, 3, 9, 2));   // 9
console.log(Math.min(1, 5, 3, 9, 2)); // 1

// With arrays, use spread:
let scores = [85, 92, 78, 96, 88];
console.log(Math.max(...scores)); // 96
console.log(Math.min(...scores)); // 78

// Edge cases
console.log(Math.max()); // -Infinity (no arguments)
console.log(Math.min()); // Infinity (no arguments)
console.log(Math.max(1, NaN, 3)); // NaN (any NaN makes result NaN)

Math.abs()

Returns the absolute value:

console.log(Math.abs(-5));    // 5
console.log(Math.abs(5)); // 5
console.log(Math.abs(0)); // 0
console.log(Math.abs(-3.14)); // 3.14

Math.pow() and the ** Operator

Both compute exponentiation:

console.log(Math.pow(2, 10));   // 1024
console.log(2 ** 10); // 1024 (modern syntax, preferred)

console.log(Math.pow(9, 0.5)); // 3 (square root)
console.log(9 ** 0.5); // 3

Math.sqrt() and Math.cbrt()

Square root and cube root:

console.log(Math.sqrt(144));   // 12
console.log(Math.sqrt(2)); // 1.4142135623730951
console.log(Math.cbrt(27)); // 3
console.log(Math.cbrt(64)); // 4

Math.sign()

Returns -1, 0, or 1 indicating the sign of a number:

console.log(Math.sign(-42));   // -1
console.log(Math.sign(0)); // 0
console.log(Math.sign(42)); // 1

Math.log(), Math.log2(), Math.log10()

Logarithmic functions:

console.log(Math.log(Math.E));    // 1 (natural log of e)
console.log(Math.log2(1024)); // 10
console.log(Math.log10(1000)); // 3

Math.hypot()

Returns the square root of the sum of squares of its arguments (Euclidean distance):

console.log(Math.hypot(3, 4));       // 5 (3-4-5 triangle)
console.log(Math.hypot(5, 12)); // 13
console.log(Math.hypot(1, 1)); // 1.4142135623730951
console.log(Math.hypot(3, 4, 5)); // 7.0710678118654755 (3D distance)

Math.clz32()

Counts leading zero bits in the 32-bit integer representation:

console.log(Math.clz32(1));    // 31
console.log(Math.clz32(1024)); // 21

Generating Random Numbers in a Range

Math.random() returns a pseudo-random floating-point number in the range [0, 1) (from 0 inclusive to 1 exclusive):

console.log(Math.random()); // e.g., 0.7254839457234
console.log(Math.random()); // e.g., 0.1839274659283
console.log(Math.random()); // e.g., 0.9481726354823

Random Float in a Range [min, max)

function randomFloat(min, max) {
return Math.random() * (max - min) + min;
}

console.log(randomFloat(1, 10)); // e.g., 5.723849
console.log(randomFloat(0, 100)); // e.g., 73.18264
console.log(randomFloat(-5, 5)); // e.g., -2.38471

Random Integer in a Range [min, max] (Inclusive)

function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}

console.log(randomInt(1, 6)); // e.g., 4 (simulates a dice roll)
console.log(randomInt(1, 100)); // e.g., 73
console.log(randomInt(0, 1)); // 0 or 1 (coin flip)

The + 1 in (max - min + 1) is critical for making the upper bound inclusive. Without it, max would never be generated.

Why the Formula Works

Breaking down randomInt(1, 6):

Math.random()                    → [0, 1)              e.g., 0.73
Math.random() * (6 - 1 + 1) → [0, 6) e.g., 4.38
Math.floor(...) → {0, 1, 2, 3, 4, 5} e.g., 4
Math.floor(...) + 1 → {1, 2, 3, 4, 5, 6} e.g., 5

Each integer from min to max has an equal probability.

Shuffling an Array (Fisher-Yates)

function shuffle(array) {
let arr = [...array]; // Create a copy
for (let i = arr.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]]; // Swap
}
return arr;
}

let cards = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log(shuffle(cards)); // e.g., [7, 3, 10, 1, 8, 5, 2, 9, 4, 6]

Picking a Random Element from an Array

function randomElement(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}

let colors = ["red", "green", "blue", "yellow", "purple"];
console.log(randomElement(colors)); // e.g., "blue"
Math.random() Is Not Cryptographically Secure

Math.random() uses a pseudo-random number generator (PRNG) that is fast but predictable. Never use it for security-sensitive purposes like generating passwords, tokens, or encryption keys. For those, use the crypto API:

// Cryptographically secure random values
let array = new Uint32Array(1);
crypto.getRandomValues(array);
console.log(array[0]); // Secure random 32-bit integer

// Secure random UUID
console.log(crypto.randomUUID());
// e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890"

Summary

  • JavaScript uses a single numeric type based on IEEE 754 double-precision 64-bit floating-point. There is no separate integer type.
  • Numeric literals can be written in decimal, hexadecimal (0x), octal (0o), binary (0b), and exponential (e) notation. Underscores (_) improve readability.
  • toString(base) converts numbers to string representations in any base from 2 to 36.
  • Rounding is handled by Math.floor (down), Math.ceil (up), Math.round (nearest), Math.trunc (toward zero), and toFixed(n) (to n decimal places, returns a string).
  • Floating-point precision errors like 0.1 + 0.2 !== 0.3 are inherent to IEEE 754. Handle them with rounding, epsilon comparison, or integer-based arithmetic.
  • Number.isNaN() checks if a value is actually NaN without type coercion. Always prefer it over the global isNaN().
  • Number.isFinite() checks if a value is a regular finite number without coercion. Always prefer it over the global isFinite().
  • parseInt() and parseFloat() extract numbers from the beginning of strings, ignoring trailing characters. Always pass the radix to parseInt().
  • Safe integer range is -(2^53 - 1) to 2^53 - 1. Beyond this, integer arithmetic becomes unreliable. Use BigInt for larger integers.
  • The Math object provides constants (PI, E) and functions (max, min, abs, pow, sqrt, random, sign, hypot, and more).
  • Math.random() generates pseudo-random numbers in [0, 1). Use formulas to generate random integers in specific ranges. For security-sensitive randomness, use the crypto API.

Table of Contents