Number Tricks: Integer Check, Digit Length, Exponent, GCD, Primes, Random Hex
A collection of small number-theory and number-formatting recipes that show up constantly: inspecting a number (integer check, digit length), looped algorithms (binary exponent, Euclidean GCD, prime listing), digit-level transforms (sort, reverse, sum), and a couple of random helpers (hex color, plus pointers to the existing range and shuffle entries). Each accordion is its own tight group so you can pull just the recipe you need.
781 views
20
// Number.isInteger is strict: rejects strings, NaN, Infinity, floats.
console.log(Number.isInteger(42)); // true
console.log(Number.isInteger(42.0)); // true (still an integer value)
console.log(Number.isInteger(42.5)); // false
console.log(Number.isInteger('42')); // false (string)
console.log(Number.isInteger(NaN)); // false
console.log(Number.isInteger(Infinity)); // false
// Compare with the looser isFinite + Math.trunc check:
const isWholeNumber = (x) => Number.isFinite(x) && Math.trunc(x) === x;
console.log(isWholeNumber(42.0)); // true
console.log(isWholeNumber(42.5)); // false
// Digit length: math version (no string coercion).
const digitLengthMath = (n) => {
if (n === 0) return 1;
return Math.floor(Math.log10(Math.abs(n))) + 1;
};
console.log(digitLengthMath(0)); // 1
console.log(digitLengthMath(7)); // 1
console.log(digitLengthMath(12345)); // 5
console.log(digitLengthMath(-9999)); // 4 (sign ignored)
// Digit length: string version (handles edge cases more naturally).
const digitLengthString = (n) => Math.abs(Math.trunc(n)).toString().length;
console.log(digitLengthString(12345)); // 5
console.log(digitLengthString(-9999)); // 4Number.isInteger is the modern, type-aware integer check; it rejects strings even if they look like numbers, which is usually the safe behavior. The looser isWholeNumber helper is for when you have already accepted a number-or-numeric-string input and just want "no fractional part". For digit length, the math form (floor(log10(abs(n))) + 1) is fast and avoids allocating a string, but it has a degenerate case at zero (the formula yields -Infinity), so guard with an explicit zero check. The string form (.toString().length) is shorter and gets the zero case for free; use it unless you are in a hot loop.
// Iterative exponent via while-loop. For full speed, use Math.pow / **,
// but this shows the principle.
const pow = (base, exp) => {
let result = 1;
let count = 0;
while (count < exp) {
result *= base;
count++;
}
return result;
};
console.log(pow(2, 10)); // 1024
console.log(pow(3, 4)); // 81
// Binary exponentiation: O(log exp) instead of O(exp). The right one for
// large exponents (used in modular arithmetic, RSA, etc.).
const powFast = (base, exp) => {
let result = 1;
let b = base;
let e = exp;
while (e > 0) {
if (e & 1) result *= b;
b *= b;
e >>>= 1;
}
return result;
};
console.log(powFast(2, 20)); // 1048576
// Euclidean GCD. Handles negatives by taking abs at the start.
const gcd = (a, b) => {
a = Math.abs(a);
b = Math.abs(b);
while (b) {
[a, b] = [b, a % b];
}
return a;
};
console.log(gcd(48, 18)); // 6
console.log(gcd(17, 5)); // 1 (coprime)
console.log(gcd(0, 7)); // 7
// LCM falls out of GCD: lcm(a, b) = |a * b| / gcd(a, b).
const lcm = (a, b) => Math.abs(a * b) / gcd(a, b);
console.log(lcm(4, 6)); // 12
// Primes between two integers, using trial division up to sqrt(n).
const isPrime = (n) => {
if (n < 2) return false;
if (n % 2 === 0) return n === 2;
for (let i = 3; i * i <= n; i += 2) {
if (n % i === 0) return false;
}
return true;
};
const primesBetween = (lo, hi) => {
const out = [];
for (let n = lo; n <= hi; n++) {
if (isPrime(n)) out.push(n);
}
return out;
};
console.log(primesBetween(10, 30)); // [11, 13, 17, 19, 23, 29]
console.log(primesBetween(2, 10)); // [2, 3, 5, 7]Iterative exponent (pow) shows the principle but does O(exp) multiplications; binary exponentiation (powFast) squares and conditionally multiplies, doing only O(log exp) work and matching the algorithm libraries use under the hood. Euclidean GCD walks (a, b) -> (b, a mod b) until the remainder is zero; the destructuring swap keeps the body to one line. LCM uses the identity a * b = gcd(a, b) * lcm(a, b), derived directly from prime factorizations. For prime listing in a small range, trial division up to sqrt(n) is fast enough; if you need every prime up to a large N, switch to a Sieve of Eratosthenes for O(N log log N).
// Sort digits descending.
const sortDigitsDesc = (n) =>
Number(
Math.abs(n)
.toString()
.split('')
.sort((a, b) => Number(b) - Number(a))
.join('')
);
console.log(sortDigitsDesc(43512)); // 54321
console.log(sortDigitsDesc(11203)); // 32110
console.log(sortDigitsDesc(9)); // 9
// Reverse digits (works on negatives by preserving the sign).
const reverseDigits = (n) => {
const sign = n < 0 ? -1 : 1;
return sign * Number(Math.abs(n).toString().split('').reverse().join(''));
};
console.log(reverseDigits(12345)); // 54321
console.log(reverseDigits(-9870)); // -789 (leading zero on reverse is dropped)
// Sum of digits (digit root style without modulo math).
const sumOfDigits = (n) =>
Math.abs(n)
.toString()
.split('')
.reduce((acc, ch) => acc + Number(ch), 0);
console.log(sumOfDigits(12345)); // 1 + 2 + 3 + 4 + 5 = 15
console.log(sumOfDigits(0)); // 0All three digit tricks share the same shape: convert to string, manipulate as a character array, convert back to a number. sortDigitsDesc uses a numeric comparator (Number(b) - Number(a)) because the default sort would compare characters lexicographically (which happens to match numerics for single digits, but is fragile to depend on). reverseDigits carries the sign explicitly because string reversal would otherwise leave the minus sign at the end; note the trailing-zero gotcha (-9870 reversed becomes -789 when parsed back to a number). sumOfDigits is a one-liner via reduce. For large numbers (above Number.MAX_SAFE_INTEGER, ~9e15), use BigInt and a different conversion pipeline.
// Random 6-digit hex color, padded so leading zeros survive.
const randomHexColor = () =>
'#' + Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0');
console.log(randomHexColor()); // e.g. '#3a7f1b'
console.log(randomHexColor()); // e.g. '#0042ff'
// Demonstrate that padStart matters: without it, '#42ff' would be invalid.
const withoutPad = () => '#' + Math.floor(Math.random() * 0xffffff).toString(16);
// withoutPad() can return e.g. '#42ff' which is wrong; padStart fixes it.
// Countdown to zero from a given number (synchronous version, prints to console).
const countdown = (from) => {
const out = [];
for (let n = from; n >= 0; n--) out.push(n);
return out;
};
console.log(countdown(5)); // [5, 4, 3, 2, 1, 0]
console.log(countdown(0)); // [0]
// For random INTEGER in a range, see the existing `js-math-random-int` snippet.
// For unbiased ARRAY SHUFFLE, see the existing `js-array-shuffle` snippet.The hex color trick combines Math.random() with 0xffffff (16,777,215, the max 6-digit hex value) and toString(16) to produce a hex string. The padStart(6, '0') is critical: small integers like 0x42ff produce a 4-character string and the result #42ff is not a valid CSS color. The countdown helper returns the values as an array so the caller can format or render them; for an animation, drive a setInterval that decrements a counter. Random integer in a range and unbiased array shuffle are deliberately not duplicated here; both already live as their own snippets in the catalog and have additional context (modulo bias, Fisher-Yates correctness) you should not lose.
