JavaScript Arithmetic

6 minute read

JavaScript’s arithmetic operators look just like grade-school math, with a couple of twists: there’s only one numeric type (a 64-bit float), the % operator preserves the sign of the dividend, and the famous 0.1 + 0.2 !== 0.3 lurks behind every decimal calculation. This chapter is the working guide to doing math safely.

The Seven Arithmetic Operators

Op Name Example Result
+ Addition 5 + 3 8
- Subtraction 5 - 3 2
* Multiplication 5 * 3 15
/ Division 10 / 4 2.5
% Remainder 10 % 3 1
** Exponentiation 2 ** 10 1024
+ / - Unary plus/minus -x, +y negation / coerce-to-number

Addition

2 + 3        // 5
2 + 3.5      // 5.5
-2 + 3       // 1

+ is the one operator with a quirk: if either operand is a string, it concatenates instead.

"5" + 3      // "53"   ← string concat
5 + "3"      // "53"
"5" + "3"    // "53"
"5" + 3 + 2  // "532"  ← left-to-right, all become strings
1 + 2 + "3"  // "33"   ← 1+2 first = 3, then "3" + "3"

Tip: If you need numeric addition and aren’t sure about types, coerce explicitly: Number(a) + Number(b).

Subtraction, Multiplication, Division

These operators force their operands to numbers. No string fallback.

"10" - "3"   // 7
"10" * "3"   // 30
"10" / "3"   // 3.333...

"abc" - 1    // NaN  — "abc" can't be a number

Division never crashes:

10 / 0       // Infinity
-10 / 0      // -Infinity
0 / 0        // NaN

Remainder (%)

Returns what’s left after integer division. Keeps the sign of the dividend (the left operand).

10 % 3      // 1
10 % 4      // 2
10 % 5      // 0

-10 % 3     // -1   ← sign comes from -10
10 % -3     // 1    ← sign comes from 10
10 ÷ 3 = 3 remainder 1     →  10 % 3 = 1
-10 ÷ 3 = -3 remainder -1  →  -10 % 3 = -1

% is most often used to check divisibility:

n % 2 === 0   // true if even
n % 2 !== 0   // true if odd

if (i % 100 === 0) console.log("milestone:", i);

Warning: % is not mathematical modulo when operands have different signs. For a true positive-modulo, use ((n % m) + m) % m.

function mod(n, m) {
  return ((n % m) + m) % m;
}
mod(-10, 3);   // 2 — always positive

Exponentiation (**)

2 ** 3       // 8
2 ** 10      // 1024
2 ** 0.5     // 1.4142... (square root)
2 ** -1      // 0.5

** is right-associative — chains evaluate right-to-left:

2 ** 3 ** 2     // 2 ** 9 = 512
(2 ** 3) ** 2   // 8 ** 2 = 64

Equivalent to Math.pow(2, 3) but cleaner. Use **.

Unary + and -

-x negates. +x coerces to a number (and is the fastest way to convert).

const a = "42";

-a          // -42
+a          // 42
+"3.14"     // 3.14
+"abc"      // NaN
+true       // 1
+false      // 0
+null       // 0
+undefined  // NaN

Increment and Decrement

++ adds 1, -- subtracts 1. Prefix vs postfix changes when the change happens.

let x = 5;

// Prefix — change first, return new value
++x;        // x becomes 6, returns 6
console.log(x);  // 6

// Postfix — return old value, then change
x++;        // returns 6, x becomes 7
console.log(x);  // 7
let a = 1;
let b = a++ + ++a;
// Step by step:
//   a++       → returns 1, a becomes 2
//   ++a       → a becomes 3, returns 3
//   b = 1 + 3 = 4
console.log(a, b);   // 3 4

Tip: Stick to x++ on its own line. Embedding ++ inside expressions creates puzzles you’ll re-solve every time you read them.

Operator Precedence in Arithmetic

Level Operator Direction
Highest () grouping —
  ** right-to-left
  * / % left-to-right
Lowest + - left-to-right
2 + 3 * 4         // 14 (not 20)
(2 + 3) * 4       // 20
2 + 3 * 4 ** 2    // 2 + 3 * 16 = 50

When in doubt, add parentheses. They’re free.

The Floating-Point Trap

JavaScript numbers are IEEE 754 binary64. Most decimal fractions can’t be represented exactly, so this happens:

