JAVASCRIPT / NUMBERS, MATH, AND BIGINT
Powers, roots, and logarithms with Math
Compute powers with **, take roots safely, and invert them with Math.log, Math.log2, and Math.log10 without hitting NaN or precedence bugs.
What you will learn
- Compute powers with ** and know it is right-associative: 2 ** 3 ** 2 is 512, not 64
- Take roots with Math.sqrt and Math.cbrt; ** (1 / n) returns NaN for a negative base
- Invert any power with Math.log(x) / Math.log(base), or Math.log2 and Math.log10
- Avoid overflow with Math.hypot, and compare log results with a tolerance, not ===
Understanding Powers, roots, and logarithms with Math
x ** y and Math.pow(x, y) run the same internal operation, so the operator is only newer syntax. It has one habit no other binary operator in JavaScript has: it groups right to left, so 2 ** 3 ** 2 means 2 ** (3 ** 2), which is 512. Because -2 ** 2 is genuinely ambiguous between (-2) ** 2 and -(2 ** 2), the grammar refuses to guess and reports a SyntaxError. Repeated multiplication is only the right mental model for whole-number exponents; anything else is computed through exponentials and logarithms internally, which is why a negative base raised to a fractional exponent has nowhere real to land and gives NaN.
A root is a power with a fractional exponent, so the nth root of x is x ** (1 / n). Two things go wrong with that in binary floating point: 1 / n is usually inexact (1/3, 1/5, and 1/10 all are), so a perfect root can come back an ulp away from a whole number, and a negative x is NaN because the exponent is not an integer. Math.sqrt maps to the machine square root and is exact whenever the answer is representable, Math.cbrt pulls the sign out first so Math.cbrt(-8) is -2, and Math.hypot exists so that computing a magnitude does not overflow while squaring large inputs.
A logarithm asks the inverse question: given base ** exponent === value, and knowing the base and the value, what is the exponent? Math.log is the natural log, base e, not base 10 as on most calculators; Math.log2 and Math.log10 cover the two bases that actually show up in code. Those two are more than conveniences, because Math.log2 works from the binary exponent of the number, so Math.log2(1024) is exactly 10 while Math.log(1024) / Math.log(2) can land a hair under it. The domain edges follow the shape of the curve: Math.log(1) is 0, Math.log(0) is -Infinity, and any negative input is NaN.
Positional note: this lesson sits between the arithmetic basics earlier in the section and BigInt later on, so everything here is Number math.
// ** is exponentiation, and it groups right to left
console.log(2 ** 10);
console.log(2 ** 3 ** 2);
// A root is a fractional power; Math.sqrt is the exact special case
console.log(16 ** 0.5);
console.log(Math.sqrt(16));
// A negative base with a fractional exponent has no real answer
console.log((-8) ** (1 / 3));
console.log(Math.cbrt(-8));
// A logarithm hands back the exponent
console.log(Math.log2(1024));
console.log(Math.log(1));
console.log(Math.log(0));Powers, roots, and logarithms are the same equation base ** exponent === value read from three different unknowns, and each direction has its own precision and domain traps.
Worked examples
Grouping and the banned unary minus
Shows how ** associates and why a negative base must be parenthesized.
console.log(Math.pow(3, 4) === 3 ** 4);
console.log(2 ** -3);
console.log((-2) ** 2);
console.log(-(2 ** 2));
console.log((2 ** 3) ** 2);
// console.log(-2 ** 2); // SyntaxError, will not even parseExample explained
Line 1Math.pow(3, 4) and 3 ** 4 go through the same specified operation, so comparing them with === is true.
Line 2A negative exponent is a reciprocal: 2 ** -3 is 1 / 8, and unary minus is allowed on the right side of **.
Line 3On the left side it is rejected, so you choose the meaning yourself: (-2) ** 2 is 4, -(2 ** 2) is -4.
Line 4(2 ** 3) ** 2 is 64, but the unparenthesized 2 ** 3 ** 2 is 512, because ** groups right to left.
Logarithms in any base
Converts bases with a ratio of natural logs and shows where Math.log2 is exact and the ratio is not.
const logBase = (x, base) => Math.log(x) / Math.log(base);
// how many 5% periods does it take to double?
console.log(logBase(2, 1.05).toFixed(4));
// log2 is exact on powers of two; the ratio only gets close
console.log(Math.log2(2 ** 40) === 40);
console.log(Math.abs(logBase(2 ** 40, 2) - 40) < 1e-9);
// smallest number of bits that can hold n
const bits = (n) => Math.ceil(Math.log2(n + 1));
console.log(bits(255), bits(256), bits(257));Example explained
Line 1logBase divides two natural logs; the shared base cancels out, so any single base works for both calls.
Line 21.05 ** 14.2067 is about 2, which is the growth reading of that logarithm: a little over 14 periods to double.
Line 3Math.log2 derives the answer from the binary exponent, so powers of two come back as exact integers, while the log ratio can miss by an ulp and needs a tolerance check.
Line 4bits uses n + 1 so 255 fits in 8 bits and 256 needs 9; that boundary is only reliable because Math.log2(256) is exactly 8, so Math.ceil does not round up.
Magnitudes without overflow
Demonstrates why Math.hypot exists and how powers overflow to Infinity.
const big = 3e200;
console.log(Math.sqrt(big ** 2 + big ** 2));
console.log(Number.isFinite(Math.hypot(big, big)));
console.log(Math.hypot(3, 4));
console.log(2 ** 1024 === Infinity);Example explained
Line 1big ** 2 is 9e400, far past Number.MAX_VALUE, so the sum is already Infinity before Math.sqrt is called.
Line 2Math.hypot scales by the largest magnitude first, so the squares it adds stay near 1 and the result stays finite.
Line 3There is no accuracy cost for ordinary inputs: Math.hypot(3, 4) is plain 5.
Line 4Powers overflow the same silent way; anything above roughly 1.8e308 becomes Infinity instead of throwing.
Important notes
These Math functions accept Numbers only. Math.sqrt(4n) throws a TypeError, and although BigInt supports its own ** (2n ** 10n is 1024n), you cannot mix a BigInt and a Number in one ** expression.
Do not test root or logarithm results with ===. Compare with a tolerance such as Math.abs(a - b) < 1e-9, or round deliberately before comparing.
Common mistakes
Writing -2 ** 2 expecting -4: the script fails to parse with a SyntaxError, so nothing at all runs until you write (-2) ** 2 or -(2 ** 2).
Reading 2 ** 3 ** 2 left to right as 64 when it is 512, which makes a formula wrong by orders of magnitude without any error being raised.
Using x ** (1 / 3) on a negative number for a cube root: it returns NaN, and that NaN then spreads silently through every later sum and comparison, so the bug surfaces far from its cause.
Try it yourself
Change, predict, then run
In a browser console, write nthRoot(x, n) that returns the real nth root: the negative root when x is negative and n is odd, and NaN when x is negative and n is even. Confirm that Math.abs(nthRoot(-32, 5) + 2) < 1e-12 is true and that Number.isNaN(nthRoot(-16, 4)) is true.
Open the JavaScript workspaceCheck your understanding
Why does (-8) ** (1 / 3) produce NaN while Math.cbrt(-8) produces -2?
- ** is defined so that a negative base with a non-integer exponent has no real result and yields NaN, while Math.cbrt takes the sign out and roots the magnitude
- 1 / 3 cannot be represented exactly as a double, and that rounding error is what turns the result into NaN
- ** only accepts integer exponents, so every fractional exponent returns NaN
- Math.cbrt works on BigInt internally, which lets it represent the negative root that ** cannot
Show answer
The exponentiation rules say a negative base with a non-integral exponent is NaN, and Math.cbrt sidesteps that by computing the root of 8 and reattaching the minus sign. Option 2 is tempting because 1 / 3 really is inexact, and that inexactness does cost you an ulp on positive bases, but it is not the cause here: even a mathematically exact 1/3 is still non-integral, so the result would be NaN anyway. Option 3 is wrong because 16 ** 0.5 is 4.