/* Chalkboard - Number Theory Namespace Version 3.0.2 Euler Released April 13th, 2026 */ /* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /// namespace Chalkboard { /** * The number theory namespace. * @namespace */ export namespace numb { /** * Returns a random number from a Bernoulli distribution. * @param {number} [p=0.5] - Probability value of distribution * @returns {number} * @example * // Probability of flipping a coin heads or tails * const bernoulliRandom = Chalkboard.numb.Bernoullian(); */ export const Bernoullian = (p: number = 0.5): number => { if (typeof p !== "number" || !Number.isFinite(p) || p < 0 || p > 1) throw new Error(`Chalkboard.numb.Bernoullian: Parameter "p" must be a finite number between 0 and 1.`); return Math.random() < p ? 1 : 0; }; /** * Returns the binomial coefficient for a polynomial. * @param {number} n - The degree of the polynomial * @param {number} k - The term of the polynomial * @returns {number} * @example * // Returns 35 * const coeff = Chalkboard.numb.binomial(7, 3); */ export const binomial = (n: number, k: number): number => { if (!Number.isInteger(n) || !Number.isInteger(k) || n < 0 || k < 0) throw new Error(`Chalkboard.numb.binomial: Parameters "n" and "k" must be non-negative integers.`); if (k < 0 || k > n) return 0; if (k === 0 || k === n) return 1; if (k === 1 || k === n - 1) return n; if (n - k < k) k = n - k; let result = n; for (let i = 2; i <= k; i++) result *= (n - i + 1) / i; return Math.round(result); }; /** * Returns the change of two numbers. * @param {number} initial - First number * @param {number} final - Second number * @returns {number} * @example * // Returns 1 or 100% * const change = Chalkboard.numb.change(1, 2); */ export const change = (initial: number, final: number): number => { if (typeof initial !== "number" || typeof final !== "number" || !Number.isFinite(initial) || !Number.isFinite(final)) throw new Error(`Chalkboard.numb.change: Parameters "initial" and "final" must be finite numbers.`); if (initial === 0) throw new Error(`Chalkboard.numb.change: Parameter "initial" must be non-zero.`); return (final - initial) / initial; }; /** * Returns the combinatorial combination of two numbers. * @param {number} n - First number (total items) * @param {number} r - Second number (chosen items) * @returns {number} * @example * // The number of five-card hands possible from a standard fifty-two card deck is 2598960 * const combine = Chalkboard.numb.combination(52, 5); */ export const combination = (n: number, r: number): number => { if (!Number.isInteger(n) || !Number.isInteger(r) || n < 0 || r < 0 || r > n) throw new Error(`Chalkboard.numb.combination: Parameters "n" and "r" must be integers with 0 <= r <= n.`); return Chalkboard.numb.binomial(n, r); }; /** * Returns an array of composite numbers between the lower and upper bounds. * @param {number} inf - Lower bound * @param {number} sup - Upper bound * @returns {number[]} * @example * // Returns the array [4, 6, 8, 9, ... , 996, 998, 999, 1000] * const arr = Chalkboard.numb.compositeArr(0, 1000); */ export const compositeArr = (inf: number, sup: number): number[] => { if (!Number.isInteger(inf) || !Number.isInteger(sup)) throw new Error(`Chalkboard.numb.compositeArr: Parameters "inf" and "sup" must be integers.`); if (inf > sup) throw new Error(`Chalkboard.numb.compositeArr: Parameter "inf" must be less than or equal to "sup".`); if (sup < 4) return []; const sieve = new Uint8Array(sup + 1); for (let p = 2; p * p <= sup; p++) if (sieve[p] === 0) for (let i = p * p; i <= sup; i += p) sieve[i] = 1; const result: number[] = []; const start = Math.max(4, inf); for (let i = start; i <= sup; i++) if (sieve[i] === 1) result.push(i); return result; }; /** * Returns the number of composite numbers between the lower and upper bounds. * @param {number} inf - Lower bound * @param {number} sup - Upper bound * @returns {number} * @example * // Returns 832 * const composites = Chalkboard.numb.compositeCount(0, 1000); */ export const compositeCount = (inf: number, sup: number): number => { if (!Number.isInteger(inf) || !Number.isInteger(sup)) throw new Error(`Chalkboard.numb.compositeCount: Parameters "inf" and "sup" must be integers.`); if (inf > sup) throw new Error(`Chalkboard.numb.compositeCount: Parameter "inf" must be less than or equal to "sup".`); return Chalkboard.numb.compositeArr(inf, sup).length; }; /** * Returns a number constrained within a range. * @param {number} num - Number * @param {number[]} [range=[0, 1]] - Range * @returns {number} * @example * const n1 = Chalkboard.numb.constrain(2); // Returns 1 * const n2 = Chalkboard.numb.constrain(1); // Returns 1 * const n3 = Chalkboard.numb.constrain(0.5); // Returns 0.5 * const n4 = Chalkboard.numb.constrain(0); // Returns 0 * const n5 = Chalkboard.numb.constrain(-1); // Returns 0 */ export const constrain = (num: number, range: [number, number] = [0, 1]): number => { if (typeof num !== "number" || !Number.isFinite(num)) throw new Error(`Chalkboard.numb.constrain: Parameter "num" must be a finite number.`); if (!Array.isArray(range) || range.length !== 2 || typeof range[0] !== "number" || typeof range[1] !== "number" || !Number.isFinite(range[0]) || !Number.isFinite(range[1]) || range[0] > range[1]) throw new Error(`Chalkboard.numb.constrain: Parameter "range" must be an array of two finite numbers [min, max] with min <= max.`); return Math.max(Math.min(num, range[1]), range[0]); }; /** * Converts a number from and to various different measurement units. * @param {number | number[]} num - Number or numbers * @param {string} from - Original measurement unit * @param {string} to - Desired measurement unit * @returns {number | number[]} * @example * const m = cb.numb.convert(1500, "mm", "m"); // Length conversion * const sqmi = cb.numb.convert(1000000, "m2", "mi2"); // Area conversion * const kg = cb.numb.convert(5000, "g", "kg"); // Mass conversion * const ml = cb.numb.convert(3, "gal", "mL"); // Volume conversion * const pa = cb.numb.convert(1, "atm", "Pa"); // Pressure conversion * const ns = cb.numb.convert(2, "hr", "ns"); // Time conversion * const k = cb.numb.convert(98.6, "F", "K"); // Temperature conversion */ export const convert = (num: number | number[], from: string, to: string): number | number[] => { if (typeof from !== "string" || typeof to !== "string") throw new Error(`Chalkboard.numb.convert: Parameters "from" and "to" must be strings.`); if (Array.isArray(num)) { for (let i = 0; i < num.length; i++) if (typeof num[i] !== "number" || !Number.isFinite(num[i])) throw new Error(`Chalkboard.numb.convert: Parameter "num[${i}]" must be a finite number.`); } else { if (typeof num !== "number" || !Number.isFinite(num)) throw new Error(`Chalkboard.numb.convert: Parameter "num" must be a finite number.`); } const normalize = (str: string): string => str.trim().replace(/\s+/g, " "); const canonicalize = (str: string): string => normalize(str).replace(/\u00B5/g, "μ"); const ALIASES: Record = { "NM": "nm", "Nm": "nm", "µm": "μm", "CM": "cm", "Cm": "cm", "M": "m", "KM": "km", "Km": "km", "IN": "in", "In": "in", "FT": "ft", "Ft": "ft", "YD": "yd", "Yd": "yd", "MI": "mi", "Mi": "mi", "NMI": "nmi", "Nmi": "nmi", "M2": "m2", "CM2": "cm2", "MM2": "mm2", "KM2": "km2", "FT2": "ft2", "IN2": "in2", "YD2": "yd2", "MI2": "mi2", "NG": "ng", "µg": "μg", "MG": "mg", "G": "g", "KG": "kg", "Kg": "kg", "LB": "lb", "Lb": "lb", "l": "L", "ml": "mL", "cl": "cL", "dl": "dL", "dal": "daL", "hl": "hL", "kl": "kL", "ul": "uL", "μl": "μL", "µl": "μL", "fl. oz": "fl oz", "fl.oz": "fl oz", "floz": "fl oz", "OZ": "oz", "Oz": "oz", "pa": "Pa", "hpa": "hPa", "kpa": "kPa", "mpa": "MPa", "gpa": "GPa", "ATM": "atm", "Atm": "atm", "Torr": "torr", "mmhg": "mmHg", "NS": "ns", "US": "μs", "Us": "μs", "µS": "μs", "MS": "ms", "Ms": "ms", "HR": "h", "Hr": "h", "HRS": "h", "Hrs": "h", "YR": "yr", "Yr": "yr", "WK": "wk", "Wk": "wk", "c": "C", "°c": "C", "°C": "C", "celsius": "C", "f": "F", "°f": "F", "°F": "F", "fahrenheit": "F", "k": "K", "°k": "K", "°K": "K", "kelvin": "K", "r": "R", "°r": "R", "°R": "R", "rankine": "R" }; const LENGTH: Record = { "fm": 1e-15, "femtometer": 1e-15, "femtometers": 1e-15, "pm": 1e-12, "picometer": 1e-12, "picometers": 1e-12, "nm": 1e-9, "nanometer": 1e-9, "nanometers": 1e-9, "μm": 1e-6, "um": 1e-6, "micrometer": 1e-6, "micrometers": 1e-6, "mm": 1e-3, "millimeter": 1e-3, "millimeters": 1e-3, "cm": 1e-2, "centimeter": 1e-2, "centimeters": 1e-2, "dm": 1e-1, "decimeter": 1e-1, "decimeters": 1e-1, "m": 1, "meter": 1, "meters": 1, "dam": 1e1, "decameter": 1e1, "decameters": 1e1, "hm": 1e2, "hectometer": 1e2, "hectometers": 1e2, "km": 1e3, "kilometer": 1e3, "kilometers": 1e3, "Mm": 1e6, "megameter": 1e6, "megameters": 1e6, "Gm": 1e9, "gigameter": 1e9, "gigameters": 1e9, "Tm": 1e12, "terameter": 1e12, "terameters": 1e12, "Pm": 1e15, "petameter": 1e15, "petameters": 1e15, "in": 0.0254, "inch": 0.0254, "inches": 0.0254, "ft": 0.3048, "foot": 0.3048, "feet": 0.3048, "yd": 0.9144, "yard": 0.9144, "yards": 0.9144, "mi": 1609.344, "mile": 1609.344, "miles": 1609.344, "nmi": 1852, "nautical mile": 1852, "nautical miles": 1852, "ly": 9.4607e15, "lyr": 9.4607e15, "light year": 9.4607e15, "light years": 9.4607e15 }; const AREA: Record = { "mm²": 1e-6, "mm2": 1e-6, "square millimeter": 1e-6, "square millimeters": 1e-6, "cm²": 1e-4, "cm2": 1e-4, "square centimeter": 1e-4, "square centimeters": 1e-4, "dm²": 1e-2, "dm2": 1e-2, "square decimeter": 1e-2, "square decimeters": 1e-2, "m²": 1, "m2": 1, "square meter": 1, "square meters": 1, "a": 100, "are": 100, "ares": 100, "ha": 1e4, "hectare": 1e4, "hectares": 1e4, "km²": 1e6, "km2": 1e6, "square kilometer": 1e6, "square kilometers": 1e6, "in²": 0.00064516, "in2": 0.00064516, "square inch": 0.00064516, "square inches": 0.00064516, "ft²": 0.09290304, "ft2": 0.09290304, "square foot": 0.09290304, "square feet": 0.09290304, "yd²": 0.83612736, "yd2": 0.83612736, "square yard": 0.83612736, "square yards": 0.83612736, "acre": 4046.8564224, "acres": 4046.8564224, "mi²": 2589988.110336, "mi2": 2589988.110336, "square mile": 2589988.110336, "square miles": 2589988.110336 }; const MASS: Record = { "fg": 1e-15, "femtogram": 1e-15, "femtograms": 1e-15, "pg": 1e-12, "picogram": 1e-12, "picograms": 1e-12, "ng": 1e-9, "nanogram": 1e-9, "nanograms": 1e-9, "μg": 1e-6, "ug": 1e-6, "microgram": 1e-6, "micrograms": 1e-6, "mg": 1e-3, "milligram": 1e-3, "milligrams": 1e-3, "cg": 1e-2, "centigram": 1e-2, "centigrams": 1e-2, "dg": 1e-1, "decigram": 1e-1, "decigrams": 1e-1, "g": 1, "gram": 1, "grams": 1, "dag": 1e1, "decagram": 1e1, "decagrams": 1e1, "hg": 1e2, "hectogram": 1e2, "hectograms": 1e2, "kg": 1e3, "kilogram": 1e3, "kilograms": 1e3, "Mg": 1e6, "megagram": 1e6, "megagrams": 1e6, "Gg": 1e9, "gigagram": 1e9, "gigagrams": 1e9, "Tg": 1e12, "teragram": 1e12, "teragrams": 1e12, "Pg": 1e15, "petagram": 1e15, "petagrams": 1e15, "oz": 28.349523125, "ounce": 28.349523125, "ounces": 28.349523125, "lb": 453.59237, "lbs": 453.59237, "pound": 453.59237, "pounds": 453.59237, "st": 6350.29318, "stone": 6350.29318, "stones": 6350.29318, "tn": 907184.74, "ton": 907184.74, "tons": 907184.74, "t": 1000000, "metric ton": 1000000, "metric tons": 1000000 }; const VOLUME: Record = { "fL": 1e-15, "femtoliter": 1e-15, "femtoliters": 1e-15, "pL": 1e-12, "picoliter": 1e-12, "picoliters": 1e-12, "nL": 1e-9, "nanoliter": 1e-9, "nanoliters": 1e-9, "μL": 1e-6, "uL": 1e-6, "microliter": 1e-6, "microliters": 1e-6, "mL": 1e-3, "milliliter": 1e-3, "milliliters": 1e-3, "cL": 1e-2, "centiliter": 1e-2, "centiliters": 1e-2, "dL": 1e-1, "deciliter": 1e-1, "deciliters": 1e-1, "L": 1, "l": 1, "liter": 1, "liters": 1, "daL": 1e1, "decaliter": 1e1, "decaliters": 1e1, "hL": 1e2, "hectoliter": 1e2, "hectoliters": 1e2, "kL": 1e3, "kiloliter": 1e3, "kiloliters": 1e3, "ML": 1e6, "megaliter": 1e6, "megaliters": 1e6, "GL": 1e9, "gigaliter": 1e9, "gigaliters": 1e9, "TL": 1e12, "teraliter": 1e12, "teraliters": 1e12, "PL": 1e15, "petaliter": 1e15, "petaliters": 1e15, "tsp": 0.00492892159, "teaspoon": 0.00492892159, "teaspoons": 0.00492892159, "tbsp": 0.0147867648, "Tbsp": 0.0147867648, "tablespoon": 0.0147867648, "tablespoons": 0.0147867648, "fl oz": 0.0295735296, "fluid ounce": 0.0295735296, "fluid ounces": 0.0295735296, "cp": 0.2365882365, "cup": 0.2365882365, "cups": 0.2365882365, "pt": 0.473176473, "pint": 0.473176473, "pints": 0.473176473, "qt": 0.946352946, "quart": 0.946352946, "quarts": 0.946352946, "gal": 3.785411784, "gallon": 3.785411784, "gallons": 3.785411784 }; const PRESSURE: Record = { "Pa": 1, "pascal": 1, "pascals": 1, "hPa": 100, "hectopascal": 100, "hectopascals": 100, "kPa": 1000, "kilopascal": 1000, "kilopascals": 1000, "MPa": 1e6, "megapascal": 1e6, "megapascals": 1e6, "GPa": 1e9, "gigapascal": 1e9, "gigapascals": 1e9, "bar": 1e5, "bars": 1e5, "mbar": 100, "millibar": 100, "millibars": 100, "atm": 101325, "atmosphere": 101325, "atmospheres": 101325, "torr": 133.32236842105263, "mmHg": 133.32236842105263, "millimeter of mercury": 133.32236842105263, "millimeters of mercury": 133.32236842105263, "psi": 6894.757293168, "pound per square inch": 6894.757293168, "pounds per square inch": 6894.757293168 }; const TIME: Record = { "ns": 1e-9, "nanosecond": 1e-9, "nanoseconds": 1e-9, "μs": 1e-6, "us": 1e-6, "microsecond": 1e-6, "microseconds": 1e-6, "ms": 1e-3, "millisecond": 1e-3, "milliseconds": 1e-3, "s": 1, "sec": 1, "secs": 1, "second": 1, "seconds": 1, "min": 60, "mins": 60, "minute": 60, "minutes": 60, "h": 3600, "hr": 3600, "hrs": 3600, "hour": 3600, "hours": 3600, "d": 86400, "day": 86400, "days": 86400, "w": 604800, "wk": 604800, "wks": 604800, "week": 604800, "weeks": 604800, "yr": 31557600, "yrs": 31557600, "year": 31557600, "years": 31557600 }; const TEMPERATURE: Record = { "K": { a: 1, b: 0, key: "K" }, "C": { a: 1, b: 273.15, key: "C" }, "F": { a: 5 / 9, b: 273.15 - (32 * 5) / 9, key: "F" }, "R": { a: 5 / 9, b: 0, key: "R" } }; const TABLE: Array<[string, Record]> = [["length", LENGTH], ["area", AREA], ["mass", MASS], ["volume", VOLUME], ["pressure", PRESSURE], ["time", TIME]]; const resolveFactor = (str: string): { category: string; factor: number; key: string } | undefined => { const key = canonicalize(str); const alias = ALIASES[key] ?? ALIASES[key.toLowerCase()]; const candidates: string[] = []; candidates.push(key); if (alias) candidates.push(alias); const shouldTryLower = key.length > 2 || key.includes(" "); if (shouldTryLower) candidates.push(key.toLowerCase()); const seen = new Set(); const uniq = candidates.filter((c) => (seen.has(c) ? false : (seen.add(c), true))); for (const [category, map] of TABLE) { for (const k of uniq) { if (Object.prototype.hasOwnProperty.call(map, k)) return { category, factor: map[k], key: k }; } } return undefined; }; const resolveTemp = (str: string): { a: number; b: number; key: string } | undefined => { const raw = canonicalize(str); const alias = ALIASES[raw] ?? ALIASES[raw.toLowerCase()]; const candidates: string[] = [raw]; if (alias) candidates.push(alias); const seen = new Set(); const uniq = candidates.filter((c) => (seen.has(c) ? false : (seen.add(c), true))); for (const k of uniq) if (Object.prototype.hasOwnProperty.call(TEMPERATURE, k)) return TEMPERATURE[k]; return undefined; }; const apply = (fn: (x: number) => number) => (Array.isArray(num) ? num.map(fn) : fn(num)); const fromTemp = resolveTemp(from); const toTemp = resolveTemp(to); if (fromTemp || toTemp) { if (!fromTemp) throw new Error(`Chalkboard.numb.convert: Unknown temperature unit: "${from}".`); if (!toTemp) throw new Error(`Chalkboard.numb.convert: Unknown temperature unit: "${to}".`); const toKelvin = (x: number) => fromTemp.a * x + fromTemp.b; const fromKelvin = (k: number) => (k - toTemp.b) / toTemp.a; return apply((x) => fromKelvin(toKelvin(x))); } const fromResolved = resolveFactor(from); const toResolved = resolveFactor(to); if (!fromResolved) throw new Error(`Chalkboard.numb.convert: Unknown unit: "${from}".`); if (!toResolved) throw new Error(`Chalkboard.numb.convert: Unknown unit: "${to}".`); if (fromResolved.category !== toResolved.category) throw new Error(`Chalkboard.numb.convert: Incompatible unit conversion: "${from}" (${fromResolved.category}) -> "${to}" (${toResolved.category}).`); const factor = fromResolved.factor / toResolved.factor; return apply((x) => x * factor); }; /** * Returns the divisors of a number. * @param {number} num - Number * @returns {number[]} * @example * // Returns the array [1, 2, 4, ... , 250000, 500000, 1000000] * const divisors = Chalkboard.numb.divisors(1000000); */ export const divisors = (num: number): number[] => { if (!Number.isInteger(num) || num <= 0) throw new Error(`Chalkboard.numb.divisors: Parameter "num" must be a positive integer.`); const result: number[] = []; const upper = Math.floor(Math.sqrt(num)); for (let i = 1; i <= upper; i++) { if (num % i === 0) { result.push(i); if (i !== num / i) result.push(num / i); } } return result.sort((a, b) => a - b); }; /** * Returns the value of Euler's totient function of a number. * @param {number} num - Number greater than 0 * @returns {number} * @example * // Returns 4, the number of integers less than or equal to 10 that are coprime to 10 * const totient = Chalkboard.numb.Euler(10); */ export const Euler = (num: number): number => { if (!Number.isInteger(num) || num <= 0) throw new Error(`Chalkboard.numb.Euler: Parameter "num" must be a positive integer.`); const primeFactors = Chalkboard.numb.factors(num); const uniquePrimes: number[] = []; for (let i = 0; i < primeFactors.length; i++) { const p = primeFactors[i]; if (uniquePrimes.indexOf(p) === -1) uniquePrimes.push(p); } let result = num; for (const p of uniquePrimes) result *= (p - 1) / p; return Math.round(result); }; /** * Returns a random number from an exponential distribution. * @param {number} [l=1] - Rate parameter (lambda) of distribution * @returns {number} * @example * // Smaller argument means a more uniform distribution * const expRandom = Chalkboard.numb.exponential(0.1); */ export const exponential = (l: number = 1): number => { if (typeof l !== "number" || !Number.isFinite(l)) throw new Error(`Chalkboard.numb.exponential: Parameter "l" must be a finite number.`); if (l <= 0) throw new Error(`Chalkboard.numb.exponential: Parameter "l" must be positive.`); const u = 1 - Math.random(); return -Math.log(u) / l; }; /** * Returns the factorial of a number. * @param {number} num - Number * @returns {number} * @example * // Returns 120 * const factorial = Chalkboard.numb.factorial(5); */ export const factorial = (num: number): number => { if (!Number.isInteger(num) || num < 0) throw new Error(`Chalkboard.numb.factorial: Parameter "num" must be a non-negative integer.`); let n = 1; for (let i = 2; i <= num; i++) n *= i; return n; }; /** * Returns the prime factors of a number. * @param {number} num - Number * @returns {number[]} * @example * // Returns the array [2, 2, 2, 2, 2, 2, 5, 5, 5, 5, 5, 5] * const factors = Chalkboard.numb.factors(1000000); */ export const factors = (num: number): number[] => { if (!Number.isInteger(num)) throw new Error(`Chalkboard.numb.factors: Parameter "num" must be an integer.`); if (num === 0) throw new Error(`Chalkboard.numb.factors: Parameter "num" must be non-zero.`); const result: number[] = []; if (num < 0) { result.push(-1); num = Math.abs(num); } while (num % 2 === 0) { result.push(2); num /= 2; } for (let i = 3; i <= Chalkboard.real.sqrt(num); i += 2) { while (num % i === 0) { result.push(i); num /= i; } } if (num > 1) result.push(num); return result; }; /** * Returns the term of the Fibonacci sequence at the inputted index. * @param {number} num - Index * @returns {number} * @example * // Returns 55 * const fibnum = Chalkboard.numb.Fibonacci(10); */ export const Fibonacci = (num: number): number => { if (!Number.isInteger(num) || num < 0) throw new Error(`Chalkboard.numb.Fibonacci: Parameter "num" must be a non-negative integer.`); if (num === 0) return 0; if (num === 1) return 1; let a = 0, b = 1; for (let i = 2; i <= num; i++) { const next = a + b; a = b; b = next; } return b; }; /** * Returns a random number from a Gaussian (normal) distribution. * @param {number} mean - Mean of distribution * @param {number} deviation - Standard deviation of distribution (σ > 0) * @returns {number} * @example * // Standard Gaussian distribution * const gaussRandom = Chalkboard.numb.Gaussian(0, 1); */ export const Gaussian = (mean: number, deviation: number): number => { if (!Number.isFinite(mean) || !Number.isFinite(deviation)) throw new Error(`Chalkboard.numb.Gaussian: Parameters "mean" and "deviation" must be finite numbers.`); if (deviation <= 0) throw new Error(`Chalkboard.numb.Gaussian: Parameter "deviation" must be positive.`); let u1 = 0; while (u1 === 0) u1 = Math.random(); const u2 = Math.random(); const z = Chalkboard.real.sqrt(-2 * Chalkboard.real.ln(u1)) * Chalkboard.trig.cos(Chalkboard.PI(2) * u2); return mean + z * deviation; }; /** * Returns the greatest common divisor of two numbers. * @param {number} a - First number * @param {number} b - Second number * @returns {number} * @example * // Returns 17 * const gcd = Chalkboard.numb.gcd(68, 119); */ export const gcd = (a: number, b: number): number => { if (!Number.isInteger(a) || !Number.isInteger(b)) throw new Error(`Chalkboard.numb.gcd: Parameters "a" and "b" must be integers.`); a = Math.abs(a); b = Math.abs(b); while (b !== 0) { const t = a % b; a = b; b = t; } return a; }; /** * Returns an even number as a sum of two prime numbers. * @param {number} num - Even number * @returns {[number, number] | undefined} * @example * // Returns [5, 7] * const primes = Chalkboard.numb.Goldbach(12); */ export const Goldbach = (num: number): [number, number] | undefined => { if (!Number.isInteger(num) || num < 4 || num % 2 !== 0) throw new Error(`Chalkboard.numb.Goldbach: Parameter "num" must be an even integer greater than or equal to 4.`); if (num !== 4) { let a = num / 2, b = num / 2; if (a % 2 === 0) { a--; b++; } while (a >= 3) { if (Chalkboard.numb.isPrime(a) && Chalkboard.numb.isPrime(b)) return [a, b]; a -= 2; b += 2; } return undefined; } else { return [2, 2]; } }; /** * Checks if two numbers are approximately equal. * @param {number} a - The first number * @param {number} b - The second number * @param {number} [precision=0.000001] - The precision to check * @returns {boolean} * @example * // Returns true * const approx = Chalkboard.numb.isApproxEqual(0.1 + 0.2, 0.3); */ export const isApproxEqual = (a: number, b: number, precision: number = 0.000001): boolean => { if (typeof a !== "number" || typeof b !== "number" || typeof precision !== "number" || !Number.isFinite(a) || !Number.isFinite(b) || !Number.isFinite(precision) || precision <= 0) throw new Error(`Chalkboard.numb.isApproxEqual: Parameters "a", "b", and "precision" must be finite numbers, and "precision" must be positive.`); return Math.abs(a - b) < precision; }; /** * Checks if a number is a prime number. * @param {number} num - Number * @returns {boolean} * @example * // All of the following return true * Chalkboard.numb.isPrime(73939133); * Chalkboard.numb.isPrime(7393913); * Chalkboard.numb.isPrime(739391); * Chalkboard.numb.isPrime(73939); * Chalkboard.numb.isPrime(7393); * Chalkboard.numb.isPrime(739); * Chalkboard.numb.isPrime(73); * Chalkboard.numb.isPrime(7); */ export const isPrime = (num: number): boolean => { if (typeof num !== "number" || !Number.isInteger(num) || num < 2) return false; if (num === 2) return true; if (num % 2 === 0) return false; for (let i = 3; i * i <= num; i += 2) if (num % i === 0) return false; return true; }; /** * Checks if a number is rational. * @param {number} num - The number to check. * @param {number} [tolerance = 1e-8] - Tolerance for approximation (optional, defaults to 1e-8). * @returns {boolean} * @example * const yes = Chalkboard.numb.isRational(0.75); // Returns true * const no = Chalkboard.numb.isRational(Chalkboard.PI()); // Returns false */ export const isRational = (num: number, tolerance: number = 1e-8): boolean => { if (typeof num !== "number" || !Number.isFinite(num) || typeof tolerance !== "number" || !Number.isFinite(tolerance) || tolerance <= 0) return false; const mult = num / Chalkboard.PI(); if (mult !== 0 && Math.abs(Math.round(mult) - mult) < tolerance) { return false; } if (num > 0) { const ln = Math.log(num); if (ln !== 0 && Math.abs(Math.round(ln) - ln) < tolerance) { const pow = Chalkboard.E(Math.round(ln)); if (Math.abs(num - pow) < tolerance) { return false; } } } for (let d = 2; d <= 6; d++) { const fract = Chalkboard.PI() / d; for (let n = 1; n <= d * 4; n++) { if (n % d !== 0) { if (Math.abs(num - n * fract) < tolerance) { return false; } } } } const knownIrrationals = [Chalkboard.E(-1), Chalkboard.E(0.5), Chalkboard.real.sqrt(Chalkboard.PI()), Chalkboard.E(), Chalkboard.PI(), Chalkboard.E(2)]; for (let i = 2; i <= 100; i++) { if (Number.isInteger(Math.sqrt(i))) continue; knownIrrationals.push(Chalkboard.real.sqrt(i)); } for (const irr of knownIrrationals) { if (Math.abs(num - irr) < tolerance) { return false; } } try { const [n, d] = Chalkboard.numb.toFraction(num, tolerance); return (Math.abs(num - n / d) < tolerance) && (Math.abs(d) <= 100000); } catch { return false; } }; /** * Returns the value of the Kronecker delta function of two numbers (returns 1 if they're equal and 0 otherwise). * @param {number} a - First number * @param {number} b - Second number * @returns {number} * @example * const yes = Chalkboard.numb.Kronecker(1, 1); // Returns 1 * const no = Chalkboard.numb.Kronecker(1, 10); // Returns 0 */ export const Kronecker = (a: number, b: number): 1 | 0 => { if (typeof a !== "number" || typeof b !== "number" || !Number.isFinite(a) || !Number.isFinite(b)) throw new Error(`Chalkboard.numb.Kronecker: Parameters "a" and "b" must be finite numbers.`); if (a === b) return 1; else return 0; }; /** * Returns the least common multiple of two numbers. * @param {number} a - First number * @param {number} b - Second number * @returns {number} * @example * // Returns 12 * const lcm = Chalkboard.numb.lcm(4, 6); */ export const lcm = (a: number, b: number): number => { if (!Number.isInteger(a) || !Number.isInteger(b)) throw new Error(`Chalkboard.numb.lcm: Parameters "a" and "b" must be integers.`); if (a === 0 || b === 0) return 0; return Math.abs(a / Chalkboard.numb.gcd(a, b) * b); }; /** * Returns the proportional mapping of a number from one range to another. * @param {number} num - Number * @param {number[]} range1 - First range * @param {number[]} range2 - Second range * @returns {number} * @example * // Returns 0.92 * const map = Chalkboard.numb.map(23, [0, 25], [0, 1]); */ export const map = (num: number, range1: number[], range2: number[]): number => { if (!Array.isArray(range1) || !Array.isArray(range2)) throw new Error(`Chalkboard.numb.map: Parameters "range1" and "range2" must be arrays.`); if (range1.length !== 2 || range2.length !== 2) throw new Error(`Chalkboard.numb.map: Parameters "range1" and "range2" must be arrays of length 2.`); if (typeof num !== "number" || !Number.isFinite(num)) throw new Error(`Chalkboard.numb.map: Parameter "num" must be a finite number.`); if (typeof range1[0] !== "number" || typeof range1[1] !== "number" || !Number.isFinite(range1[0]) || !Number.isFinite(range1[1]) || range1[0] >= range1[1]) throw new Error(`Chalkboard.numb.map: Parameter "range1" must be an array of two finite numbers [min, max] with min < max.`); if (typeof range2[0] !== "number" || typeof range2[1] !== "number" || !Number.isFinite(range2[0]) || !Number.isFinite(range2[1]) || range2[0] > range2[1]) throw new Error(`Chalkboard.numb.map: Parameter "range2" must be an array of two finite numbers [min, max] with min <= max.`); return range2[0] + (range2[1] - range2[0]) * ((num - range1[0]) / (range1[1] - range1[0])); }; /** * Calculates the mathematically correct modulo of a mod b. * @param {number} a - Number * @param {number} b - Number * @returns {number} * @example * // Returns -1 (instead of 2, which is what 5 % -3 would return) * const mod = Chalkboard.numb.mod(5, -3); */ export const mod = (a: number, b: number): number => { if (typeof a !== "number" || typeof b !== "number" || !Number.isFinite(a) || !Number.isFinite(b)) throw new Error(`Chalkboard.numb.mod: Parameters "a" and "b" must be finite numbers.`); if (b === 0) throw new Error(`Chalkboard.numb.mod: Parameter "b" must be non-zero.`); return ((a % b) + b) % b; }; /** * Returns the product of a sequence. * @param {(n: number) => number} formula - Sequence written in product notation * @param {number} inf - Lower bound * @param {number} sup - Upper bound * @returns {number} * @example * // Returns 120 * const mul = Chalkboard.numb.mul((n) => n + 1, 0, 5); */ export const mul = (formula: (n: number) => number, inf: number, sup: number): number => { if (typeof formula !== "function") throw new Error(`Chalkboard.numb.mul: Parameter "formula" must be a function.`); if (!Number.isInteger(inf) || !Number.isInteger(sup)) throw new Error(`Chalkboard.numb.mul: Parameters "inf" and "sup" must be integers.`); if (inf > sup) throw new Error(`Chalkboard.numb.mul: Parameter "inf" must be less than or equal to "sup".`); let result = 1; for (let i = inf; i <= sup; i++) result *= formula(i); return result; }; /** * Returns the prime number that succeeds the inputted number. * @param {number} num - Number * @returns {number} * @example * // Returns 541 * const prime = Chalkboard.numb.nextPrime(523); */ export const nextPrime = (num: number): number => { if (!Number.isFinite(num)) throw new Error(`Chalkboard.numb.nextPrime: Parameter "num" must be finite.`); let result = Math.floor(num) + 1; if (result <= 2) return 2; if (result % 2 === 0) result++; while (!Chalkboard.numb.isPrime(result)) result += 2; return result; }; /** * Returns the combinatorial permutation of two numbers. * @param {number} n - First number (total items) * @param {number} r - Second number (chosen items) * @returns {number} * @example * // The number of different ways to arrange the word "MATH" is 24 * const permute = Chalkboard.numb.permutation(4, 4); */ export const permutation = (n: number, r: number): number => { if (!Number.isInteger(n) || !Number.isInteger(r) || n < 0 || r < 0 || r > n) throw new Error(`Chalkboard.numb.permutation: Parameters "n" and "r" must be integers with 0 <= r <= n.`); let result = 1; for (let i = n; i > n - r; i--) result *= i; return Math.round(result); }; /** * Returns a random number from a Poisson distribution. * @param {number} [l=1] - Rate parameter (lambda) of distribution * @returns {number} * @example * // Smaller argument means a less uniform distribution * const poissonRandom = Chalkboard.numb.Poissonian(0.5); */ export const Poissonian = (l: number = 1): number => { if (typeof l !== "number" || !Number.isFinite(l)) throw new Error(`Chalkboard.numb.Poissonian: Parameter "l" must be a finite number.`); if (l <= 0) throw new Error(`Chalkboard.numb.Poissonian: Parameter "l" must be positive.`); const L = Chalkboard.E(-l); let p = 1, k = 0; for (; p > L; ++k) p *= Math.random(); return k - 1; }; /** * Returns the nth prime number. * @param {number} num - The "n" in "nth prime number" (the index of the prime number in the set of all prime numbers) * @returns {number} * @example * // The 100th prime number is 523 * const prime = Chalkboard.numb.prime(100); */ export const prime = (num: number): number => { if (!Number.isInteger(num) || num < 1) throw new Error(`Chalkboard.numb.prime: Parameter "num" must be a positive integer.`); if (num === 1) return 2; let count = 1; let p = 3; while (true) { if (Chalkboard.numb.isPrime(p)) { count++; if (count === num) return p; } p += 2; } }; /** * Returns an array of prime numbers between the lower and upper bounds. * @param {number} inf - Lower bound * @param {number} sup - Upper bound * @returns {number[]} * @example * // Returns the array [2, 3, 5, ... , 983, 991, 997] * const arr = Chalkboard.numb.primeArr(0, 1000); */ export const primeArr = (inf: number, sup: number): number[] => { if (!Number.isInteger(inf) || !Number.isInteger(sup)) throw new Error(`Chalkboard.numb.primeArr: Parameters "inf" and "sup" must be integers.`); if (inf > sup) throw new Error(`Chalkboard.numb.primeArr: Parameter "inf" must be less than or equal to "sup".`); if (sup < 2) return []; const sieve = new Uint8Array(sup + 1); sieve[0] = 1; sieve[1] = 1; for (let p = 2; p * p <= sup; p++) if (sieve[p] === 0) for (let i = p * p; i <= sup; i += p) sieve[i] = 1; const result: number[] = []; const start = Math.max(2, inf); for (let i = start; i <= sup; i++) if (sieve[i] === 0) result.push(i); return result; }; /** * Returns the number of prime numbers between the lower and upper bounds. * @param {number} inf - Lower bound * @param {number} sup - Upper bound * @returns {number} * @example * // Returns 169 * const primes = Chalkboard.numb.primeCount(0, 1000); */ export const primeCount = (inf: number, sup: number): number => { if (!Number.isInteger(inf) || !Number.isInteger(sup)) throw new Error(`Chalkboard.numb.primeCount: Parameters "inf" and "sup" must be integers.`); if (inf > sup) throw new Error(`Chalkboard.numb.primeCount: Parameter "inf" must be less than or equal to "sup".`); return Chalkboard.numb.primeArr(inf, sup).length; }; /** * Returns the largest prime gap between the lower and upper bounds. * @param {number} inf - Lower bound * @param {number} sup - Upper bound * @returns {number} * @example * // Returns 8, the largest prime gap between 1 and 100 * const gap = Chalkboard.numb.primeGap(1, 100); */ export const primeGap = (inf: number, sup: number): number => { if (!Number.isInteger(inf) || !Number.isInteger(sup)) throw new Error(`Chalkboard.numb.primeGap: Parameters "inf" and "sup" must be integers.`); if (inf > sup) throw new Error(`Chalkboard.numb.primeGap: Parameter "inf" must be less than or equal to "sup".`); let prime: number | null = null; let gap = 0; for (let i = inf; i <= sup; i++) if (Chalkboard.numb.isPrime(i)) { if (prime !== null) { const temp = i - prime; if (temp > gap) gap = temp; } prime = i; } return gap; }; /** * Returns a random number from a uniform distribution. * @param {number} [inf=0] - Lower bound of distribution * @param {number} [sup=1] - Upper bound of distribution * @returns {number} * @example * // Random number between -1 and 1 * const random = Chalkboard.numb.random(-1, 1); */ export const random = (inf: number = 0, sup: number = 1): number => { if (typeof inf !== "number" || typeof sup !== "number" || !Number.isFinite(inf) || !Number.isFinite(sup)) throw new Error(`Chalkboard.numb.random: Parameters "inf" and "sup" must be finite numbers.`); if (inf > sup) throw new Error(`Chalkboard.numb.random: Parameter "inf" must be less than or equal to "sup".`); return inf + (sup - inf) * Math.random(); }; /** * Rounds a number to the nearest positional notation index (or the nearest place value). * @param {number} num - Number * @param {number} positionalIndex - The positional notation index (or place value) * @returns {number} * @example * // Returns 1240 * const rounded = Chalkboard.numb.roundTo(1237, 10); */ export const roundTo = (num: number, positionalIndex: number): number => { if (!Number.isFinite(num) || !Number.isFinite(positionalIndex)) throw new Error(`Chalkboard.numb.roundTo: Parameters must be finite numbers.`); if (positionalIndex === 0) throw new Error(`Chalkboard.numb.roundTo: Parameter "positionalIndex" must be non-zero.`); return Math.round(num / positionalIndex) * positionalIndex; }; /** * Returns the sign of a number. * @param {number} num - Number * @returns {-1 | 0 | 1 | undefined} * @example * const pos = Chalkboard.numb.sgn(19); // Returns 1 * const zero = Chalkboard.numb.sgn(0); // Returns 0 * const neg = Chalkboard.numb.sgn(-5); // Returns -1 */ export const sgn = (num: number): -1 | 0 | 1 | undefined => { if (Number.isNaN(num)) return undefined; if (!Number.isFinite(num)) throw new Error(`Chalkboard.numb.sgn: Parameter "num" must be a finite number.`); if (num > 0) return 1; else if (num < 0) return -1; else return 0; }; /** * Returns the summation of a sequence. * @param {(n: number) => number} formula - Sequence written in summation notation * @param {number} inf - Lower bound * @param {number} sup - Upper bound * @returns {number} * @example * // Returns almost π²/6 * const sum = Chalkboard.numb.sum((n) => 1 / (n * n), 0, 1000); */ export const sum = (formula: (n: number) => number, inf: number, sup: number): number => { if (typeof formula !== "function") throw new Error(`Chalkboard.numb.sum: Parameter "formula" must be a function.`); if (!Number.isInteger(inf) || !Number.isInteger(sup)) throw new Error(`Chalkboard.numb.sum: Parameters "inf" and "sup" must be integers.`); if (inf > sup) throw new Error(`Chalkboard.numb.sum: Parameter "inf" must be less than or equal to "sup".`); let result = 0; for (let i = inf; i <= sup; i++) result += formula(i); return result; }; /** * Converts a decimal number to binary representation (base 2). * @param {number} num - The decimal number * @param {boolean} [prefix=false] - Whether to include "0b" prefix (optional, defaults to false) * @returns {string} * @example * const bin1 = Chalkboard.numb.toBinary(10); // Returns "1010" * const bin2 = Chalkboard.numb.toBinary(10, true); // Returns "0b1010" */ export const toBinary = (num: number, prefix: boolean = false): string => { if (!Number.isInteger(num)) throw new Error(`Chalkboard.numb.toBinary: Parameter "num" must be an integer.`); const sign = num < 0 ? "-" : ""; const digits = Math.abs(num).toString(2); return sign + (prefix ? "0b" : "") + digits; }; /** * Converts a number from a specified base to decimal (base 10). * @param {string} num - The string representation of the number in the specified base * @param {number} base - The base (2-36) of the number * @returns {number} * @example * const dec1 = Chalkboard.numb.toDecimal("1010", 2); // Returns 10 * const dec2 = Chalkboard.numb.toDecimal("1a", 16); // Returns 26 * const dec3 = Chalkboard.numb.toDecimal("0x2a", 16); // Returns 42 */ export const toDecimal = (num: string, base: number): number => { if (typeof num !== "string") throw new Error(`Chalkboard.numb.toDecimal: Parameter "num" must be a string.`); if (!Number.isInteger(base) || base < 2 || base > 36) throw new Error(`Chalkboard.numb.toDecimal: Parameter "base" must be an integer between 2 and 36.`); num = num.toLowerCase().trim(); const isNegative = num.startsWith("-"); if (isNegative) num = num.substring(1); if (base === 2 && num.startsWith("0b")) num = num.substring(2); if (base === 8 && num.startsWith("0o")) num = num.substring(2); if (base === 16 && num.startsWith("0x")) num = num.substring(2); if (num.length === 0) throw new Error(`Chalkboard.numb.toDecimal: Parameter "num" must contain digits.`); const chars = "0123456789abcdefghijklmnopqrstuvwxyz".substring(0, base); for (const char of num) if (!chars.includes(char)) throw new Error(`Chalkboard.numb.toDecimal: Invalid character "${char}" for base ${base}.`); const result = parseInt(num, base); if (!Number.isFinite(result)) throw new Error(`Chalkboard.numb.toDecimal: Failed to parse "num".`); return isNegative ? -result : result; }; /** * Converts a decimal to a fraction which is represented as an array of its numerator and denominator. * @param {number} num - The decimal number * @param {number} [tolerance = 1e-8] - The tolerance of the approximation algorithm (optional, defaults to 1e-8) * @returns {[number, number]} * @example * // Returns [-5, 4] * const fraction = Chalkboard.numb.toFraction(-1.25); */ export const toFraction = (num: number, tolerance: number = 1e-8): [number, number] => { if (typeof num !== "number" || typeof tolerance !== "number") throw new Error(`Chalkboard.numb.toFraction: Parameters "num" and "tolerance" must be numbers.`); if (!Number.isFinite(num)) throw new Error(`Chalkboard.numb.toFraction: The parameter "num" must be finite to be converted to a fraction.`); if (!Number.isFinite(tolerance) || tolerance <= 0) throw new Error(`Chalkboard.numb.toFraction: The parameter "tolerance" must be a positive finite number.`); const sign = Chalkboard.numb.sgn(num); if (sign === undefined) throw new Error(`Chalkboard.numb.toFraction: The parameter "num" must be a valid number to be converted to a fraction.`); const x = Math.abs(num); if (Number.isInteger(x)) return [sign * x, 1]; let h1 = 1, h2 = 0, k1 = 0, k2 = 1; let b = x; const MAX_ITER = 10000; for (let iter = 0; iter < MAX_ITER; iter++) { const a = Math.floor(b); const h = a * h1 + h2; const k = a * k1 + k2; if (k === 0) break; const approx = h / k; if (Math.abs(x - approx) < tolerance) { const g = Chalkboard.numb.gcd(h, k); return [sign * (h / g), k / g]; } h2 = h1; h1 = h; k2 = k1; k1 = k; const frac = b - a; if (Math.abs(frac) <= Number.EPSILON) { const g = Chalkboard.numb.gcd(h, k); return [sign * (h / g), k / g]; } b = 1 / frac; if (!Number.isFinite(b)) { const g = Chalkboard.numb.gcd(h, k); return [sign * (h / g), k / g]; } } throw new Error(`Chalkboard.numb.toFraction: Failed to converge to a fraction within the iteration limit.`); }; /** * Converts a decimal number to hexadecimal representation (base 16). * @param {number} num - The decimal number * @param {boolean} [prefix=false] - Whether to include "0x" prefix (optional, defaults to false) * @param {boolean} [uppercase=false] - Whether to use uppercase letters (optional, defaults to false) * @returns {string} * @example * const hex1 = Chalkboard.numb.toHexadecimal(26); // Returns "1a" * const hex2 = Chalkboard.numb.toHexadecimal(26, true, true); // Returns "0x1A" */ export const toHexadecimal = (num: number, prefix: boolean = false, uppercase: boolean = false): string => { if (!Number.isInteger(num)) throw new Error(`Chalkboard.numb.toHexadecimal: The parameter "num" must be an integer.`); const sign = num < 0 ? "-" : ""; let digits = Math.abs(num).toString(16); if (uppercase) digits = digits.toUpperCase(); return sign + (prefix ? "0x" : "") + digits; }; /** * Converts a decimal number to octal representation (base 8). * @param {number} num - The decimal number * @param {boolean} [prefix=false] - Whether to include "0o" prefix (optional, defaults to false) * @returns {string} * @example * const oct1 = Chalkboard.numb.toOctal(10); // Returns "12" * const oct2 = Chalkboard.numb.toOctal(10, true); // Returns "0o12" */ export const toOctal = (num: number, prefix: boolean = false): string => { if (!Number.isInteger(num)) throw new Error(`Chalkboard.numb.toOctal: The parameter "num" must be an integer.`); const sign = num < 0 ? "-" : ""; const digits = Math.abs(num).toString(8); return sign + (prefix ? "0o" : "") + digits; }; } }