JavaScript Foundations for Algorithmic Programming
Establish a JS toolkit tailored for writing correct, fast, and readable algorithmic code.
Content
Numbers and precision
Versions:
JavaScript Numbers and Precision: The Drama You Did Not Know You Signed Up For
"0.1 + 0.2 = 0.30000000000000004" — JavaScript, being honest about floating point since forever.
Why You Should Care (Even If You Don't Yet)
You're writing algorithms — you live and die by comparisons, bounds, and counts. Numbers are your bread, butter, and occasionally your mortal enemy. If you do any of the following:
- Binary search with floating thresholds
- Count things bigger than a safe integer
- Work with money (yikes)
- Implement geometry, statistics, or physics sims
…then JavaScript's number system matters. A lot. Like "why is my equality check crying in the corner" a lot.
This is your guided tour through the chaos — precision, pitfalls, and the hacks that keep your code from faceplanting.
The One Number Type (Mostly)
In JavaScript, the default number type is: double-precision IEEE‑754 floating point. Translation: 64 bits split into sign, exponent, and fraction. It does a lot, but not everything.
- Range: about ±1.79e308 (huge)
- Precision: ~15–17 decimal digits (not infinite)
- Safe integer range: exactly representable integers from −9,007,199,254,740,991 to +9,007,199,254,740,991
- These are
Number.MIN_SAFE_INTEGERandNumber.MAX_SAFE_INTEGER.
- These are
Example:
Number.MAX_SAFE_INTEGER === 9007199254740991 // true
9007199254740992 === 9007199254740993 // true 😬 not safe
Rule of thumb: if you care about exact integers beyond 15 digits, do not use Number. See: BigInt.
BigInt: When Your Integers Are Absolutely Swole
BigInt represents whole numbers of arbitrary size. Perfect for huge counts, cryptography, factorials, and "I need exactness" vibes.
- Create with
123norBigInt("123"). - You cannot mix
NumberandBigIntin arithmetic. Convert intentionally.
const a = 2n ** 100n; // big
// 1 + a // TypeError: Cannot mix BigInt and other types
Number(a) // be careful: may overflow
If you're storing counts, IDs, or factorials: BigInt. If you're doing trig, logs, or divisions with fractions: Number.
The Floating Point Plot Twist
Decimal fractions like 0.1 don't convert cleanly to binary. JS stores approximations.
0.1 + 0.2 === 0.3 // false
0.1 + 0.2 // 0.30000000000000004
(1.005).toFixed(2) // '1.01'? '1.00'? Depends on binary representation
This is not a bug — it's math meeting binary storage. Your job is to work with it, not scream at your laptop. (You can still scream. Catharsis matters.)
Comparisons: The Zen of "Close Enough"
For floating point comparisons, never use strict equality unless numbers are, like, identical constants computed the same way. Instead, use a tolerance.
const nearlyEqual = (a, b, eps = Number.EPSILON * Math.max(1, Math.abs(a), Math.abs(b))) =>
Math.abs(a - b) <= eps;
nearlyEqual(0.1 + 0.2, 0.3); // true
Number.EPSILONis the smallest difference where1 + x !== 1(~2.22e-16). Think of it as the machine's tiny whisper.- Scale
epsrelative to magnitude to compare big or small numbers fairly.
Rounding: Your Four Favorite Frenemies
Math.floor(x): round down (toward −∞)Math.ceil(x): round up (toward +∞)Math.round(x): round to nearest integer (ties at .5 go to +∞)Math.trunc(x): drop the fractional part (toward 0)
Math.floor(-1.2) // -2
Math.ceil(-1.2) // -1
Math.trunc(-1.2) // -1
(2.55).toFixed(1) // '2.6' (string!)
Caveats:
toFixed(n)andtoPrecision(n)return strings, and they round — but binary imprecision can still surprise you.- For money, avoid
toFixedfor storage; it’s for display. Store scaled integers instead.
Money and Other Decimal Drama: Scale It
If you care about exact cents, meters, grams — store integers. Multiply by a scale factor, compute, then format.
// $19.99 + $0.10 => scale to cents
const sumCents = 1999 + 10; // 2009
const display = (cents) => (cents / 100).toFixed(2);
display(sumCents); // '20.09'
Alternatively, use a decimal library (e.g., Big.js, Decimal.js) when you need precise decimal arithmetic without scaling gymnastics.
The Remainder Operator: It’s Not True Modulo
In JS, % is the remainder, not mathematical modulo. It keeps the sign of the dividend.
5 % 3 // 2
-5 % 3 // -2 (not 1)
If you want a non-negative modulo result:
const mod = (n, m) => ((n % m) + m) % m;
mod(-5, 3) // 1
NaN, Infinity, and the Soap Opera of -0
NaNmeans "not a number" — a special number that is the messiest number of all time.NaN !== NaNis true. Don’t equality-check it. UseNumber.isNaN(x).
Infinityand-Infinityare results of overflow or division by zero (1/0→Infinity).-0exists. It’s real. It matters in some algorithms (direction-sensitive things).
Number.isNaN(NaN) // true
isNaN('foo') // true (coerces!) avoid; use Number.isNaN
Object.is(0, -0) // false — Object.is can distinguish +0 and -0
Parsing and Formatting: Subtle Traps
- Always pass a radix to
parseInt.
parseInt('08') // 8 (modern engines), but always do:
parseInt('08', 10) // 8
parseFloat('3.14px') // 3.14, stops at first non-number
Number('3.14') // 3.14, but Number('3.14px') -> NaN
- Numeric separators improve readability but not parsing:
const million = 1_000_000; // ✅ valid
Number('1_000_000') // NaN — not valid in strings
- Exponent notation is your friend:
1e6 === 1000000.
Bitwise Ops: Secretly 32-bit
All bitwise operations in JS coerce to signed 32-bit integers. Useful for masks, hashes, but beware overflow.
(1 << 31) // -2147483648 (sign bit)
(1 >>> 31) // 1 (unsigned right shift)
(3.7 | 0) // 3 (fast trunc in 32-bit range)
Never use bitwise tricks on values outside 32-bit or when you need exact floating math.
Summation Without Tears: Reduce Error Like a Pro
When summing lots of floats, small errors accumulate. Stable summation helps.
- Naive sum can drift.
- Kahan summation keeps a running compensation for lost low-order bits.
function kahanSum(arr) {
let sum = 0;
let c = 0; // compensation
for (const x of arr) {
const y = x - c;
const t = sum + y;
c = (t - sum) - y;
sum = t;
}
return sum;
}
Use this when you care about accurate sums of many small terms (e.g., statistics).
Which Numeric Tool Do I Use?
| Scenario | Use | Why |
|---|---|---|
| Geometry, physics, trig | Number + tolerance | Floating math with comparisons that allow wiggle room |
| Money, decimal counts | Scaled integers or decimal lib | Exact decimals; avoid binary rounding drama |
| Huge counters, factorials, IDs | BigInt | Exact integers beyond 2^53 − 1 |
| Bit masks, flags | Number with bitwise ops | Works in 32-bit lane |
Micro-Recipes You’ll Actually Use
- Safe float compare:
const approxEq = (a, b, eps = 1e-12) => Math.abs(a - b) <= eps * Math.max(1, Math.abs(a), Math.abs(b));
- Clamp with care:
const clamp = (x, lo, hi) => Math.min(hi, Math.max(lo, x));
- Normalize angle to [0, 2π):
const TWO_PI = Math.PI * 2;
const normAngle = (θ) => ((θ % TWO_PI) + TWO_PI) % TWO_PI;
- Big factorial (exact):
const fact = (n) => {
let r = 1n;
for (let i = 2n; i <= BigInt(n); i++) r *= i;
return r;
};
Common Gotchas (A Non-Exhaustive Roast)
- "But it's only two decimals!" — Binary doesn't care about your feelings or your cents.
toFixedreturns strings. Accidentally concatenating? Welcome to'42.00' + 1 === '42.001'land.%is remainder. You wanted modulo. Admit it.NaNnever equals anything, including itself, because it's chaos in numeric form.- Mixing
BigIntandNumberthrows. Convert explicitly. - Bitwise ops shrink your world to 32 bits. If you overflow, that's on you and your hubris.
Wrap-Up: The Vibe Check
JavaScript numbers are powerful but… particular. Use them right, and your algorithms are fast and correct. Use them wrong, and your binary search becomes existentialist fiction.
Key takeaways:
- Numbers are IEEE‑754 doubles. Great range, ~15–17 digits of precision.
- Use
BigIntfor exact large integers. Don’t mix withNumbercarelessly. - Float comparisons need tolerances.
Number.EPSILONis your friend. - For money/decimals: scale to integers or use a decimal library.
- Understand
%,NaN,Infinity, and-0— or they will understand you. - When summing many floats, consider Kahan or other stable algorithms.
Final thought: Precision isn’t about perfection; it’s about controlling error so your code behaves predictably. Master that, and numbers stop being gremlins and start being tools.
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!