JavaScript Number Tricks Every Developer Should Know (2026)
Numbers in JavaScript are weirder than you think. Here are the tricks that save me from bugs.
The Big Gotcha: Floating Point
0.1 + 0.2 === 0.3 // false!
0.1 + 0.2 // 0.30000000000000004
This isn't a JavaScript bug — it's how IEEE 754 floating point works.
Fix #1: Comparing Numbers
// ❌ Never use == or === for floats
if (a === b) { ... } // Dangerous!
// ✅ Use epsilon comparison
function approxEqual(a, b, epsilon = Number.EPSILON) {
return Math.abs(a - b) < epsilon;
}
approxEqual(0.1 + 0.2, 0.3) // true
Fix #2: Currency (Never Use Floats!)
// ❌ WRONG
const price = 19.99;
const tax = price * 0.088; // 1.7591200000000002
const total = price + tax; // 21.7491200000000002
// ✅ Option A: Work in cents (integers)
const priceCents = 1999;
const taxCents = Math.round(priceCents * 88 / 1000); // 176
const totalCents = priceCents + taxCents; // 2175
console.log((totalCents / 100).toFixed(2)); // "21.75"
// ✅ Option B: Round at display time
const total = parseFloat((price * 1.088).toFixed(2)); // "21.75"
// ✅ Option C: Use a library (for serious financial apps)
// intl-numberformat, currency.js, or dinero.js
Useful Number Methods
Rounding Done Right
const num = 42.6789;
Math.round(num); // 43 (rounds to nearest)
Math.floor(num); // 42 (always down)
Math.ceil(num); // 43 (always up)
Math.trunc(num); // 42 (removes decimal)
num.toFixed(2); // "42.68" (returns STRING, not number!)
num.toPrecision(4); // "42.68" (significant digits)
// ⚠️ toFixed returns a string! Don't do math with it:
parseFloat(num.toFixed(2)) + 1; // 43.68 ✓
num.toFixed(2) + 1; // "42.681" ✗ (string concat!)
Clamping Values
// Keep number between min and max
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
clamp(-5, 0, 100); // 0
clamp(50, 0, 100); // 50
clamp(150, 0, 100); // 100
// Or with Math.clamp (newer browsers/Node)
Math.clamp(-5, 0, 100); // 0
Random Numbers
// Basic random
Math.random(); // 0.123... (0 ≤ x < 1)
// Random integer between min and max (inclusive)
function randomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
randomInt(1, 10); // 1-10 inclusive
randomInt(0, 1); // 0 or 1 (coin flip)
// Random element from array
const items = ['rock', 'paper', 'scissors'];
items[Math.floor(Math.random() * items.length)];
// Shuffle array (Fisher-Yates)
function shuffle(arr) {
const result = [...arr];
for (let i = result.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[result[i], result[j]] = [result[j], result[i]];
}
return result;
}
shuffle([1, 2, 3, 4, 5]); // [3, 1, 5, 2, 4] (random order)
// Random hex color
'#' + Math.floor(Math.random()*16777215).toString(16).padStart(6, '0');
// "#a3f2c1"
// Random ID generator
Math.random().toString(36).substring(2, 10);
// "x7k2m9np"
Number Formatting
// Add commas to large numbers
function formatNumber(num) {
return num.toLocaleString('en-US');
}
formatNumber(1000000); // "1,000,000"
formatNumber(1234567.89); // "1,234,567.89"
formatNumber(0.9876); // "0.988" (auto rounds)
// Currency formatting
function formatCurrency(amount, currency = 'USD') {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
}).format(amount);
}
formatCurrency(1234.56); // "$1,234.56"
formatCurrency(1234.56, 'EUR'); // "€1,234.56"
formatCurrency(1234.56, 'JPY'); // "¥1,235" (no decimals for JPY)
// Percentage
function formatPercent(num, decimals = 1) {
return new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
}).format(num);
}
formatPercent(0.867); // "86.7%"
formatPercent(0.05); // "5.0%"
// Compact notation (1.2K, 3.4M)
function formatCompact(num) {
return new Intl.NumberFormat('en-US', {
notation: 'compact',
compactDisplay: 'short',
}).format(num);
}
formatCompact(1500); // "1.5K"
formatCompact(2500000); // "2.5M"
formatCompact(999500); // "1M" (not 999K)
// Bytes to human readable
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
}
formatBytes(1024); // "1 KB"
formatBytes(1048576); // "1 MB"
formatBytes(1073741824); // "1 GB"
formatBytes(15360000); // "14.65 MB"
Parsing Numbers Safely
// ❌ Dangerous
parseInt('123abc'); // 123 (silently ignores non-numbers!)
parseInt(''); // NaN
parseInt(null); // NaN
parseInt(true); // NaN
parseInt('0x10'); // 16 (hex! might not be what you want)
parseFloat(' 42.5 '); // 42.5 (trims whitespace)
// ✅ Safe parsing
function safeParseInt(val, fallback = 0) {
const parsed = parseInt(val, 10);
return isNaN(parsed) ? fallback : parsed;
}
function safeParseFloat(val, fallback = 0) {
const parsed = parseFloat(val);
return isNaN(parsed) ? fallback : parsed;
}
safeParseInt('123abc', 0); // 123 (still accepts this)
safeParseInt('abc', 0); // 0 ← fallback
safeParseInt('', -1); // -1 ← fallback
// ✅ Even safer: validate first
function isNumeric(str) {
if (typeof str === 'number') return !isNaN(str);
if (typeof str !== 'string') return false;
return !isNaN(str) && !isNaN(parseFloat(str)) && isFinite(str);
}
isNumeric('42'); // true
isNumeric('42px'); // false
isNumeric(''); // false
isNumeric(null); // false
isNumeric(Infinity); // false
Math Tricks That Save Time
// Min/Max without Math.min/max (for small arrays)
const min = [3, 1, 4, 1, 5].sort((a,b)=>a-b)[0]; // 1
const max = [...[3, 1, 4, 1, 5]].sort((a,b)=>b-a)[0]; // 5
// But Math is faster for large arrays:
Math.min(...[3, 1, 4, 1, 5]); // 1
Math.max(...[3, 1, 4, 1, 5]); // 5
// ⚠️ Math.max/min spread can overflow the stack for huge arrays (>~65K elements)
// Power of 2 check
function isPowerOf2(n) {
return n > 0 && (n & (n - 1)) === 0;
}
isPowerOf2(8); // true
isPowerOf2(7); // false
isPowerOf2(1024); // true
// Between two numbers
function between(n, min, max) {
return n >= min && n <= max;
}
// Or clamp it: Math.min(max, Math.max(min, n))
// Distance between two numbers
function distance(a, b) {
return Math.abs(a - b);
}
// Linear interpolation (lerp)
function lerp(start, end, t) {
return start + (end - start) * t;
}
lerp(0, 100, 0.5); // 50
lerp(0, 100, 0.25); // 25
// Map value from one range to another
function mapRange(value, inMin, inMax, outMin, outMax) {
return outMin + ((outMax - outMin) * (value - inMin)) / (inMax - inMin);
}
mapRange(5, 0, 10, 0, 100); // 50
mapRange(7, 0, 10, 0, 1); // 0.7
// Average
function average(...nums) {
return nums.reduce((a, b) => a + b, 0) / nums.length;
}
average(1, 2, 3, 4, 5); // 3
// Median
function median(nums) {
const sorted = [...nums].sort((a, b) => a - b);
const mid = Math.floor(sorted.length / 2);
return sorted.length % 2 !== 0
? sorted[mid]
: (sorted[mid - 1] + sorted[mid]) / 2;
}
median([1, 2, 3, 4, 5]); // 3
median([1, 2, 3, 4]); // 2.5
// Sum
function sum(nums) {
return nums.reduce((a, b) => a + b, 0);
}
sum([1, 2, 3, 4, 5]); // 15
// Percent change
function percentChange(oldVal, newVal) {
if (oldVal === 0) return newVal > 0 ? Infinity : 0;
return ((newVal - oldVal) / Math.abs(oldVal)) * 100;
}
percentChange(100, 150); // 50 (% increase)
percentChange(100, 50); // -50 (% decrease)
percentChange(0, 50); // Infinity (from zero)
Weird JavaScript Number Facts
// These are all TRUE in JavaScript:
typeof NaN === 'number' // NaN IS a number type!
NaN === NaN // false (NaN is never equal to itself)
Number.isNaN(NaN) // true (correct way to check)
Infinity === 1/0 // true
-Infinity === -1/0 // true
1 / 0 === Infinity // true
0 / 0 === NaN // true
Number.MAX_VALUE + 1 === Number.MAX_VALUE // true (overflow stays at MAX)
Number.MIN_VALUE > 0 // true (smallest positive fraction)
Number.MAX_SAFE_INTEGER === 9007199254740991 // 2^53 - 1
Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 // true (precision lost!)
Number.isInteger(42) // true
Number.isInteger(42.0) // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false
// ParseInt quirks:
parseInt(1e21); // 1 (too big for int)
parseInt('1e21'); // 1 (same)
parseInt('0x10'); // 16 (hex detected)
parseInt('010'); // 10 (old behavior was octal, now decimal)
parseInt(''); // NaN
parseInt(null); // NaN
parseInt([]); // 0 (!!! coerces to "")
parseInt([5]); // 5 (!!! coerces to "5")
// Bitwise operators truncate to 32-bit integers:
| 0; // 0 (fast floor for positive numbers)
~~1.9; // 1 (double tilde = fast Math.floor for positives)
1.9 | 0; // 1
Quick Reference Card
| Task | Code |
|---|---|
| Round to 2 decimals |
+(n.toFixed(2)) or Math.round(n*100)/100
|
| Integer from float |
Math.trunc(n) or `n \ |
| Clamp | {% raw %}Math.min(max, Math.max(min, n))
|
| Random int 1-10 | Math.floor(Math.random() * 10) + 1 |
| Is integer? | Number.isInteger(n) |
| Is safe int? | Number.isSafeInteger(n) |
| Is NaN? |
Number.isNaN(n) (NOT global isNaN!) |
| Format as $ | n.toLocaleString('en-US',{style:'currency'}) |
| Parse safely |
parseInt(str, 10) always specify radix! |
| Sum array | arr.reduce((a,b)=>a+b, 0) |
| Unique random IDs | Math.random().toString(36).slice(2) |
What's your favorite number trick?
Follow @armorbreak for more practical JS guides.
Top comments (0)