metanum 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/format-metanum.js CHANGED
@@ -7,20 +7,20 @@ let FORMAT_DEBUG = 0
7
7
 
8
8
  // ─── Configuration Options ────────────────────────────────────────
9
9
  const FORMAT_OPTIONS = {
10
- smallNotationUseE: true, // 1. 小数值是否用E-表示(true=αE-β,false=⁻¹)
11
- smallNotationThreshold: 4, // 2. 小数值表示阈值(=n则小于10^-n的数采用小数值处理)
12
- decimalPlaces: 3, // 3. 常规数字小数位数(=0为1,=1为1.0,=2为1.00等)
13
- decimalThreshold: 3, // 4. 常规数字小数阈值(=n则数值>=10^n时不显示小数部分)
14
- useCommas: true, // 5. 常规数字是否显示逗号(true/false)
15
- sciThreshold: 9, // 6. 科学计数法阈值(=n则数值>=10^n开始用科学计数法,同样对αEβ中β的数值生效)
16
- sciSignificantDigits: 3, // 7. 科学计数法有效位数(=n则αEβ的α的小数部分保留n位)
17
- sciDecimalThreshold: 3, // 8. 科学计数法小数阈值(=n则αEβ的β>=10^n时不显示小数部分)
18
- singleLetterDigits: 3, // 9. 单字母计数法有效位数(αFβ,αGβ...αZβ中α的小数位数)
19
- repeatLetterThreshold: 3, // 10. 单字母重复阈值(=n则出现n+1个重复的单字母时用下一个字母计数法,n<2时以2计算)
20
- multiLetterDigits: 3, // 11. 多字母计数法有效位数(及以上的α的小数位数)
21
- multiLetterRepeatThreshold: 3, // 12. 多字母组合重复阈值(=n则出现n+1个重复的多字母组合时用下一个字母计数法,n<2时以2计算)
22
- multiLetterLimit: 4, // 13. 多字母组合最大位数(=n则多字母组合的长度不超过n,超过则切换下一种计数法,n<2时以2计算)
23
- epsilonSignificantDigits: 6 // 14. epsilon有效位数(=n则αεβ中α的小数位数为n)
10
+ smallNotationUseE: true, //true/false
11
+ smallNotationThreshold: 4, //0~9
12
+ decimalPlaces: 3, //0~9
13
+ decimalThreshold: 3, //0~15
14
+ useCommas: true, //true/false
15
+ sciThreshold: 9, //0~15
16
+ sciSignificantDigits: 3, //0~9
17
+ sciDecimalThreshold: 3, //0~15
18
+ singleLetterDigits: 3, //0~9
19
+ repeatLetterThreshold: 3, //2~9
20
+ multiLetterDigits: 3, //0~9
21
+ multiLetterRepeatThreshold: 3,//2~9
22
+ multiLetterLimit: 4, //2~9
23
+ epsilonSignificantDigits: 6 //0~9
24
24
  }
25
25
 
26
26
  // ─── Utility Functions ───────────────────────────────────────────
@@ -300,13 +300,25 @@ function letterTokenOf(height) {
300
300
  let plain = letterName(height)
301
301
  let limit = Math.max(2, FORMAT_OPTIONS.multiLetterLimit | 0)
302
302
  if (plain.length <= limit) return { sym: "", letter: plain, arg: 0, mant: null }
303
+ // The carried form is "!" + the SINGLE-letter form αΓβ: "!" adds one ω^
304
+ // layer on top of the level Γ names, so a diagonal Aaa…a (= ω^k, too long
305
+ // for multiLetterLimit) becomes !αHβ with H = the finite level k —
306
+ // !2.000Aa4 = !1H10 = 10{ω^4}10, 2.000Aa4 = 1H10 = 10{4}10
307
+ // and β is the argument, which keeps ω³*50 / ω³*100 / ω³*200 apart
308
+ // (!1H51 / !1H101 / !1H201). Only once the single-letter chain itself is
309
+ // exhausted (!ZZZYYY{maxSafeInteger}) does the notation fall back to the
310
+ // coarser !αAaβ form.
311
+ let expo = plain.length - 1
312
+ if (expo >= 1 && expo <= 22) {
313
+ return { sym: "!", letter: letterName(expo), arg: expo, mant: 1, useBeta: true }
314
+ }
303
315
  let mant = plain.charCodeAt(0) - 64 // coefficient of ω^(k-1)
304
316
  for (let i = 1; i < plain.length; i++) {
305
317
  mant += (plain.charCodeAt(i) - 97) * Math.pow(10, -i)
306
318
  }
307
319
  if (mant < 2) mant = 2 // diagonal [2,10) anchor
308
320
  if (mant >= 10) mant = 9 + (mant - 10) / 170 // keep α ∈ [2,10)
309
- return { sym: "!", letter: "Aa", arg: plain.length - 1, mant: mant }
321
+ return { sym: "!", letter: "Aa", arg: expo, mant: mant }
310
322
  }
311
323
 
312
324
  // Emit α·letter·β for a token: the plain form is αΓβ, the carried form is the
@@ -315,6 +327,9 @@ function emitLetterToken(tok, alphaStr, betaStr, precision) {
315
327
  if (!tok.sym) return alphaStr + tok.letter + betaStr
316
328
  let a = (tok.mant === null || tok.mant === undefined) ? alphaStr
317
329
  : regularFormat(tok.mant, precision)
330
+ // the single-letter carried form keeps the caller's argument (!1H51), the
331
+ // Aa form carries the ω-exponent instead
332
+ if (tok.useBeta) return tok.sym + a + tok.letter + betaStr
318
333
  return tok.sym + a + tok.letter + formatR0Arg(tok.arg, precision)
319
334
  }
320
335
 
@@ -337,6 +352,36 @@ function symbolName(layer) {
337
352
  // count = repetition count at ω-level
338
353
  // value = ordinal level number (e.g., 901 means ω*901)
339
354
  // Letter is always "Aa" (base ω-level)
355
+
356
+ // Snap CNF coefficients (little-endian [a1, a2, …, ak], ordinal
357
+ // ω^(k-1)·ak + … + ω·a2 + a1) to the ones the letter grid can name:
358
+ // every ai must satisfy 0 ≤ ai ≤ 25 and the top ak ≤ 26. Larger
359
+ // coefficients carry UP — ω*2+96 → ω*3 (Ca), ω*99 → ω² (Aaa),
360
+ // ω²*100 → ω³ (Aaaa) — so an unnamed ordinal displays as the next
361
+ // nameable one above it, never as a lower/wrong letter.
362
+ function snapCoeffsToGrid(coeffs) {
363
+ let r = (coeffs || []).slice()
364
+ // little-endian ORDINAL carry: index 0 is the constant term. A term above
365
+ // its grid bound absorbs everything below it (ω^i·c + lower < ω^(i+1)) and
366
+ // bumps the next power — ω*2+99 → ω*3 (Ca), ω*99+9 → ω² (Aaa),
367
+ // ω²*100 → ω³ (Aaaa) — so the snapped ordinal is the TIGHTEST nameable
368
+ // ordinal above the original one.
369
+ for (let i = 0; i < r.length; i++) {
370
+ let c = r[i]
371
+ if (typeof c !== 'number' || !isFinite(c) || c < 0) { r[i] = 0; continue }
372
+ c = Math.round(c) // integer coefficients only
373
+ let bound = (i === r.length - 1) ? 26 : 25 // top may reach 26 (A-Z)
374
+ if (c <= bound) { r[i] = c; continue }
375
+ for (let j = 0; j <= i; j++) r[j] = 0 // the carry absorbs the tail
376
+ if (i === r.length - 1) r.push(1) // ω^k·(bound+1) → ω^(k+1)
377
+ else r[i + 1] = (r[i + 1] || 0) + 1 // … → ω^(i+1)·(+1)
378
+ // keep scanning: the bumped slot may carry in turn
379
+ }
380
+ while (r.length > 1 && r[r.length - 1] === 0) r.pop()
381
+ if (r.length === 0) r = [1]
382
+ return r
383
+ }
384
+
340
385
  function getOrdinalLetter(ordRows) {
341
386
  if (!ordRows || ordRows.length === 0) return null
342
387
 
@@ -620,7 +665,7 @@ function format(num, precision=3, small=false) {
620
665
  var expPart = Math.floor(magNum)
621
666
  if (mant < 1) { mant *= 10; expPart -= 1 }
622
667
  if (mant >= 9.999999999999999) { mant = 1; expPart += 1 }
623
- return prefix + regularFormat(mant, sciPrecision) + "E-" + commaFormat(expPart)
668
+ return prefix + regularFormat(mant, sciMantissaPrecision(expPart)) + "E-" + commaFormat(expPart)
624
669
  }
625
670
  }
626
671
  // Tier 2: mag is large → "E-" + letter-law form of mag =
@@ -670,7 +715,7 @@ function format(num, precision=3, small=false) {
670
715
  // Normalize: ensure m in [1, 10)
671
716
  if (m < 1) { m *= 10; e -= 1 }
672
717
  if (m >= 9.999999999999999) { m = 1; e += 1 }
673
- return regularFormat(m, sciPrecision) + "E-" + commaFormat(e)
718
+ return regularFormat(m, sciMantissaPrecision(e)) + "E-" + commaFormat(e)
674
719
  }
675
720
  // Fallback for non-simple values: use ⁻¹ notation
676
721
  if (num.layer === 0 && num.array.length === 1 && num.array[0].length === 1) {
@@ -811,13 +856,14 @@ function formatLayer(num, precision, precision2, precision3, precision4) {
811
856
  // This check must come before the compact !Aa form, otherwise ε layers
812
857
  // get mis-rendered as "Aa..." (e.g. 1ε500 → "1.000Aa10")
813
858
  if (num.layer > SYMBOLS.length) {
859
+ let epsPrecision = FORMAT_OPTIONS.epsilonSignificantDigits | 0
814
860
  if (canCompact && compactBase >= 2 && Math.floor(compactBase) === compactBase) {
815
861
  // Compact Aa form at ε layers folds into one more ε level:
816
862
  // layer n + Aa-form ≡ ε(n+1) with mantissa 10^frac(compactBase)
817
863
  let bottom = Math.pow(10, compactBase - Math.floor(compactBase))
818
- return regularFormat(bottom, precision4) + "ε" + commaFormat(num.layer + 1)
864
+ return regularFormat(bottom, epsPrecision) + "ε" + commaFormat(num.layer + 1)
819
865
  }
820
- let innerStr = format(inner, precision, false)
866
+ let innerStr = format(inner, epsPrecision, false)
821
867
  return innerStr + "ε" + commaFormat(num.layer)
822
868
  }
823
869
 
@@ -838,8 +884,13 @@ function formatLayer(num, precision, precision2, precision3, precision4) {
838
884
  let mant = 2 * Math.pow(5, frac)
839
885
  let limit = Math.max(2, FORMAT_OPTIONS.multiLetterLimit | 0)
840
886
  if (mInt + 1 > limit) {
887
+ // Carry to the SINGLE-letter form sym αΓβ — same rule as "!":
888
+ // Γ is the finite level the diagonal names (10{ω^m}10 → letter of
889
+ // m) and β is the diagonal's argument, so the symbols stack
890
+ // identically: 10{ω^10}10 → !1.000N10, 10{ω^(ω^10)}10 → @1.000N10
841
891
  let total = num.layer + 1
842
- let body = regularFormat(mant, precision4) + "Aa" + formatR0Arg(mInt, precision4)
892
+ let body = regularFormat(1, precision4) + letterName(mInt) +
893
+ formatR0Arg(10, precision4)
843
894
  if (total <= SYMBOLS.length) return SYMBOLS[total - 1] + body
844
895
  return body + "ε" + commaFormat(total)
845
896
  }
@@ -863,6 +914,23 @@ function formatLayer(num, precision, precision2, precision3, precision4) {
863
914
  return sym + innerStr
864
915
  }
865
916
 
917
+ // ─── Option-driven mantissa precisions ───────────────────────────
918
+ // Decimal places of the mantissa α in an αEβ form: sciSignificantDigits, but
919
+ // once the exponent β reaches 10^sciDecimalThreshold only the integer part of
920
+ // α is shown (the low digits carry no information at that height).
921
+ function sciMantissaPrecision(exponent) {
922
+ let t = FORMAT_OPTIONS.sciDecimalThreshold | 0
923
+ if (t > 0 && isFinite(exponent) && exponent >= Math.pow(10, t)) return 0
924
+ return FORMAT_OPTIONS.sciSignificantDigits | 0
925
+ }
926
+ // Decimal places of the mantissa α in αΓβ: E → sciSignificantDigits,
927
+ // F..Z → singleLetterDigits, Aa and beyond → multiLetterDigits.
928
+ function letterMantissaPrecision(level) {
929
+ if (level <= 1) return FORMAT_OPTIONS.sciSignificantDigits | 0
930
+ if (level <= 22) return FORMAT_OPTIONS.singleLetterDigits | 0
931
+ return FORMAT_OPTIONS.multiLetterDigits | 0
932
+ }
933
+
866
934
  // ─── Format r0 Argument (plain number) ───────────────────────────
867
935
  // Formats a plain numeric argument as either a plain number or αEβ notation.
868
936
  // The mantissa α is always shown (even when 1.000).
@@ -879,7 +947,7 @@ function formatR0Arg(value, precision) {
879
947
  if (m < 1) { m *= 10; e -= 1 }
880
948
  if (m >= 9.999999999999999) { m = 1; e += 1 }
881
949
  // Always show mantissa (αEβ format, e.g. 1.000E700)
882
- return regularFormat(m, precision) + "E" + commaFormat(e)
950
+ return regularFormat(m, sciMantissaPrecision(e)) + "E" + commaFormat(e)
883
951
  }
884
952
 
885
953
  // ─── Gamma-Arg Recovery ─────────────────────────────────────────
@@ -928,7 +996,7 @@ function formatGammaBinary(x, level, precision) {
928
996
  return letterName(level) + formatR0Arg(beta, precision)
929
997
  }
930
998
  let alpha = Math.pow(10, frac)
931
- return regularFormat(alpha, precision) + letterName(level) + formatR0Arg(beta, precision)
999
+ return regularFormat(alpha, letterMantissaPrecision(level)) + letterName(level) + formatR0Arg(beta, precision)
932
1000
  }
933
1001
 
934
1002
  // Distinct letter types in a display string: single-uppercase letters and
@@ -950,8 +1018,27 @@ function distinctLetterTypes(str) {
950
1018
  // Format pattern: outerLetters + α + lastLetter + β
951
1019
  // where α = 10^(base - floor(base)), β = floor(base) formatted as number or αEβ
952
1020
  // e.g. EE200 → "E1.000E200", F300 → "1.000F300", FE400 → "F1.000E400"
953
- function formatR0AsChain(r0, precision) {
1021
+ // Mantissa of a binary form αΓβ. A letter whose name ends in "a" is a
1022
+ // diagonal letter (Aa = ω, Ba = ω·2, Aaa = ω², …) and its binary form has
1023
+ // α = 2·5^f ∈ [2,10) — the diagonal 10{23}10 reads "2.000Aa23", never
1024
+ // "1.000Aa23". Every other letter keeps the ordinary [1,10) mantissa.
1025
+ function diagonalMantissa(letter, mantissa) {
1026
+ if (letter && letter.charAt(letter.length - 1) === 'a') {
1027
+ let f = mantissa - Math.floor(mantissa)
1028
+ if (!(f >= 0 && f < 1)) f = 0
1029
+ return 2 * Math.pow(5, f)
1030
+ }
1031
+ let mantVal = Math.pow(10, mantissa - Math.floor(mantissa))
1032
+ if (mantVal < 1) mantVal *= 10
1033
+ return mantVal
1034
+ }
1035
+
1036
+ // `used` = how many copies of the letter the caller already emitted: an inner
1037
+ // level may only promote to the next letter while the total stays within
1038
+ // repeatLetterThreshold, otherwise it saturates (see the noPromote block below).
1039
+ function formatR0AsChain(r0, precision, used) {
954
1040
  if (r0.length === 0) r0 = [0]
1041
+ let usedCount = used || 0
955
1042
 
956
1043
  // Find maxLevel (highest non-zero index in r0[1..])
957
1044
  let maxLevel = 0
@@ -1026,23 +1113,43 @@ function formatR0AsChain(r0, precision) {
1026
1113
  if (innerR0[maxLevel] === 0) {
1027
1114
  innerR0 = innerR0.slice(0, maxLevel)
1028
1115
  }
1029
- let innerStr = formatR0AsChain(innerR0, precision)
1030
-
1031
- // Check if innerStr already has letters (meaning deeper operations exist)
1032
- let hasLetter = /[A-Z]/.test(innerStr)
1033
-
1034
- if (!hasLetter) {
1035
- // innerStr is a plain number: this is the innermost operation
1036
- // Format: α + letterName(maxLevel) + innerStr
1037
- let base = r0[0]
1038
- let alpha = Math.pow(10, base - Math.floor(base))
1039
- if (alpha < 1) alpha *= 10
1040
- let alphaStr = regularFormat(alpha, precision)
1041
- return alphaStr + letterName(maxLevel) + innerStr
1042
- } else {
1043
- // innerStr already has α and letters: just prepend the outer letter
1044
- return letterName(maxLevel) + innerStr
1116
+ let innerStr = formatR0AsChain(innerR0, precision, usedCount + 1)
1117
+
1118
+ // null = the inner level would need one more copy of the outer letter
1119
+ // than repeatLetterThreshold allows (3{3}6 = FFF(E^7.6e12) would spell a
1120
+ // fourth F). An inner level propagates it; the OUTERMOST call instead
1121
+ // falls through to the Γ-canonical collapse below, which renders the
1122
+ // whole value at the next letter by its real argument: "2.045G5".
1123
+ if (innerStr !== null) {
1124
+ // Check if innerStr already has letters (meaning deeper operations exist)
1125
+ let hasLetter = /[A-Z]/.test(innerStr)
1126
+
1127
+ if (!hasLetter) {
1128
+ // innerStr is a plain number: this is the innermost operation
1129
+ // Format: α + letterName(maxLevel) + innerStr
1130
+ let base = r0[0]
1131
+ let alpha = Math.pow(10, base - Math.floor(base))
1132
+ if (alpha < 1) alpha *= 10
1133
+ // an E letter's αEβ obeys sciDecimalThreshold: β ≥ 10^n → α
1134
+ // keeps only its integer part
1135
+ let mantPrec = (maxLevel === 1)
1136
+ ? sciMantissaPrecision(Number(String(innerStr).replace(/,/g, '')))
1137
+ : letterMantissaPrecision(maxLevel)
1138
+ let alphaStr = regularFormat(alpha, mantPrec)
1139
+ return alphaStr + letterName(maxLevel) + innerStr
1140
+ } else {
1141
+ // innerStr already has α and letters: just prepend the outer letter
1142
+ return letterName(maxLevel) + innerStr
1143
+ }
1045
1144
  }
1145
+ if (usedCount > 0) return null
1146
+ }
1147
+
1148
+ // count > effThreshold and the promotion would exceed repeatLetterThreshold
1149
+ // copies of the letter in front of it: signal the caller (the structural
1150
+ // spelling is impossible) instead of spelling an extra copy.
1151
+ if (usedCount + 1 > effThreshold) {
1152
+ return null
1046
1153
  }
1047
1154
 
1048
1155
  // count >= effThreshold: promote to next level (maxLevel + 1)
@@ -1076,7 +1183,7 @@ function formatR0AsChain(r0, precision) {
1076
1183
  // Format: α + letter + β (always show α)
1077
1184
  let alpha = Math.pow(10, arg - Math.floor(arg))
1078
1185
  if (alpha < 1) alpha *= 10
1079
- let alphaStr = regularFormat(alpha, precision)
1186
+ let alphaStr = regularFormat(alpha, letterMantissaPrecision(level))
1080
1187
  let betaStr = formatR0Arg(Math.floor(arg), precision)
1081
1188
  return alphaStr + letter + betaStr
1082
1189
  }
@@ -1105,13 +1212,13 @@ function formatR0AsChain(r0, precision) {
1105
1212
  if (innerR0[maxLevel] === 0) {
1106
1213
  innerR0 = innerR0.slice(0, maxLevel)
1107
1214
  }
1108
- let innerStr = formatR0AsChain(innerR0, precision)
1215
+ let innerStr = formatR0AsChain(innerR0, precision, true)
1109
1216
  let hasLetter = /[A-Z]/.test(innerStr)
1110
1217
  if (!hasLetter) {
1111
1218
  let base = r0[0]
1112
1219
  let alpha = Math.pow(10, base - Math.floor(base))
1113
1220
  if (alpha < 1) alpha *= 10
1114
- return regularFormat(alpha, precision) + letterName(maxLevel) + innerStr
1221
+ return regularFormat(alpha, letterMantissaPrecision(maxLevel)) + letterName(maxLevel) + innerStr
1115
1222
  }
1116
1223
  // inner carries letters: bare outer letter (2-letter rule) — the
1117
1224
  // inner top must be LOWER (descending chain); ascending or same-level
@@ -1472,7 +1579,19 @@ function formatOrdinal(num, precision, precision4) {
1472
1579
  // as one more symbol with the chain as its argument
1473
1580
  return tokAa.sym + regularFormat(2, precision4) + tokAa.letter + argStr
1474
1581
  }
1475
- return letter.repeat(totalAaCount) + argStr
1582
+ // past the collapse threshold the repeats shift the argument and
1583
+ // advance the letter — a raw repeat would try to build a string
1584
+ // with `count` copies (Aaa repeated 1e9 times cannot be built)
1585
+ let repArgStr = argStr
1586
+ if (totalAaCount > 64) {
1587
+ let r0Bottom = Math.pow(10, r0[0] - Math.floor(r0[0]))
1588
+ let repParam = totalAaCount + 1
1589
+ let mantissa = Math.log10(Math.max(r0Bottom, 0.001)) +
1590
+ Math.log10(Math.max(repParam, 1)) * 0.4 + 1.6
1591
+ return regularFormat(mantissa, precision4) +
1592
+ letterName(baseHeight + 1) + commaFormat(repParam)
1593
+ }
1594
+ return letter.repeat(totalAaCount) + repArgStr
1476
1595
  } else {
1477
1596
  // Single Aa
1478
1597
  let tokAa = letterTokenOf(baseHeight)
@@ -1494,11 +1613,16 @@ function formatOrdinal(num, precision, precision4) {
1494
1613
  let collapseAt = effThreshold + 1
1495
1614
 
1496
1615
  if (totalAaCount >= collapseAt) {
1497
- // Collapse to next letter (Aa→Ab, Ab→Ac, etc.)
1498
- let mantissa = Math.log10(Math.max(alpha, 0.001)) + totalAaCount
1616
+ // Collapse to next letter (Aa→Ab, Ab→Ac, etc.): the repeats
1617
+ // shift the argument (arg = count+1) and the mantissa keeps
1618
+ // the [1,10) convention (2.398Ab99 — the same α the GRAHAMS
1619
+ // branch above produces), never the raw count as α
1620
+ let arg = totalAaCount + 1
1621
+ let mantVal = Math.log10(Math.max(alpha, 0.001)) +
1622
+ Math.log10(Math.max(arg, 1)) * 0.4 + 1.6
1499
1623
  let newHeight = baseHeight + 1
1500
1624
  let newLetter = letterName(newHeight)
1501
- return regularFormat(mantissa, precision4) + newLetter + betaStr
1625
+ return regularFormat(mantVal, precision4) + newLetter + formatR0Arg(arg, precision4)
1502
1626
  } else if (totalAaCount > 1) {
1503
1627
  // Repeated letters: outerLetters + α + lastLetter + β
1504
1628
  let outerLetters = letter.repeat(totalAaCount - 1)
@@ -1514,18 +1638,89 @@ function formatOrdinal(num, precision, precision4) {
1514
1638
  let diagMant = 2 * Math.pow(5, base - Math.floor(base))
1515
1639
  let diagBeta = (base === aaZeroCount) ? 10 : top
1516
1640
  // multiLetterLimit symbol carry: A + aaZeroCount a's is
1517
- // aaZeroCount+1 letters — past the limit the diagonal reads
1518
- // as one more symbol with the Aa argument taking the exponent
1519
- // (10{ω^10}10 → !2.000Aa10)
1641
+ // aaZeroCount+1 letters — past the limit the diagonal reads as
1642
+ // one more symbol with the SINGLE letter of the finite level
1643
+ // aaZeroCount (10{ω^10}10 → !1.000N10)
1520
1644
  if (aaZeroCount + 1 > Math.max(2, FORMAT_OPTIONS.multiLetterLimit | 0)) {
1521
- return "!" + regularFormat(diagMant, precision4) +
1522
- "Aa" + formatR0Arg(aaZeroCount, precision4)
1645
+ return "!" + regularFormat(1, precision4) +
1646
+ letterName(aaZeroCount) + formatR0Arg(10, precision4)
1523
1647
  }
1524
1648
  return regularFormat(diagMant, precision4) + letter + commaFormat(diagBeta)
1525
1649
  }
1526
1650
  }
1527
1651
  }
1528
-
1652
+
1653
+ // ── Unnamed-level display (v2.0.2) ──
1654
+ // The letter grid only names ordinals with coefficients ≤ 25 (top ≤ 26).
1655
+ // When the TOP row's ordinal is above that (ω*2+96, ω*99, ω²*100, …), every
1656
+ // row of the cascade snaps up to the SAME next nameable level, and summing
1657
+ // their counts would collapse the letter further (243 Ca's → Cb(244)) —
1658
+ // overstating the level and hiding the argument. Instead the value reads
1659
+ // as level-arg: the snapped level with the operation's own argument —
1660
+ // 10{ω*2+100}100 → "1.000Ca100", 10{ω*100}100 → "1.000Aaa100",
1661
+ // 10{ω²*100+ω*100+100}1000 → "1.000Aaaa1,000".
1662
+ let gapStr = null
1663
+ {
1664
+ let topRow = ordRows[ordRows.length - 1]
1665
+ if (topRow && topRow.length >= 3) {
1666
+ let oc = topRow.slice(1)
1667
+ let carried = false
1668
+ for (let gi = 0; gi < oc.length; gi++) {
1669
+ let gc = oc[gi]
1670
+ let gbound = (gi === oc.length - 1) ? 26 : 25
1671
+ if (typeof gc !== 'number' || !isFinite(gc) || gc < 0 ||
1672
+ Math.round(gc) > gbound) { carried = true; break }
1673
+ }
1674
+ if (carried) {
1675
+ let g = snapCoeffsToGrid(oc)
1676
+ let gdiag = g[g.length - 1]
1677
+ let gvals = g.slice(0, g.length - 1)
1678
+ let gh = 23 + (gdiag - 1) * 26 + (gvals.length ? gvals[0] : 0)
1679
+ if (gvals.length >= 2) {
1680
+ let goffset = 0
1681
+ for (let gk = 2; gk <= gvals.length; gk++) goffset += Math.pow(26, gk)
1682
+ let gn = (gdiag - 1) * Math.pow(26, gvals.length)
1683
+ for (let gj = 0; gj < gvals.length; gj++) gn += gvals[gj] * Math.pow(26, gj)
1684
+ gh = 23 + goffset + gn
1685
+ }
1686
+ // β = the fundamental-sequence index that recovers the ordinal
1687
+ // from the snapped level. When the carry is in the TOP
1688
+ // coefficient (ω^p·c → ω^(p+1)) that index is c+1:
1689
+ // 10{ω²·50+ω·100+100}100 → "2.000Aaaa51"
1690
+ // 10{ω²·100+ω·100+100}100 → "2.000Aaaa101"
1691
+ // 10{ω²·200+…}100 → "2.000Aaaa201"
1692
+ // (taking the largest coefficient instead collapsed all three
1693
+ // of 50/60/100 into one string). When the carry is BELOW the
1694
+ // top (ω·2+100 → ω·3) the index is the operation's argument.
1695
+ let gbeta = (topRow[0] || 1) + 2
1696
+ for (let g2 = oc.length - 1; g2 >= 0; g2--) {
1697
+ let gc2 = Math.round(oc[g2])
1698
+ let gbound = (g2 === oc.length - 1) ? 26 : 25
1699
+ if (isFinite(gc2) && gc2 > gbound) { gbeta = gc2 + 1; break }
1700
+ }
1701
+ let gtok = letterTokenOf(gh)
1702
+ let gbase = r0[0]
1703
+ let galpha = Math.pow(10, gbase - Math.floor(gbase))
1704
+ if (galpha < 1) galpha *= 10
1705
+ if (gtok.sym) {
1706
+ // the carried !αAaβ form: β is the ω-EXPONENT of the
1707
+ // snapped level (ω³·50+… → ω⁴ → "!2.000Aa4"), not the
1708
+ // fundamental-sequence index — !2.000Aa51 would read as
1709
+ // the ω⁵¹ level (README: !αAaβ = ω^β + digits of α)
1710
+ gapStr = emitLetterToken(gtok, regularFormat(gtok.mant, precision4),
1711
+ formatR0Arg(gbeta, precision4), precision4)
1712
+ } else {
1713
+ // a diagonal letter ("Ca", "Aaa", …) carries α = 2·5^f ∈
1714
+ // [2,10) — never the [1,10) mantissa of the other letters
1715
+ gapStr = regularFormat(
1716
+ diagonalMantissa(gtok.letter, Math.log10(galpha)), precision4) +
1717
+ gtok.letter + formatR0Arg(gbeta, precision4)
1718
+ }
1719
+ }
1720
+ }
1721
+ }
1722
+ if (gapStr !== null) return gapStr
1723
+
1529
1724
  // ── Check for repeated non-ω letters → advance to next letter ──
1530
1725
  // AbAb→Ac, AcAc→Ad, ..., AzAz→Ba, BaBa→Bb, etc.
1531
1726
  // Only applies when repeat count >= multiLetterRepeatThreshold
@@ -1536,8 +1731,13 @@ function formatOrdinal(num, precision, precision4) {
1536
1731
  for (let i = 0; i < ordRows.length; i++) {
1537
1732
  let row = ordRows[i]
1538
1733
  if (row.length < 3) { allSameLetter = false; break }
1539
- let diag = row[row.length - 1]
1540
- let vals = row.slice(1, row.length - 1)
1734
+ // The letter grid only names ordinals whose coefficients are ≤ 25 (the
1735
+ // top one ≤ 26); anything larger (ω*2+96, ω*99, ω²*100, …) is rounded UP
1736
+ // to the next nameable ordinal — ω*2+96 → ω*3 (Ca), ω*99 → ω² (Aaa),
1737
+ // ω²*100 → ω³ (Aaaa) — never clamped down, never wrapped.
1738
+ let g = snapCoeffsToGrid(row.slice(1))
1739
+ let diag = g[g.length - 1]
1740
+ let vals = g.slice(0, g.length - 1)
1541
1741
  let nVals = vals.length
1542
1742
 
1543
1743
  // Calculate height for this letter
@@ -1591,19 +1791,24 @@ function formatOrdinal(num, precision, precision4) {
1591
1791
  let letter = letterName(baseLetterHeight)
1592
1792
  let tokAdv = letterTokenOf(baseLetterHeight + 1)
1593
1793
  if (totalRepeat >= collapseAt && tokAdv.sym) {
1594
- // Collapse to next letter — carried when it exceeds the limit;
1595
- // the r0 chain stays as the β of the carried form
1596
- return tokAdv.sym + regularFormat(tokAdv.mant, precision4) +
1597
- tokAdv.letter + r0Str
1794
+ // Collapse to next letter — carried when it exceeds the limit.
1795
+ // !αAaβ takes the ω-EXPONENT as β (README: !αAaβ = ω^β + the
1796
+ // digits of α), not the r0 chain.
1797
+ return emitLetterToken(tokAdv, regularFormat(tokAdv.mant, precision4),
1798
+ r0Str, precision4)
1598
1799
  }
1599
1800
  if (totalRepeat >= collapseAt) {
1600
- // Collapse to next letter
1801
+ // Collapse to next letter — the repeat count shifts the
1802
+ // argument, so the mantissa keeps the [1,10) convention
1601
1803
  let mantissa = Math.log10(Math.max(alpha, 0.001)) + totalRepeat
1602
1804
  let newLetter = letterName(baseLetterHeight + 1)
1603
- return regularFormat(mantissa, precision4) + newLetter + r0Str
1805
+ let mantVal = diagonalMantissa(newLetter, mantissa)
1806
+ return regularFormat(mantVal, precision4) + newLetter + r0Str
1604
1807
  } else if (totalRepeat > 1) {
1605
- // Repeated letters + chain
1606
- let outerLetters = letter.repeat(totalRepeat - 1)
1808
+ // Repeated letters + chain: one token per application, the
1809
+ // innermost one in front of the chain (Ba(Ba(1e16)) → the
1810
+ // stacked "BaBa1.000E16" — apea(apea(a)) must stay visible)
1811
+ let outerLetters = letter.repeat(totalRepeat)
1607
1812
  return outerLetters + r0Str
1608
1813
  } else {
1609
1814
  // Single letter + chain
@@ -1622,7 +1827,9 @@ function formatOrdinal(num, precision, precision4) {
1622
1827
  let mantissa = Math.log10(Math.max(alpha, 0.001)) + totalRepeat
1623
1828
  let newHeight = baseLetterHeight + 1
1624
1829
  let letter = letterName(newHeight)
1625
- return regularFormat(mantissa, precision4) + letter + betaStr
1830
+ let mantVal = diagonalMantissa(letter, mantissa)
1831
+ return regularFormat(mantVal, precision4) + letter +
1832
+ formatR0Arg(totalRepeat + 1, precision4)
1626
1833
  } else if (totalRepeat > 1) {
1627
1834
  // Repeated letters: outerLetters + α + lastLetter + β
1628
1835
  let letter = letterName(baseLetterHeight)
@@ -1674,16 +1881,24 @@ function formatOrdinal(num, precision, precision4) {
1674
1881
  if (row.length === 2) {
1675
1882
  height = 23 // ω-level (Aa)
1676
1883
  } else {
1677
- let diag = row[row.length - 1]
1678
- let vals = row.slice(1, row.length - 1)
1884
+ // round the ordinal UP to the next one the letter grid can name
1885
+ // (ω*2+96 → ω*3 = Ca, ω*99 → ω² = Aaa, ω²*100 → ω³ = Aaaa)
1886
+ let g2 = snapCoeffsToGrid(row.slice(1))
1887
+ let diag = g2[g2.length - 1]
1888
+ let vals = g2.slice(0, g2.length - 1)
1679
1889
  let nVals = vals.length
1680
1890
  if (nVals === 1) {
1891
+ // 2-letter: Aa=23, Ab=24, ..., Az=48, Ba=49, ...
1681
1892
  height = 23 + (diag - 1) * 26 + vals[0]
1682
1893
  } else {
1894
+ // vals are the CNF coefficients a1, a2, …, a(x-1) of
1895
+ // ord = ω^(x-1)·ax + … + ω·a2 + a1 — little-endian, the same
1896
+ // order letterName()/getOrdinalLetter() decode the letter:
1897
+ // the LAST val is the first lowercase letter of the token.
1683
1898
  let offset = 0
1684
1899
  for (let k = 2; k <= nVals; k++) offset += Math.pow(26, k)
1685
1900
  let n = (diag - 1) * Math.pow(26, nVals)
1686
- for (let j = 0; j < nVals; j++) n += vals[j] * Math.pow(26, nVals - 1 - j)
1901
+ for (let j = 0; j < nVals; j++) n += vals[j] * Math.pow(26, j)
1687
1902
  height = 23 + offset + n
1688
1903
  }
1689
1904
  }
@@ -1694,109 +1909,107 @@ function formatOrdinal(num, precision, precision4) {
1694
1909
  letterCounts[height] = (letterCounts[height] || 0) + count
1695
1910
  }
1696
1911
 
1697
- if (heights.length >= 2) {
1912
+ if (heights.length >= 1) {
1698
1913
  // Sort heights ascending
1699
1914
  heights.sort((a, b) => a - b)
1700
1915
 
1701
1916
  // README: at most TWO finite letter types survive — a longer
1702
- // cascade keeps only its top two levels and compresses the rest
1703
- // into the second letter's binary form (its repeat count).
1917
+ // cascade keeps only its top two levels, every lower row
1918
+ // compresses into the second letter's argument (they are below
1919
+ // display precision, and folding them into its repeat COUNT would
1920
+ // overstate the level: 8 ω's + 8 (ω+1)'s ≠ 16 more (ω+2)'s).
1704
1921
  if (heights.length > 2) {
1705
1922
  let keepLo = heights[heights.length - 2]
1706
- for (let i = 0; i < heights.length - 2; i++) {
1707
- letterCounts[keepLo] = (letterCounts[keepLo] || 0) + (letterCounts[heights[i]] || 0)
1708
- delete letterCounts[heights[i]]
1709
- }
1923
+ for (let i = 0; i < heights.length - 2; i++) delete letterCounts[heights[i]]
1710
1924
  heights = Object.keys(letterCounts).map(Number).sort((a, b) => a - b)
1711
1925
  }
1712
1926
 
1713
- // Collapse from lowest to highest
1714
- let effThreshold = Math.max(2, FORMAT_OPTIONS.multiLetterRepeatThreshold)
1715
- let collapseAt = effThreshold + 1
1716
- let collapseInfo = {} // height -> collapsedFromCount
1717
-
1718
- for (let h of heights.slice()) {
1719
- let count = letterCounts[h] || 0
1720
- if (count >= collapseAt) {
1721
- let nextH = h + 1
1722
- if (!(nextH in letterCounts) && heights.indexOf(nextH) < 0) heights.push(nextH)
1723
- letterCounts[nextH] = (letterCounts[nextH] || 0) + 1
1724
- collapseInfo[nextH] = count
1725
- delete letterCounts[h]
1726
- }
1727
- }
1728
-
1729
- // Re-sort after collapse
1730
- heights = Object.keys(letterCounts).map(Number).sort((a, b) => a - b)
1731
-
1732
1927
  if (heights.length >= 1) {
1733
1928
  // ── Display rules (README "format-metanum rules (v2.0)") ──
1734
- // * at most TWO finite letter types survive (VαEβ … VαVβ):
1735
- // a longer cascade keeps its top two levels and compresses
1736
- // the rest into the second letter's binary form;
1737
- // * exactly one α, attached to the innermost (lowest) letter;
1929
+ // * the rows are a COMPOSITION: the top row is the outermost
1930
+ // operation, so it leads the display and the row below it
1931
+ // supplies the single α and β (at most TWO letter types —
1932
+ // every lower row compresses into that inner argument);
1933
+ // * "+count collapse": count >= multiLetterRepeatThreshold+1
1934
+ // repeats of a letter collapse to ONE next letter with
1935
+ // arg count+1 (F⁴(G(1.3796)) = G(5.3796));
1936
+ // * stacking the same function therefore nests the letter:
1937
+ // poea(10,poea(10,100)) = Ad(Ad(99)) → "Ad1.000Ad99";
1738
1938
  // * a combination longer than multiLetterLimit carries to the
1739
- // symbol notation !αAaβ instead of growing another letter.
1939
+ // symbol notation !αAaβ.
1740
1940
  let effTh = Math.max(2, FORMAT_OPTIONS.multiLetterRepeatThreshold)
1941
+ let collapseAt = effTh + 1
1741
1942
 
1742
- // (1) the single α / β pair
1743
1943
  let base = r0[0]
1744
1944
  let r0Alpha = Math.pow(10, base - Math.floor(base))
1745
1945
  if (r0Alpha < 1) r0Alpha *= 10
1746
1946
  let frac = base - Math.floor(base)
1747
1947
  if (frac < 0) frac += 1
1748
1948
 
1749
- let collapseCount = 0
1750
- for (let ci = 0; ci < heights.length; ci++) {
1751
- collapseCount += collapseInfo[heights[ci]] || 0
1949
+ // α of a "+count collapse" (the ω-level form recovers the base
1950
+ // mantissa the way the single-row Aa cascade does: 2.398Ab99)
1951
+ let collapseMant = function (h, count) {
1952
+ let arg = count + 1
1953
+ if (h === 23) {
1954
+ return regularFormat(Math.log10(Math.max(r0Alpha, 0.001)) +
1955
+ Math.log10(Math.max(arg, 1)) * 0.4 + 1.6, precision4)
1956
+ }
1957
+ return regularFormat(r0Alpha, precision4)
1958
+ }
1959
+ // one collapsed level: α + next letter + (count+1)
1960
+ let collapseStr = function (h, count) {
1961
+ let tok = letterTokenOf(h + 1)
1962
+ if (tok.sym) {
1963
+ return tok.sym + regularFormat(tok.mant, precision4) +
1964
+ tok.letter + formatR0Arg(count + 1, precision4)
1965
+ }
1966
+ return collapseMant(h, count) + tok.letter + formatR0Arg(count + 1, precision4)
1752
1967
  }
1753
1968
 
1754
- let alphaStr, betaStr
1755
- if (collapseCount > 0) {
1756
- // Collapsed repeats: α and β come from the mantissa
1757
- // α = 10^(fractional part) ∈ [1, 10), β = floor(mantissa)
1758
- let mantissa = Math.log10(Math.max(r0Alpha, 0.001)) + collapseCount
1759
- let alphaVal = Math.pow(10, mantissa - Math.floor(mantissa))
1760
- if (alphaVal < 1) alphaVal *= 10
1761
- alphaStr = regularFormat(alphaVal, precision4)
1762
- betaStr = commaFormat(Math.floor(mantissa))
1763
- } else {
1764
- alphaStr = regularFormat(r0Alpha, precision4)
1765
- betaStr = formatR0Arg(Math.floor(base), precision4)
1969
+ let hTop = heights[heights.length - 1]
1970
+ let cTop = letterCounts[hTop] || 1
1971
+ let hLow = heights.length >= 2 ? heights[heights.length - 2] : -1
1972
+ let cLow = hLow < 0 ? 0 : (letterCounts[hLow] || 1)
1973
+
1974
+ // (1) a single row level: collapse, or repeats with α + letter + β
1975
+ if (hLow < 0) {
1976
+ if (cTop >= collapseAt) return collapseStr(hTop, cTop)
1977
+ let tok = letterTokenOf(hTop)
1978
+ if (tok.sym) return emitLetterToken(tok, regularFormat(r0Alpha, precision4),
1979
+ formatR0Arg(Math.floor(base), precision4), precision4)
1980
+ let aStr = (tok.letter.length >= 2 && tok.letter[tok.letter.length - 1] === "a")
1981
+ ? regularFormat(2 * Math.pow(5, frac), precision4)
1982
+ : regularFormat(r0Alpha, precision4)
1983
+ return tok.letter.repeat(Math.max(cTop - 1, 0)) + aStr + tok.letter +
1984
+ formatR0Arg(Math.floor(base), precision4)
1766
1985
  }
1767
- // Diagonal combinations (letters ending in 'a': Aa, Ba, Aaa, …)
1768
- // use the pure dlsdl mantissa α = 2·5^f ∈ [2, 10) — but only
1769
- // when the diagonal letter IS the value's canonical letter
1770
- // (single-type display). Inside a cascade the α is the inner
1771
- // value's own mantissa and keeps the ordinary [1,10) form
1772
- // (AbAa600 → Ab1.000Aa600).
1773
- let diagMant = function (tok) {
1774
- if (collapseCount > 0) return alphaStr
1775
- if (descHeights.length > 1) return alphaStr
1776
- if (!(tok.letter.length >= 2 && tok.letter[tok.letter.length - 1] === "a")) return alphaStr
1777
- return regularFormat(2 * Math.pow(5, frac), precision4)
1986
+
1987
+ // (2) the inner (second) level carries the single α and β
1988
+ let lowTok = letterTokenOf(hLow)
1989
+ let innerStr
1990
+ if (cLow >= collapseAt) {
1991
+ innerStr = collapseStr(hLow, cLow)
1992
+ } else {
1993
+ let aStr = regularFormat(r0Alpha, precision4)
1994
+ innerStr = lowTok.letter.repeat(Math.max(cLow - 1, 0)) + aStr + lowTok.letter +
1995
+ formatR0Arg(Math.floor(base), precision4)
1778
1996
  }
1779
1997
 
1780
- // (3) compose: descending order (highest first = outermost)
1781
- let descHeights = heights.slice().reverse()
1782
- let topTok = letterTokenOf(descHeights[0])
1998
+ // (3) the top level leads: collapse when it repeats enough, else
1999
+ // one token per application (or the symbol-carried !αAaβ)
2000
+ if (cTop >= collapseAt) return collapseStr(hTop, cTop)
2001
+ let topTok = letterTokenOf(hTop)
1783
2002
  if (topTok.sym) {
1784
- // the top level needs the symbol notation — it dwarfs every
1785
- // lower level, so the display is the single !αAaβ form
1786
- return emitLetterToken(topTok, alphaStr, betaStr, precision4)
1787
- }
1788
- let outerLetters = topTok.letter.repeat(
1789
- Math.min(letterCounts[descHeights[0]] || 1, effTh))
1790
- if (descHeights.length >= 2) {
1791
- let loH = descHeights[1]
1792
- let loTok = letterTokenOf(loH)
1793
- let loCnt = letterCounts[loH] || 1
1794
- if (loCnt > 1) {
1795
- outerLetters += loTok.letter.repeat(Math.min(loCnt - 1, effTh))
2003
+ // multiLetterLimit carry: !αAaβ, β = the ω-exponent; the
2004
+ // inner level only survives when it collapses on its own
2005
+ if (cLow >= collapseAt) {
2006
+ return topTok.sym + regularFormat(topTok.mant, precision4) +
2007
+ topTok.letter + collapseStr(hLow, cLow)
1796
2008
  }
1797
- return outerLetters + diagMant(loTok) + loTok.letter + betaStr
2009
+ return topTok.sym + regularFormat(topTok.mant, precision4) +
2010
+ topTok.letter + formatR0Arg(topTok.arg, precision4)
1798
2011
  }
1799
- return diagMant(topTok) + topTok.letter + betaStr
2012
+ return topTok.letter.repeat(Math.min(cTop, effTh)) + innerStr
1800
2013
  }
1801
2014
  }
1802
2015
  }