metanum 2.0.1 → 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
@@ -1531,18 +1638,89 @@ function formatOrdinal(num, precision, precision4) {
1531
1638
  let diagMant = 2 * Math.pow(5, base - Math.floor(base))
1532
1639
  let diagBeta = (base === aaZeroCount) ? 10 : top
1533
1640
  // multiLetterLimit symbol carry: A + aaZeroCount a's is
1534
- // aaZeroCount+1 letters — past the limit the diagonal reads
1535
- // as one more symbol with the Aa argument taking the exponent
1536
- // (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)
1537
1644
  if (aaZeroCount + 1 > Math.max(2, FORMAT_OPTIONS.multiLetterLimit | 0)) {
1538
- return "!" + regularFormat(diagMant, precision4) +
1539
- "Aa" + formatR0Arg(aaZeroCount, precision4)
1645
+ return "!" + regularFormat(1, precision4) +
1646
+ letterName(aaZeroCount) + formatR0Arg(10, precision4)
1540
1647
  }
1541
1648
  return regularFormat(diagMant, precision4) + letter + commaFormat(diagBeta)
1542
1649
  }
1543
1650
  }
1544
1651
  }
1545
-
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
+
1546
1724
  // ── Check for repeated non-ω letters → advance to next letter ──
1547
1725
  // AbAb→Ac, AcAc→Ad, ..., AzAz→Ba, BaBa→Bb, etc.
1548
1726
  // Only applies when repeat count >= multiLetterRepeatThreshold
@@ -1553,19 +1731,20 @@ function formatOrdinal(num, precision, precision4) {
1553
1731
  for (let i = 0; i < ordRows.length; i++) {
1554
1732
  let row = ordRows[i]
1555
1733
  if (row.length < 3) { allSameLetter = false; break }
1556
- let diag = row[row.length - 1]
1557
- 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)
1558
1741
  let nVals = vals.length
1559
1742
 
1560
1743
  // Calculate height for this letter
1561
1744
  let height = 0
1562
1745
  if (nVals === 1) {
1563
- // 2-letter: Aa=23, Ab=24, ..., Az=48, Ba=49, ... A coefficient
1564
- // above 25 (ω+81, ω+99, … from a ω*2 fundamental sequence) has no
1565
- // letter in the grid — clamp down to the largest letter below it
1566
- // rather than wrapping into a higher, wrong letter.
1567
- let v0 = vals[0] > 25 ? 25 : vals[0]
1568
- height = 23 + (diag - 1) * 26 + v0
1746
+ // 2-letter: Aa=23, Ab=24, ..., Az=48, Ba=49, ...
1747
+ height = 23 + (diag - 1) * 26 + vals[0]
1569
1748
  } else {
1570
1749
  // 3+ letters: need to calculate offset
1571
1750
  // vals are stored in reverse letter order (e.g. "bc" -> [2,1]),
@@ -1612,18 +1791,18 @@ function formatOrdinal(num, precision, precision4) {
1612
1791
  let letter = letterName(baseLetterHeight)
1613
1792
  let tokAdv = letterTokenOf(baseLetterHeight + 1)
1614
1793
  if (totalRepeat >= collapseAt && tokAdv.sym) {
1615
- // Collapse to next letter — carried when it exceeds the limit;
1616
- // the r0 chain stays as the β of the carried form
1617
- return tokAdv.sym + regularFormat(tokAdv.mant, precision4) +
1618
- 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)
1619
1799
  }
1620
1800
  if (totalRepeat >= collapseAt) {
1621
1801
  // Collapse to next letter — the repeat count shifts the
1622
1802
  // argument, so the mantissa keeps the [1,10) convention
1623
1803
  let mantissa = Math.log10(Math.max(alpha, 0.001)) + totalRepeat
1624
- let mantVal = Math.pow(10, mantissa - Math.floor(mantissa))
1625
- if (mantVal < 1) mantVal *= 10
1626
1804
  let newLetter = letterName(baseLetterHeight + 1)
1805
+ let mantVal = diagonalMantissa(newLetter, mantissa)
1627
1806
  return regularFormat(mantVal, precision4) + newLetter + r0Str
1628
1807
  } else if (totalRepeat > 1) {
1629
1808
  // Repeated letters + chain: one token per application, the
@@ -1646,16 +1825,9 @@ function formatOrdinal(num, precision, precision4) {
1646
1825
  return emitLetterToken(tokAdv, regularFormat(tokAdv.mant, precision4), betaStr, precision4)
1647
1826
  }
1648
1827
  let mantissa = Math.log10(Math.max(alpha, 0.001)) + totalRepeat
1649
- // the repeat count shifts the argument, so the mantissa keeps the
1650
- // [1,10) convention (the ω-level form recovers the base mantissa
1651
- // the way the single-row Aa cascade does: 2.398Ab99)
1652
- let mantVal = (baseLetterHeight === 23)
1653
- ? Math.log10(Math.max(alpha, 0.001)) +
1654
- Math.log10(Math.max(totalRepeat + 1, 1)) * 0.4 + 1.6
1655
- : Math.pow(10, mantissa - Math.floor(mantissa))
1656
- if (mantVal < 1) mantVal *= 10
1657
1828
  let newHeight = baseLetterHeight + 1
1658
1829
  let letter = letterName(newHeight)
1830
+ let mantVal = diagonalMantissa(letter, mantissa)
1659
1831
  return regularFormat(mantVal, precision4) + letter +
1660
1832
  formatR0Arg(totalRepeat + 1, precision4)
1661
1833
  } else if (totalRepeat > 1) {
@@ -1709,17 +1881,15 @@ function formatOrdinal(num, precision, precision4) {
1709
1881
  if (row.length === 2) {
1710
1882
  height = 23 // ω-level (Aa)
1711
1883
  } else {
1712
- let diag = row[row.length - 1]
1713
- 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)
1714
1889
  let nVals = vals.length
1715
1890
  if (nVals === 1) {
1716
- // the letter grid holds ω·d+v with 0 ≤ v ≤ 25; a larger
1717
- // coefficient (a ω*2 fundamental sequence produces ω+81,
1718
- // ω+99, …) has no letter, so clamp it down to the largest
1719
- // letter below the ordinal instead of wrapping into a
1720
- // higher, wrong letter (ω+81 must not display as ω*4+3)
1721
- let v0 = vals[0] > 25 ? 25 : vals[0]
1722
- height = 23 + (diag - 1) * 26 + v0
1891
+ // 2-letter: Aa=23, Ab=24, ..., Az=48, Ba=49, ...
1892
+ height = 23 + (diag - 1) * 26 + vals[0]
1723
1893
  } else {
1724
1894
  // vals are the CNF coefficients a1, a2, …, a(x-1) of
1725
1895
  // ord = ω^(x-1)·ax + … + ω·a2 + a1 — little-endian, the same