0.1 + 0.2                 // 0.30000000000000004
0.1 + 0.2 === 0.3         // false
1.005 * 100               // 100.49999999999999

0.3 - 0.1                 // 0.19999999999999998
3 * 0.1                   // 0.30000000000000004

This is not a bug — it’s how binary floats work in every language (Java, C, Python all have the same issue).

Fix 1: Round to fixed precision for display

(0.1 + 0.2).toFixed(2);   // "0.30"   — returns a STRING
Number((0.1 + 0.2).toFixed(2));   // 0.3

Fix 2: Compare with a small epsilon

function nearlyEqual(a, b, epsilon = 1e-9) {
  return Math.abs(a - b) < epsilon;
}
nearlyEqual(0.1 + 0.2, 0.3);   // true

Fix 3: Work in integers (cents, not dollars)

const priceCents = 1010;   // $10.10
const totalCents = priceCents * 3;
const totalDollars = (totalCents / 100).toFixed(2);   // "30.30"

Warning: Never use plain floats for money. Use cents, BigInt, or a decimal library (decimal.js, big.js).

Special Numeric Values

1 / 0           // Infinity
-1 / 0          // -Infinity
0 / 0           // NaN
"abc" * 2       // NaN
parseInt("xx")  // NaN

Infinity + 1    // Infinity
NaN + 1         // NaN
NaN === NaN     // false   ← NaN is not equal to itself

Number.isNaN(NaN);       // true   ← use this to test
Number.isFinite(1/0);    // false  ← excludes Infinity AND NaN
Value Coerces to boolean Test with
NaN false Number.isNaN(x)
Infinity true Number.isFinite(x) === false
-Infinity true same
0, -0 false Object.is(x, -0) for distinguishing

The Math Object — Quick Tour

A grab-bag of useful math utilities. (Full chapter is 23.)

Math.PI              // 3.141592653589793
Math.E               // 2.718281828459045

Math.abs(-7)         // 7
Math.sign(-5)        // -1
Math.sqrt(81)        // 9
Math.cbrt(27)        // 3
Math.pow(2, 10)      // 1024 (or use 2 ** 10)
Math.hypot(3, 4)     // 5

Math.min(3, 1, 2)    // 1
Math.max(3, 1, 2)    // 3

Math.round(2.5)      // 3   — rounds half away from zero (for positive)
Math.floor(2.9)      // 2
Math.ceil(2.1)       // 3
Math.trunc(-2.9)     // -2  — chops the decimal

Math.random()        // 0 ≤ x < 1

Random Integer in a Range

function randInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
randInt(1, 6);   // simulates a die roll

BigInt Arithmetic

For integers beyond Number.MAX_SAFE_INTEGER (≈ 9 quadrillion):

9007199254740993n + 1n        // 9007199254740994n

9007199254740993 + 1          // 9007199254740994  — looks right...
9007199254740993 === 9007199254740994   // true!  ← precision lost

BigInt is exact, but you can’t mix it with Number:

1n + 1     // ❌ TypeError
1n + 1n    // 2n
Number(2n) // 2

Real-World Example

// Compute a tip and round to cents, working in integers
function bill(subtotalCents, tipPercent) {
  const tipCents = Math.round(subtotalCents * tipPercent / 100);
  const totalCents = subtotalCents + tipCents;
  return {
    subtotal: (subtotalCents / 100).toFixed(2),
    tip:      (tipCents      / 100).toFixed(2),
    total:    (totalCents    / 100).toFixed(2),
  };
}

bill(4250, 18);
// { subtotal: '42.50', tip: '7.65', total: '50.15' }

Integers all the way through, conversion to display string at the end. Zero float surprises.

Best Practices Checklist

  • ✅ Use ** instead of Math.pow
  • ✅ Use Number.isNaN(x) and Number.isFinite(x), not the global versions
  • ✅ Work in integer units for money (cents, not dollars)
  • ✅ Add () when mixing precedences
  • ✅ Use toFixed(n) for display, not for comparison
  • ❌ Don’t compare floats with === — use an epsilon
  • ❌ Don’t mix BigInt and Number without explicit conversion
  • ❌ Don’t use ++ or -- inside larger expressions

What’s Next?

You can compute. Next up: how to update variables with the assignment operators — including the modern shorthand forms &&=, ||=, and ??=.

← Previous: JS Operators Next: JS Assignment →

Updated: