diff --git a/game.js b/game.js index 25e7c3a..a0beeae 100644 --- a/game.js +++ b/game.js @@ -685,6 +685,9 @@ function fmt(n) { if (n < 0) return '-' + fmt(-n); if (n < 1000) return Math.floor(n).toLocaleString(); const scale = Math.floor(Math.log10(n) / 3); + // At undecillion+ (scale >= 12, i.e. 10^36), switch to spelled-out words + // This helps players grasp cosmic scale when digits become meaningless + if (scale >= 12) return spellf(n); if (scale >= NUMBER_ABBREVS.length) return n.toExponential(2); const abbrev = NUMBER_ABBREVS[scale]; return (n / Math.pow(10, scale * 3)).toFixed(1) + abbrev; @@ -722,7 +725,41 @@ function spellf(n) { // For very large numbers beyond our lookup table, fall back if (n >= 1e306) return n.toExponential(2) + ' (beyond centillion)'; - // Break number into groups of three digits from the top + // Use string-based chunking for numbers >= 1e54 to avoid floating point drift + // Math.log10 / Math.pow lose precision beyond ~54 bits + if (n >= 1e54) { + // Convert to scientific notation string, extract digits + const sci = n.toExponential(); // "1.23456789e+60" + const [coeff, expStr] = sci.split('e+'); + const exp = parseInt(expStr); + // Rebuild as integer string with leading digits from coefficient + const coeffDigits = coeff.replace('.', ''); // "123456789" + const totalDigits = exp + 1; + // Pad with zeros to reach totalDigits, then take our coefficient digits + let intStr = coeffDigits; + const zerosNeeded = totalDigits - coeffDigits.length; + if (zerosNeeded > 0) intStr += '0'.repeat(zerosNeeded); + + // Split into groups of 3 from the right + const groups = []; + for (let i = intStr.length; i > 0; i -= 3) { + groups.unshift(parseInt(intStr.slice(Math.max(0, i - 3), i))); + } + + const parts = []; + const numGroups = groups.length; + for (let i = 0; i < numGroups; i++) { + const chunk = groups[i]; + if (chunk === 0) continue; + const scaleIdx = numGroups - 1 - i; + const scaleName = scaleIdx < NUMBER_NAMES.length ? NUMBER_NAMES[scaleIdx] : ''; + parts.push(spellSmall(chunk) + (scaleName ? ' ' + scaleName : '')); + } + + return parts.join(' ') || 'zero'; + } + + // Standard math-based chunking for numbers < 1e54 const scale = Math.min(Math.floor(Math.log10(n) / 3), NUMBER_NAMES.length - 1); const parts = []; @@ -734,7 +771,7 @@ function spellf(n) { if (chunk > 0 && chunk < 1000) { parts.push(spellSmall(chunk) + (NUMBER_NAMES[s] ? ' ' + NUMBER_NAMES[s] : '')); } else if (chunk >= 1000) { - // Floating point chunk too large — simplify + // Floating point chunk too large — shouldn't happen below 1e54 parts.push(spellSmall(Math.floor(chunk % 1000)) + (NUMBER_NAMES[s] ? ' ' + NUMBER_NAMES[s] : '')); } }