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/README.md +149 -228
- package/format-metanum.js +249 -79
- package/metanum.js +413 -61
- package/package.json +1 -1
- package/test-interface.html +1 -0
- package/test_mixed.js +739 -42
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,
|
|
11
|
-
smallNotationThreshold: 4,
|
|
12
|
-
decimalPlaces: 3,
|
|
13
|
-
decimalThreshold: 3,
|
|
14
|
-
useCommas: true,
|
|
15
|
-
sciThreshold: 9,
|
|
16
|
-
sciSignificantDigits: 3,
|
|
17
|
-
sciDecimalThreshold: 3,
|
|
18
|
-
singleLetterDigits: 3,
|
|
19
|
-
repeatLetterThreshold: 3,
|
|
20
|
-
multiLetterDigits: 3,
|
|
21
|
-
multiLetterRepeatThreshold: 3
|
|
22
|
-
multiLetterLimit: 4,
|
|
23
|
-
epsilonSignificantDigits: 6
|
|
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:
|
|
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,
|
|
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,
|
|
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,
|
|
864
|
+
return regularFormat(bottom, epsPrecision) + "ε" + commaFormat(num.layer + 1)
|
|
819
865
|
}
|
|
820
|
-
let innerStr = format(inner,
|
|
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(
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
//
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
let
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
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,
|
|
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,
|
|
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
|
-
//
|
|
1536
|
-
// (10{ω^10}10 → !
|
|
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(
|
|
1539
|
-
|
|
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
|
-
|
|
1557
|
-
|
|
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, ...
|
|
1564
|
-
|
|
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
|
-
//
|
|
1617
|
-
|
|
1618
|
-
|
|
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
|
-
|
|
1713
|
-
|
|
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
|
-
//
|
|
1717
|
-
|
|
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
|