JavaScript Number Tricks Every Developer Should Know (2026)

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.