import type { NumberFormat } from "@elysium/types"; // Named suffixes up to 1e33 (Decillion). Letter-based suffixes take over from 1e36 onwards. const NAMED_SUFFIXES: { threshold: number; suffix: string }[] = [ { threshold: 1e33, suffix: "Dc" }, // Decillion { threshold: 1e30, suffix: "No" }, // Nonillion { threshold: 1e27, suffix: "Oc" }, // Octillion { threshold: 1e24, suffix: "Sp" }, // Septillion { threshold: 1e21, suffix: "Sx" }, // Sextillion { threshold: 1e18, suffix: "Qi" }, // Quintillion { threshold: 1e15, suffix: "Qa" }, // Quadrillion { threshold: 1e12, suffix: "T" }, // Trillion { threshold: 1e9, suffix: "B" }, // Billion { threshold: 1e6, suffix: "M" }, // Million { threshold: 1e3, suffix: "K" }, // Thousand ]; // Letter suffixes start at 1e36 ("a"), stepping by 1000 each time (i.e. +3 exponent per letter). const LETTER_BASE_EXP = 36; /** * Generates an alphabetic suffix for a given index: * 0 → "a", 1 → "b", ..., 25 → "z", * 26 → "aa", 27 → "ab", ..., 701 → "zz", 702 → "aaa", ... */ const getLetterSuffix = (index: number): string => { let result = ""; let n = index; do { result = String.fromCharCode(97 + (n % 26)) + result; n = Math.floor(n / 26) - 1; } while (n >= 0); return result; }; const formatSuffix = (value: number): string => { if (value >= Math.pow(10, LETTER_BASE_EXP)) { const exp = Math.floor(Math.log10(value)); const stepsAboveBase = Math.floor((exp - LETTER_BASE_EXP) / 3); const divisorExp = LETTER_BASE_EXP + stepsAboveBase * 3; const divisor = Math.pow(10, divisorExp); return `${(value / divisor).toFixed(2)}${getLetterSuffix(stepsAboveBase)}`; } for (const { threshold, suffix } of NAMED_SUFFIXES) { if (value >= threshold) { return `${(value / threshold).toFixed(2)}${suffix}`; } } return value.toFixed(1); }; /** * Formats a number in scientific notation: e.g. 1.23e15. * Falls back to K/M/B/T style below 1 million. */ const formatScientific = (value: number): string => { if (value < 1e6) return formatSuffix(value); // toExponential handles all magnitudes JS can represent (up to ~1.8e308) return value.toExponential(2).replace("e+", "e"); }; /** * Formats a number in engineering notation (exponent always a multiple of 3): * e.g. 12.35E12, 1.23E300. Falls back to K/M/B/T style below 1 million. */ const formatEngineering = (value: number): string => { if (value < 1e6) return formatSuffix(value); const exp = Math.floor(Math.log10(value)); const engExp = Math.floor(exp / 3) * 3; const mantissa = value / Math.pow(10, engExp); return `${mantissa.toFixed(2)}E${engExp}`; }; /** * Formats a number for display using the player's chosen notation style. * Negative values are formatted with a leading minus sign. */ export const formatNumber = (value: number, format: NumberFormat = "suffix"): string => { if (!isFinite(value) || isNaN(value)) return "0"; if (value < 0) return `-${formatNumber(-value, format)}`; switch (format) { case "scientific": return formatScientific(value); case "engineering": return formatEngineering(value); default: return formatSuffix(value); } };