JavaScript Arithmetic
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 ofMath.pow - ✅ Use
Number.isNaN(x)andNumber.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
BigIntandNumberwithout 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 ??=.