Math and number handling
Numbers in code are not numbers in math. C# ints wrap silently at ±2³¹, JS numbers are doubles that stop being exact past 2⁵³, and only Python ints never overflow. Integer division floors in Python but truncates in TS and C#, so -7 % 2 is 1 in one and -1 in the others. Floats can’t store 0.1 exactly: compare with a tolerance and keep money in integers.
The idea in plain words
Section titled “The idea in plain words”A car’s odometer has six digits. Drive one mile past 999,999 and it shows 000,000. It didn’t crash; it just ran out of digits and kept the low ones. That’s integer overflow, and it’s what a C# int does by default.
A ruler marked only in eighths of an inch can’t show a tenth of an inch. It shows the nearest mark. That’s floating point: a double stores the nearest sum of powers of two, and 0.1 isn’t one.
Interview code breaks on numbers in four predictable places:
- Size. Does every intermediate value fit in the type? (Sums of 10⁵ values up to 10⁹ reach 10¹⁴: too big for int, fine for long.)
- Sign. Which way does division round with negatives? What sign does
%return? - Exactness. Is this value a float that only looks exact?
- Precision of the algorithm. Should this be integer arithmetic all the way (gcd, modular arithmetic, cross products) instead of a float?
See it
Section titled “See it”Three labs. In each, change the numbers and watch where the languages disagree.
| language | expression | quotient | remainder | rounds |
|---|---|---|---|---|
| Python | a // b, a % b | -4 | 1 | toward −∞ (floor) |
| TypeScript | Math.trunc(a / b), a % b | -3 | -1 | toward 0 (truncate) |
| C# | a / b, a % b | -3 | -1 | toward 0 (truncate) |
| TypeScript | Math.floor(a / b) | -4 | – | floor, but via a float |
a = q·b + r: -7 = -4·2 + 1 = -3·2 + -1. Python's remainder takes the sign of b; TS and C# take the sign of a.When to reach for it
Section titled “When to reach for it”| The problem says… | What to think about |
|---|---|
| “values up to 10⁹”, “n up to 10⁵”, “sum of…” | Multiply the bounds. Over 2.1·10⁹ needs 64 bits; over 9·10¹⁵ is unsafe for JS numbers. |
| “return the answer modulo 10⁹ + 7” | Reduce after every + and *. In JS, products of two residues need BigInt. |
negative numbers and % or / |
Rounding direction differs by language. Wrap indices with a floor-mod. |
| “fractions”, “ratios”, “slopes”, “same line” | Avoid floats: reduce p/q by gcd, or cross-multiply to compare. |
| money, prices, percentages | Integer minor units (cents) or a decimal type. See money arithmetic. |
| “every k days”, “both cycles line up”, “tile exactly” | gcd and lcm. |
How it works
Section titled “How it works”1. Know your integer types
Section titled “1. Know your integer types”| Python | TypeScript | C# | |
|---|---|---|---|
| default integer | int, unbounded |
number, a 64-bit double |
int, 32-bit |
| exact integer range | unlimited | ±(2⁵³ − 1) = Number.MAX_SAFE_INTEGER |
int ±2.1·10⁹, long ±9.2·10¹⁸ |
| on overflow | never overflows | rounds to the nearest double, silently | wraps silently (unchecked); throws in checked |
| big integers | built in | bigint (10n), can’t mix with number |
System.Numerics.BigInteger, also Int128 (.NET 7+) |
| 32-bit wrapping on purpose | (x + 2**31) % 2**32 - 2**31 |
x | 0, Math.imul |
default |
def to_int32(x: int) -> int: """Wrap an exact Python int the way a 32-bit two's-complement register would.""" return (x + 2**31) % 2**32 - 2**31 # 1. shift, reduce mod 2^32, shift back
if __name__ == "__main__": INT_MAX, INT_MIN = 2**31 - 1, -(2**31) # 2. Python ints never overflow; they grow as needed. assert INT_MAX + 1 == 2_147_483_648 assert 2**64 == 18_446_744_073_709_551_616 # 3. What C# int (unchecked) or JS `x | 0` would give you instead: assert to_int32(INT_MAX + 1) == INT_MIN assert to_int32(50_000 * 50_000) == -1_794_967_296 assert to_int32(-5) == -5 # 4. Floats DO have limits: past 2^53, not every integer is representable. assert float(2**53) == float(2**53 + 1) # 5. Safe midpoint: in fixed-width languages lo + hi can overflow; this form cannot. lo, hi = 2_000_000_000, 2_100_000_000 assert lo + (hi - lo) // 2 == 2_050_000_000 print("ok")import assert from "node:assert/strict";
// 1. Every JS number is a 64-bit float. Integers are exact only up to 2^53 - 1.assert.equal(Number.MAX_SAFE_INTEGER, 2 ** 53 - 1);assert.equal(2 ** 53 + 1, 2 ** 53); // silently rounded, no errorassert.equal(Number.isSafeInteger(2 ** 53), false);
// 2. Bitwise operators convert to int32 first, so they wrap like C# int.const INT_MAX = 2 ** 31 - 1;assert.equal((INT_MAX + 1) | 0, -(2 ** 31));assert.equal(Math.imul(50_000, 50_000), -1_794_967_296); // 32-bit multiplyassert.equal(50_000 * 50_000, 2_500_000_000); // ordinary * does not wrap
// 3. Need exact big integers? Use BigInt (and you cannot mix it with number).assert.equal(2n ** 64n, 18_446_744_073_709_551_616n);assert.equal(BigInt.asIntN(32, 2_147_483_648n), -2_147_483_648n); // emulate int32 wrap
/** 4. Guard a product before trusting it. */export function safeMultiply(a: number, b: number): number { const p = a * b; if (!Number.isSafeInteger(p)) throw new RangeError(`${a} * ${b} is not exact`); return p;}assert.equal(safeMultiply(3_000_000, 3_000_000), 9e12);assert.throws(() => safeMultiply(1e9 + 7, 1e9 + 7), RangeError);console.log("ok");// 1. C# int arithmetic is unchecked by default: it wraps silently.int max = int.MaxValue;int wrapped = unchecked(max + 1);if (wrapped != int.MinValue) throw new Exception("expected wrap to MinValue");int big = 50_000;if (unchecked(big * big) != -1_794_967_296) throw new Exception("expected wrapped product");
// 2. `checked` turns overflow into an exception. Use it where a wrong number is worse than a crash.bool threw = false;try { _ = checked(big * big); } catch (OverflowException) { threw = true; }if (!threw) throw new Exception("expected OverflowException");
// 3. Widen BEFORE multiplying: casting the result is too late.long late = unchecked(big * big); // int * int wrapped, then widenedlong early = (long)big * big; // long * int, exactif (late == early || early != 2_500_000_000) throw new Exception("widen first");
// 4. Two edge cases that throw even in unchecked code.try { _ = Math.Abs(int.MinValue); throw new Exception("no throw"); } catch (OverflowException) { }int minusOne = -1, min = int.MinValue;try { _ = min / minusOne; throw new Exception("no throw"); } catch (OverflowException) { }
// 5. Safe midpoint: lo + hi can overflow; this form cannot.int lo = 2_000_000_000, hi = 2_100_000_000;if (lo + (hi - lo) / 2 != 2_050_000_000) throw new Exception("midpoint");Console.WriteLine("ok");2. Integer division and remainder
Section titled “2. Integer division and remainder”All three languages promise a == q*b + r. They disagree on which way q rounds:
-7 / 2 (int) |
-7 % 2 |
rule | |
|---|---|---|---|
Python //, % |
−4 | 1 | floor (toward −∞); r has the sign of b |
TypeScript Math.trunc(a / b), % |
−3 | −1 | truncate (toward 0); r has the sign of a |
C# /, % |
−3 | −1 | truncate (toward 0); r has the sign of a |
The everyday consequence: stepping backwards around a ring of size n. In Python (i - 1) % n just works. In TS and C# it gives -1 for i = 0; use ((i - 1) % n + n) % n or a helper.
import math
def trunc_div_mod(a: int, b: int) -> tuple[int, int]: """What TS and C# do with / and %: quotient rounds toward zero, remainder takes a's sign.""" q = abs(a) // abs(b) * (1 if (a < 0) == (b < 0) else -1) return q, a - q * b
if __name__ == "__main__": # 1. Python's // floors (rounds toward -infinity); % takes the sign of the divisor. assert (7 // 2, 7 % 2) == (3, 1) assert (-7 // 2, -7 % 2) == (-4, 1) assert divmod(-7, 2) == (-4, 1) # 2. Truncating semantics, as in TS / C#, for comparison. assert trunc_div_mod(-7, 2) == (-3, -1) assert math.fmod(-7, 2) == -1.0 # fmod follows C: sign of the dividend # 3. Both satisfy a == q*b + r; they only differ in which way q rounds. for a, b in [(-7, 2), (7, -2), (-7, -2)]: q, r = divmod(a, b) assert a == q * b + r # 4. Never write int(a / b) for integers: a / b is a float and loses precision. assert int(10**17 / 3) != 10**17 // 3 assert (-1) % 5 == 4 # wrap an index backwards around a ring of 5 print("ok")import assert from "node:assert/strict";
/** Python-style floor modulo: result has the sign of b. Exact for safe integers. */export function floorMod(a: number, b: number): number { const r = a % b; // 1. JS % truncates: r has the sign of a return r !== 0 && r < 0 !== b < 0 ? r + b : r; // 2. signs disagree: shift into b's range}
/** Python-style floor division (rounds toward -infinity). */export function floorDiv(a: number, b: number): number { return (a - floorMod(a, b)) / b; // 3. a - r is an exact multiple of b, so this divides cleanly}
assert.equal(-7 % 2, -1); // the trap: % keeps the dividend's signassert.equal(Math.trunc(-7 / 2), -3); // integer division idiom rounds toward zeroassert.equal(floorMod(-7, 2), 1);assert.equal(floorDiv(-7, 2), -4);assert.equal(floorMod(7, -2), -1);assert.equal(floorMod(-1, 5), 4); // wrap an index backwards around a ring of 5for (const [a, b] of [[-7, 2], [7, -2], [-7, -2], [9, 3]]) { assert.equal(floorDiv(a, b) * b + floorMod(a, b), a); // a == q*b + r always holds}assert.equal(-7n / 2n, -3n); // BigInt division truncates tooconsole.log("ok");// Python-style floor modulo: result has the sign of b.static long FloorMod(long a, long b){ long r = a % b; // 1. C# % truncates: r has the sign of a return r != 0 && (r < 0) != (b < 0) ? r + b : r; // 2. signs disagree: shift into b's range}
// Python-style floor division (rounds toward -infinity).static long FloorDiv(long a, long b) => (a - FloorMod(a, b)) / b; // 3. exact: a - r is a multiple of b
if (-7 / 2 != -3 || -7 % 2 != -1) throw new Exception("C# truncates toward zero");if (FloorMod(-7, 2) != 1 || FloorDiv(-7, 2) != -4) throw new Exception("floor -7,2");if (FloorMod(7, -2) != -1) throw new Exception("floor 7,-2");if (FloorMod(-1, 5) != 4) throw new Exception("ring wrap");foreach (var (a, b) in new (long, long)[] { (-7, 2), (7, -2), (-7, -2), (9, 3) }) if (FloorDiv(a, b) * b + FloorMod(a, b) != a) throw new Exception("identity");var (q, rem) = Math.DivRem(-7, 2); // built-in, still truncatingif (q != -3 || rem != -1) throw new Exception("DivRem");Console.WriteLine("ok");3. gcd and lcm
Section titled “3. gcd and lcm”Euclid’s algorithm rests on one fact: any number dividing both a and b also divides a mod b. So gcd(a, b) = gcd(b, a mod b), and the numbers shrink fast: at most about 5 × (digits of the smaller number) steps (Lamé’s theorem), i.e. O(log min(a, b)).
- While
bisn’t 0, replace(a, b)with(b, a mod b). - When
breaches 0,ais the gcd. lcm(a, b) = a / gcd(a, b) * b. Divide first so the intermediate stays small.
import math
def gcd(a: int, b: int) -> int: """Euclid: gcd(a, b) = gcd(b, a mod b). O(log min(a, b)) steps.""" a, b = abs(a), abs(b) while b: # 1. replace (a, b) by (b, a mod b) until b hits 0 a, b = b, a % b return a # 2. gcd(a, 0) = a
def lcm(a: int, b: int) -> int: if a == 0 or b == 0: return 0 return abs(a) // gcd(a, b) * abs(b) # 3. divide first: in fixed-width languages a*b can overflow
if __name__ == "__main__": assert gcd(84, 36) == 12 and gcd(36, 84) == 12 assert gcd(-12, 18) == 6 and gcd(0, 5) == 5 assert lcm(4, 6) == 12 and lcm(0, 7) == 0 assert gcd(84, 36) == math.gcd(84, 36) and lcm(4, 6) == math.lcm(4, 6) # stdlib, 3.9+ print("ok")import assert from "node:assert/strict";
/** Euclid: gcd(a, b) = gcd(b, a mod b). O(log min(a, b)) steps. JS has no built-in gcd. */export function gcd(a: number, b: number): number { a = Math.abs(a); b = Math.abs(b); while (b !== 0) [a, b] = [b, a % b]; // 1. replace (a, b) by (b, a mod b) until b hits 0 return a; // 2. gcd(a, 0) = a}
export function lcm(a: number, b: number): number { if (a === 0 || b === 0) return 0; return (Math.abs(a) / gcd(a, b)) * Math.abs(b); // 3. divide first so the product stays small}
assert.equal(gcd(84, 36), 12);assert.equal(gcd(36, 84), 12);assert.equal(gcd(-12, 18), 6);assert.equal(gcd(0, 5), 5);assert.equal(lcm(4, 6), 12);assert.equal(lcm(0, 7), 0);console.log("ok");// Euclid: gcd(a, b) = gcd(b, a mod b). O(log min(a, b)) steps.// .NET has BigInteger.GreatestCommonDivisor but no Math.Gcd for long.static long Gcd(long a, long b){ a = Math.Abs(a); b = Math.Abs(b); while (b != 0) (a, b) = (b, a % b); // 1. replace (a, b) by (b, a mod b) until b hits 0 return a; // 2. gcd(a, 0) = a}
static long Lcm(long a, long b) => a == 0 || b == 0 ? 0 : Math.Abs(a) / Gcd(a, b) * Math.Abs(b); // 3. divide first: a*b may overflow
if (Gcd(84, 36) != 12 || Gcd(36, 84) != 12) throw new Exception("gcd 84,36");if (Gcd(-12, 18) != 6 || Gcd(0, 5) != 5) throw new Exception("gcd signs/zero");if (Lcm(4, 6) != 12 || Lcm(0, 7) != 0) throw new Exception("lcm");// Both operands near 3e9: a*b alone would be ~9e18, right at long's edge; dividing first is safe.if (Lcm(3_000_000_000, 2_000_000_000) != 6_000_000_000) throw new Exception("lcm big");Console.WriteLine("ok");Stdlib: Python has math.gcd and math.lcm (3.9+). .NET has BigInteger.GreatestCommonDivisor but no Math.Gcd. JS has neither.
4. Modular arithmetic
Section titled “4. Modular arithmetic”When an answer is huge (“count the ways…”), problems ask for it modulo a prime, usually 10⁹ + 7. The rules: (a + b) mod m and (a · b) mod m can be computed from a mod m and b mod m, so reduce after every operation and numbers never grow. Division is different: you multiply by a modular inverse, which for prime m is a^(m−2) mod m (Fermat’s little theorem).
Fast exponentiation reads the exponent’s bits: square the base each step, multiply it into the result when the bit is 1. That’s O(log e) multiplications instead of e.
MOD = 1_000_000_007
def mod_pow(base: int, exp: int, m: int) -> int: """base**exp % m by repeated squaring: O(log exp) multiplications.""" result = 1 base %= m while exp > 0: if exp & 1: # 1. this bit of exp is set: multiply it in result = result * base % m base = base * base % m # 2. square for the next bit; reduce every step exp >>= 1 return result
if __name__ == "__main__": assert mod_pow(2, 10, 1000) == 24 assert mod_pow(3, 0, 7) == 1 assert mod_pow(123456789, 10**18, MOD) == pow(123456789, 10**18, MOD) # built in # 3. Modular inverse when m is prime (Fermat): a^(m-2). Python 3.8+: pow(a, -1, m). assert mod_pow(5, MOD - 2, MOD) * 5 % MOD == 1 assert pow(5, -1, MOD) == mod_pow(5, MOD - 2, MOD) print("ok")import assert from "node:assert/strict";
const MOD = 1_000_000_007n;
/** base**exp % m by repeated squaring. BigInt: (1e9)^2 is past 2^53, so number would lose digits. */export function modPow(base: bigint, exp: bigint, m: bigint): bigint { let result = 1n; base %= m; while (exp > 0n) { if (exp & 1n) result = (result * base) % m; // 1. this bit of exp is set: multiply it in base = (base * base) % m; // 2. square for the next bit; reduce every step exp >>= 1n; } return result;}
assert.equal(modPow(2n, 10n, 1000n), 24n);assert.equal(modPow(3n, 0n, 7n), 1n);// 3. Modular inverse when m is prime (Fermat): a^(m-2).assert.equal((modPow(5n, MOD - 2n, MOD) * 5n) % MOD, 1n);// Why BigInt: with plain numbers the product of two residues is not exact.const r = 999_999_999;assert.notEqual(BigInt(r * r), BigInt(r) * BigInt(r)); // 999999998000000001 needs 60 bitsconsole.log("ok");using System.Numerics;
const long Mod = 1_000_000_007;
// base^exp % m by repeated squaring. With m < 2^31, a product of residues < 2^62 fits in long.static long ModPow(long b, long exp, long m){ long result = 1; b %= m; while (exp > 0) { if ((exp & 1) == 1) result = result * b % m; // 1. this bit of exp is set: multiply it in b = b * b % m; // 2. square for the next bit; reduce every step exp >>= 1; } return result;}
if (ModPow(2, 10, 1000) != 24) throw new Exception("expected 24");if (ModPow(3, 0, 7) != 1) throw new Exception("expected 1");if (ModPow(123456789, 1_000_000_000_000_000_000, Mod) != (long)BigInteger.ModPow(123456789, 1_000_000_000_000_000_000, Mod)) throw new Exception("vs BigInteger");// 3. Modular inverse when m is prime (Fermat): a^(m-2).if (ModPow(5, Mod - 2, Mod) * 5 % Mod != 1) throw new Exception("inverse");Console.WriteLine("ok");5. Floating point
Section titled “5. Floating point”A double (IEEE 754 binary64, the type of every JS number, Python float and C# double) has 1 sign bit, 11 exponent bits and 52 fraction bits: about 15-17 significant decimal digits. Consequences:
- 0.1, 0.2 and 0.3 aren’t exact.
0.1 + 0.2is0.30000000000000004. Compare with a tolerance. - Rounding errors add up. Ten additions of
0.1give0.9999999999999999. - Large numbers lose small ones.
1e16 + 1 == 1e16. - Special values.
NaN != NaN;0.0 == -0.0; dividing by zero givesInfinity, not an exception (for floats).
| Python | TypeScript | C# | |
|---|---|---|---|
| tolerant compare | math.isclose(a, b) |
write your own (below) | write your own (below) |
| exact decimal | decimal.Decimal("0.1") |
none built in: use integer cents or a library | decimal (0.1m), 28-29 digits |
| exact fraction | fractions.Fraction(1, 3) |
none | none |
| accurate sum | math.fsum |
none | none |
import mathfrom decimal import Decimalfrom fractions import Fraction
def nearly_equal(a: float, b: float, rel: float = 1e-9, abs_tol: float = 1e-12) -> bool: """Relative tolerance for big values, absolute tolerance near zero.""" return abs(a - b) <= max(rel * max(abs(a), abs(b)), abs_tol)
if __name__ == "__main__": # 1. 0.1 has no exact binary form, so errors appear in the last digit. assert 0.1 + 0.2 != 0.3 assert repr(0.1 + 0.2) == "0.30000000000000004" # 2. Compare with a tolerance, never ==. assert nearly_equal(0.1 + 0.2, 0.3) assert math.isclose(0.1 + 0.2, 0.3) # stdlib, rel_tol=1e-09 by default # 3. Exact decimal arithmetic: construct from strings, not floats. assert Decimal("0.1") + Decimal("0.2") == Decimal("0.3") assert Decimal(0.1) != Decimal("0.1") # Decimal(0.1) captures the binary error # 4. Exact rationals. assert Fraction(1, 10) + Fraction(2, 10) == Fraction(3, 10) # 5. A running += drifts; math.fsum tracks the lost bits (3.12+ sum() compensates too). total = 0.0 for _ in range(10): total += 0.1 assert total != 1.0 and math.fsum([0.1] * 10) == 1.0 print("ok")import assert from "node:assert/strict";
/** Relative tolerance for big values, absolute tolerance near zero. */export function nearlyEqual(a: number, b: number, rel = 1e-9, absTol = 1e-12): boolean { return Math.abs(a - b) <= Math.max(rel * Math.max(Math.abs(a), Math.abs(b)), absTol);}
// 1. 0.1 has no exact binary form, so errors appear in the last digit.assert.notEqual(0.1 + 0.2, 0.3);assert.equal(String(0.1 + 0.2), "0.30000000000000004");// 2. Compare with a tolerance, never ===.assert.ok(nearlyEqual(0.1 + 0.2, 0.3));assert.equal(Number.EPSILON, 2 ** -52); // gap between 1 and the next double// 3. JS has no decimal type. For money, count integer cents (or use BigInt).const cents = 10 + 20;assert.equal(cents / 100, 0.3); // a single rounding at display time is fine// 4. Adding many small floats drifts.let total = 0;for (let i = 0; i < 10; i++) total += 0.1;assert.notEqual(total, 1);assert.ok(nearlyEqual(total, 1));// 5. NaN is not equal to itself; -0 equals 0 but is distinguishable.assert.ok(Number.isNaN(0 / 0) && NaN !== NaN);assert.ok(-0 === 0 && Object.is(-0, -0) && !Object.is(-0, 0));console.log("ok");// 1. 0.1 has no exact binary form, so errors appear in the last digit.double x = 0.1 + 0.2;if (x == 0.3) throw new Exception("expected 0.1 + 0.2 != 0.3");if (x.ToString("R") != "0.30000000000000004") throw new Exception(x.ToString("R"));
// 2. Compare with a tolerance, never ==.if (!NearlyEqual(x, 0.3)) throw new Exception("tolerance compare");
// 3. decimal is base 10 with 28-29 significant digits: exact for 0.1, slower than double.if (0.1m + 0.2m != 0.3m) throw new Exception("decimal is exact here");if (1m / 3m * 3m == 1m) throw new Exception("but 1/3 still rounds");
// 4. Adding many small doubles drifts.double total = 0;for (int i = 0; i < 10; i++) total += 0.1;if (total == 1.0 || !NearlyEqual(total, 1.0)) throw new Exception("drift");
// 5. NaN is not equal to itself; double.IsNaN is the test.double nan = 0.0 / 0.0;#pragma warning disable CS1718if (nan == nan || !double.IsNaN(nan)) throw new Exception("NaN");#pragma warning restore CS1718Console.WriteLine("ok");
// Relative tolerance for big values, absolute tolerance near zero.static bool NearlyEqual(double a, double b, double rel = 1e-9, double absTol = 1e-12) => Math.Abs(a - b) <= Math.Max(rel * Math.Max(Math.Abs(a), Math.Abs(b)), absTol);What it costs
Section titled “What it costs”| Operation | Cost | Note |
|---|---|---|
+ − * / on fixed-width ints or doubles |
O(1) | one CPU instruction |
Python int / bigint / BigInteger with d digits |
+: O(d); *: about O(d^1.585) (Karatsuba) for big d |
“free” until numbers get huge |
| gcd(a, b) | O(log min(a, b)) | Euclid |
b^e mod m |
O(log e) multiplications | repeated squaring |
decimal (C#, Python) |
10-100× slower than double | software arithmetic |
An interviewer can push on this: “your solution is O(n), but each step multiplies two growing Python ints”. If numbers grow to n digits, the “O(1)” arithmetic isn’t. Modular reduction keeps every number small and every step truly O(1).
Variations you’ll meet
Section titled “Variations you’ll meet”- Integer square root.
math.isqrt(n)in Python. In other languages,Math.sqrton a double can be off by one for values near 2⁵³: correct withwhile r*r > n: r--andwhile (r+1)*(r+1) <= n: r++, or binary search. - Ceiling division.
ceil(a / b)for positive ints is(a + b - 1) // b, or-(-a // b)in Python. Avoid the floatMath.ceil(a / b)for large values. - Sieve of Eratosthenes. All primes up to n in O(n log log n).
- Cross products instead of slopes. To test whether three points are collinear, check
(x2-x1)*(y3-y1) == (y2-y1)*(x3-x1)with integers. No division, no floats. - Counting with combinations.
C(n, k) mod pvia precomputed factorials and inverse factorials. - Bit tricks live on their own page: bit manipulation.
Pitfalls
Section titled “Pitfalls”- Overflow in the middle, not the result.
a * b / coverflows even when the answer fits. Reorder (a / gcd * b) or widen. Math.abs(int.MinValue)throws in C#:-2³¹has no positive counterpart in 32 bits. Same forint.MinValue / -1.int(a / b)in Python goes through a float. For big ints it’s wrong. Usea // b.parseIntand big JSON numbers in JS. Anything past 2⁵³ is already rounded when it’s parsed. Ids like that must travel as strings.- Comparing floats with
==, or using floats as hash keys. Round, scale to integers, or use a tolerance. - Money in floats.
0.1 + 0.2dollars isn’t 30 cents. Use integer cents. - Sort comparators that subtract.
(a, b) => a - boverflows in C# with large ints (wrong sign) and loses precision in JS past 2⁵³. Compare instead:a.CompareTo(b).
In the interview
Section titled “In the interview”- Ask for the bounds: largest value, longest input, can values be negative? Then say the arithmetic out loud: “n is 10⁵ and values are up to 10⁹, so the sum can reach 10¹⁴. I’ll use a long.”
- In TypeScript, mention when you’re relying on
numberbeing exact: “these sums stay under 2⁵³, so plain numbers are fine.” - With negative operands, say which modulo you mean and use a helper rather than
%directly. - If you see division with floats, ask yourself whether integers can do it: cross-multiply, reduce by gcd, or work in cents.
- Expect follow-ups: “What if the values were 64-bit?” (overflow of the sum: BigInteger, or 128-bit), “What if this ran on money?” (decimal, rounding modes, banker’s rounding).
Check yourself
Section titled “Check yourself”Practice on your own
Section titled “Practice on your own”Classic textbook exercises, not gym questions:
- Reverse the digits of a 32-bit integer, returning 0 if the result overflows, without using 64-bit types.
- Add two non-negative integers given as decimal strings of any length.
- Count primes below n with the sieve of Eratosthenes.
- Compute
C(n, k) mod 10⁹ + 7for many queries with n ≤ 10⁶. - Given points with integer coordinates, find the largest number that lie on one line, using gcd-reduced slopes instead of floats.
Go deeper
Section titled “Go deeper”- Python docs: binary arithmetic operations, the floor division and modulo rules.
- MDN: Remainder (%) and Number.MAX_SAFE_INTEGER.
- C# reference: arithmetic operators, including integer division, overflow and
checked. - David Goldberg, “What Every Computer Scientist Should Know About Floating-Point Arithmetic” (1991). Also cp-algorithms: binary exponentiation and Euclidean algorithm.