metanum 2.0.1 → 2.0.3
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 +269 -79
- package/metanum.js +632 -91
- 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:
|
|
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: 3, //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
|
|
|
@@ -600,6 +645,26 @@ function format(num, precision=3, small=false) {
|
|
|
600
645
|
if (num.sign < 0) return "-" + format(num.neg(), precision, small)
|
|
601
646
|
if (num.isInfinite()) return "Infinity"
|
|
602
647
|
|
|
648
|
+
// Sub-unit values (sign 2 / -2, the whole 0 < |v| < 1 range): a plain
|
|
649
|
+
// finite r0 stores the RECIPROCAL, so the real value is 1/r0 — format that
|
|
650
|
+
// number and let the normal machinery below render it (decimal places at or
|
|
651
|
+
// above 10^-smallNotationThreshold, αE-β below it). Reading the raw r0 as
|
|
652
|
+
// the value printed the reciprocal instead: MetaNum(1).div(2000)
|
|
653
|
+
// (r0 = [2000], value 5e-4) came out as "2.000E-3" instead of "5.000E-4".
|
|
654
|
+
// Structured sign-2 forms (layer > 0, ordinal rows, power-tower r0) keep the
|
|
655
|
+
// letter-law path further down.
|
|
656
|
+
if ((num.sign === 2 || num.sign === -2) && num.layer === 0 &&
|
|
657
|
+
num.array.length === 1 && num.array[0].length === 1 &&
|
|
658
|
+
isFinite(num.array[0][0]) && num.array[0][0] !== 0) {
|
|
659
|
+
let realVal = 1 / num.array[0][0]
|
|
660
|
+
if (isFinite(realVal) && realVal > 0) {
|
|
661
|
+
let asNormal = num.clone()
|
|
662
|
+
asNormal.sign = 1
|
|
663
|
+
asNormal.array = [[realVal]]
|
|
664
|
+
num = asNormal
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
603
668
|
// Small value handling (e.g., 0.0000000001 → 1.000E-10 or 1.000E10⁻¹)
|
|
604
669
|
let smallThreshold = Math.pow(10, -FORMAT_OPTIONS.smallNotationThreshold)
|
|
605
670
|
if (num.lt(smallThreshold)) {
|
|
@@ -620,7 +685,7 @@ function format(num, precision=3, small=false) {
|
|
|
620
685
|
var expPart = Math.floor(magNum)
|
|
621
686
|
if (mant < 1) { mant *= 10; expPart -= 1 }
|
|
622
687
|
if (mant >= 9.999999999999999) { mant = 1; expPart += 1 }
|
|
623
|
-
return prefix + regularFormat(mant,
|
|
688
|
+
return prefix + regularFormat(mant, sciMantissaPrecision(expPart)) + "E-" + commaFormat(expPart)
|
|
624
689
|
}
|
|
625
690
|
}
|
|
626
691
|
// Tier 2: mag is large → "E-" + letter-law form of mag =
|
|
@@ -670,7 +735,7 @@ function format(num, precision=3, small=false) {
|
|
|
670
735
|
// Normalize: ensure m in [1, 10)
|
|
671
736
|
if (m < 1) { m *= 10; e -= 1 }
|
|
672
737
|
if (m >= 9.999999999999999) { m = 1; e += 1 }
|
|
673
|
-
return regularFormat(m,
|
|
738
|
+
return regularFormat(m, sciMantissaPrecision(e)) + "E-" + commaFormat(e)
|
|
674
739
|
}
|
|
675
740
|
// Fallback for non-simple values: use ⁻¹ notation
|
|
676
741
|
if (num.layer === 0 && num.array.length === 1 && num.array[0].length === 1) {
|
|
@@ -811,13 +876,14 @@ function formatLayer(num, precision, precision2, precision3, precision4) {
|
|
|
811
876
|
// This check must come before the compact !Aa form, otherwise ε layers
|
|
812
877
|
// get mis-rendered as "Aa..." (e.g. 1ε500 → "1.000Aa10")
|
|
813
878
|
if (num.layer > SYMBOLS.length) {
|
|
879
|
+
let epsPrecision = FORMAT_OPTIONS.epsilonSignificantDigits | 0
|
|
814
880
|
if (canCompact && compactBase >= 2 && Math.floor(compactBase) === compactBase) {
|
|
815
881
|
// Compact Aa form at ε layers folds into one more ε level:
|
|
816
882
|
// layer n + Aa-form ≡ ε(n+1) with mantissa 10^frac(compactBase)
|
|
817
883
|
let bottom = Math.pow(10, compactBase - Math.floor(compactBase))
|
|
818
|
-
return regularFormat(bottom,
|
|
884
|
+
return regularFormat(bottom, epsPrecision) + "ε" + commaFormat(num.layer + 1)
|
|
819
885
|
}
|
|
820
|
-
let innerStr = format(inner,
|
|
886
|
+
let innerStr = format(inner, epsPrecision, false)
|
|
821
887
|
return innerStr + "ε" + commaFormat(num.layer)
|
|
822
888
|
}
|
|
823
889
|
|
|
@@ -838,8 +904,13 @@ function formatLayer(num, precision, precision2, precision3, precision4) {
|
|
|
838
904
|
let mant = 2 * Math.pow(5, frac)
|
|
839
905
|
let limit = Math.max(2, FORMAT_OPTIONS.multiLetterLimit | 0)
|
|
840
906
|
if (mInt + 1 > limit) {
|
|
907
|
+
// Carry to the SINGLE-letter form sym αΓβ — same rule as "!":
|
|
908
|
+
// Γ is the finite level the diagonal names (10{ω^m}10 → letter of
|
|
909
|
+
// m) and β is the diagonal's argument, so the symbols stack
|
|
910
|
+
// identically: 10{ω^10}10 → !1.000N10, 10{ω^(ω^10)}10 → @1.000N10
|
|
841
911
|
let total = num.layer + 1
|
|
842
|
-
let body = regularFormat(
|
|
912
|
+
let body = regularFormat(1, precision4) + letterName(mInt) +
|
|
913
|
+
formatR0Arg(10, precision4)
|
|
843
914
|
if (total <= SYMBOLS.length) return SYMBOLS[total - 1] + body
|
|
844
915
|
return body + "ε" + commaFormat(total)
|
|
845
916
|
}
|
|
@@ -863,6 +934,23 @@ function formatLayer(num, precision, precision2, precision3, precision4) {
|
|
|
863
934
|
return sym + innerStr
|
|
864
935
|
}
|
|
865
936
|
|
|
937
|
+
// ─── Option-driven mantissa precisions ───────────────────────────
|
|
938
|
+
// Decimal places of the mantissa α in an αEβ form: sciSignificantDigits, but
|
|
939
|
+
// once the exponent β reaches 10^sciDecimalThreshold only the integer part of
|
|
940
|
+
// α is shown (the low digits carry no information at that height).
|
|
941
|
+
function sciMantissaPrecision(exponent) {
|
|
942
|
+
let t = FORMAT_OPTIONS.sciDecimalThreshold | 0
|
|
943
|
+
if (t > 0 && isFinite(exponent) && exponent >= Math.pow(10, t)) return 0
|
|
944
|
+
return FORMAT_OPTIONS.sciSignificantDigits | 0
|
|
945
|
+
}
|
|
946
|
+
// Decimal places of the mantissa α in αΓβ: E → sciSignificantDigits,
|
|
947
|
+
// F..Z → singleLetterDigits, Aa and beyond → multiLetterDigits.
|
|
948
|
+
function letterMantissaPrecision(level) {
|
|
949
|
+
if (level <= 1) return FORMAT_OPTIONS.sciSignificantDigits | 0
|
|
950
|
+
if (level <= 22) return FORMAT_OPTIONS.singleLetterDigits | 0
|
|
951
|
+
return FORMAT_OPTIONS.multiLetterDigits | 0
|
|
952
|
+
}
|
|
953
|
+
|
|
866
954
|
// ─── Format r0 Argument (plain number) ───────────────────────────
|
|
867
955
|
// Formats a plain numeric argument as either a plain number or αEβ notation.
|
|
868
956
|
// The mantissa α is always shown (even when 1.000).
|
|
@@ -879,7 +967,7 @@ function formatR0Arg(value, precision) {
|
|
|
879
967
|
if (m < 1) { m *= 10; e -= 1 }
|
|
880
968
|
if (m >= 9.999999999999999) { m = 1; e += 1 }
|
|
881
969
|
// Always show mantissa (αEβ format, e.g. 1.000E700)
|
|
882
|
-
return regularFormat(m,
|
|
970
|
+
return regularFormat(m, sciMantissaPrecision(e)) + "E" + commaFormat(e)
|
|
883
971
|
}
|
|
884
972
|
|
|
885
973
|
// ─── Gamma-Arg Recovery ─────────────────────────────────────────
|
|
@@ -928,7 +1016,7 @@ function formatGammaBinary(x, level, precision) {
|
|
|
928
1016
|
return letterName(level) + formatR0Arg(beta, precision)
|
|
929
1017
|
}
|
|
930
1018
|
let alpha = Math.pow(10, frac)
|
|
931
|
-
return regularFormat(alpha,
|
|
1019
|
+
return regularFormat(alpha, letterMantissaPrecision(level)) + letterName(level) + formatR0Arg(beta, precision)
|
|
932
1020
|
}
|
|
933
1021
|
|
|
934
1022
|
// Distinct letter types in a display string: single-uppercase letters and
|
|
@@ -950,8 +1038,27 @@ function distinctLetterTypes(str) {
|
|
|
950
1038
|
// Format pattern: outerLetters + α + lastLetter + β
|
|
951
1039
|
// where α = 10^(base - floor(base)), β = floor(base) formatted as number or αEβ
|
|
952
1040
|
// e.g. EE200 → "E1.000E200", F300 → "1.000F300", FE400 → "F1.000E400"
|
|
953
|
-
|
|
1041
|
+
// Mantissa of a binary form αΓβ. A letter whose name ends in "a" is a
|
|
1042
|
+
// diagonal letter (Aa = ω, Ba = ω·2, Aaa = ω², …) and its binary form has
|
|
1043
|
+
// α = 2·5^f ∈ [2,10) — the diagonal 10{23}10 reads "2.000Aa23", never
|
|
1044
|
+
// "1.000Aa23". Every other letter keeps the ordinary [1,10) mantissa.
|
|
1045
|
+
function diagonalMantissa(letter, mantissa) {
|
|
1046
|
+
if (letter && letter.charAt(letter.length - 1) === 'a') {
|
|
1047
|
+
let f = mantissa - Math.floor(mantissa)
|
|
1048
|
+
if (!(f >= 0 && f < 1)) f = 0
|
|
1049
|
+
return 2 * Math.pow(5, f)
|
|
1050
|
+
}
|
|
1051
|
+
let mantVal = Math.pow(10, mantissa - Math.floor(mantissa))
|
|
1052
|
+
if (mantVal < 1) mantVal *= 10
|
|
1053
|
+
return mantVal
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// `used` = how many copies of the letter the caller already emitted: an inner
|
|
1057
|
+
// level may only promote to the next letter while the total stays within
|
|
1058
|
+
// repeatLetterThreshold, otherwise it saturates (see the noPromote block below).
|
|
1059
|
+
function formatR0AsChain(r0, precision, used) {
|
|
954
1060
|
if (r0.length === 0) r0 = [0]
|
|
1061
|
+
let usedCount = used || 0
|
|
955
1062
|
|
|
956
1063
|
// Find maxLevel (highest non-zero index in r0[1..])
|
|
957
1064
|
let maxLevel = 0
|
|
@@ -1026,23 +1133,43 @@ function formatR0AsChain(r0, precision) {
|
|
|
1026
1133
|
if (innerR0[maxLevel] === 0) {
|
|
1027
1134
|
innerR0 = innerR0.slice(0, maxLevel)
|
|
1028
1135
|
}
|
|
1029
|
-
let innerStr = formatR0AsChain(innerR0, precision)
|
|
1030
|
-
|
|
1031
|
-
//
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
let
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1136
|
+
let innerStr = formatR0AsChain(innerR0, precision, usedCount + 1)
|
|
1137
|
+
|
|
1138
|
+
// null = the inner level would need one more copy of the outer letter
|
|
1139
|
+
// than repeatLetterThreshold allows (3{3}6 = FFF(E^7.6e12) would spell a
|
|
1140
|
+
// fourth F). An inner level propagates it; the OUTERMOST call instead
|
|
1141
|
+
// falls through to the Γ-canonical collapse below, which renders the
|
|
1142
|
+
// whole value at the next letter by its real argument: "2.045G5".
|
|
1143
|
+
if (innerStr !== null) {
|
|
1144
|
+
// Check if innerStr already has letters (meaning deeper operations exist)
|
|
1145
|
+
let hasLetter = /[A-Z]/.test(innerStr)
|
|
1146
|
+
|
|
1147
|
+
if (!hasLetter) {
|
|
1148
|
+
// innerStr is a plain number: this is the innermost operation
|
|
1149
|
+
// Format: α + letterName(maxLevel) + innerStr
|
|
1150
|
+
let base = r0[0]
|
|
1151
|
+
let alpha = Math.pow(10, base - Math.floor(base))
|
|
1152
|
+
if (alpha < 1) alpha *= 10
|
|
1153
|
+
// an E letter's αEβ obeys sciDecimalThreshold: β ≥ 10^n → α
|
|
1154
|
+
// keeps only its integer part
|
|
1155
|
+
let mantPrec = (maxLevel === 1)
|
|
1156
|
+
? sciMantissaPrecision(Number(String(innerStr).replace(/,/g, '')))
|
|
1157
|
+
: letterMantissaPrecision(maxLevel)
|
|
1158
|
+
let alphaStr = regularFormat(alpha, mantPrec)
|
|
1159
|
+
return alphaStr + letterName(maxLevel) + innerStr
|
|
1160
|
+
} else {
|
|
1161
|
+
// innerStr already has α and letters: just prepend the outer letter
|
|
1162
|
+
return letterName(maxLevel) + innerStr
|
|
1163
|
+
}
|
|
1045
1164
|
}
|
|
1165
|
+
if (usedCount > 0) return null
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// count > effThreshold and the promotion would exceed repeatLetterThreshold
|
|
1169
|
+
// copies of the letter in front of it: signal the caller (the structural
|
|
1170
|
+
// spelling is impossible) instead of spelling an extra copy.
|
|
1171
|
+
if (usedCount + 1 > effThreshold) {
|
|
1172
|
+
return null
|
|
1046
1173
|
}
|
|
1047
1174
|
|
|
1048
1175
|
// count >= effThreshold: promote to next level (maxLevel + 1)
|
|
@@ -1076,7 +1203,7 @@ function formatR0AsChain(r0, precision) {
|
|
|
1076
1203
|
// Format: α + letter + β (always show α)
|
|
1077
1204
|
let alpha = Math.pow(10, arg - Math.floor(arg))
|
|
1078
1205
|
if (alpha < 1) alpha *= 10
|
|
1079
|
-
let alphaStr = regularFormat(alpha,
|
|
1206
|
+
let alphaStr = regularFormat(alpha, letterMantissaPrecision(level))
|
|
1080
1207
|
let betaStr = formatR0Arg(Math.floor(arg), precision)
|
|
1081
1208
|
return alphaStr + letter + betaStr
|
|
1082
1209
|
}
|
|
@@ -1105,13 +1232,13 @@ function formatR0AsChain(r0, precision) {
|
|
|
1105
1232
|
if (innerR0[maxLevel] === 0) {
|
|
1106
1233
|
innerR0 = innerR0.slice(0, maxLevel)
|
|
1107
1234
|
}
|
|
1108
|
-
let innerStr = formatR0AsChain(innerR0, precision)
|
|
1235
|
+
let innerStr = formatR0AsChain(innerR0, precision, true)
|
|
1109
1236
|
let hasLetter = /[A-Z]/.test(innerStr)
|
|
1110
1237
|
if (!hasLetter) {
|
|
1111
1238
|
let base = r0[0]
|
|
1112
1239
|
let alpha = Math.pow(10, base - Math.floor(base))
|
|
1113
1240
|
if (alpha < 1) alpha *= 10
|
|
1114
|
-
return regularFormat(alpha,
|
|
1241
|
+
return regularFormat(alpha, letterMantissaPrecision(maxLevel)) + letterName(maxLevel) + innerStr
|
|
1115
1242
|
}
|
|
1116
1243
|
// inner carries letters: bare outer letter (2-letter rule) — the
|
|
1117
1244
|
// inner top must be LOWER (descending chain); ascending or same-level
|
|
@@ -1531,18 +1658,89 @@ function formatOrdinal(num, precision, precision4) {
|
|
|
1531
1658
|
let diagMant = 2 * Math.pow(5, base - Math.floor(base))
|
|
1532
1659
|
let diagBeta = (base === aaZeroCount) ? 10 : top
|
|
1533
1660
|
// multiLetterLimit symbol carry: A + aaZeroCount a's is
|
|
1534
|
-
// aaZeroCount+1 letters — past the limit the diagonal reads
|
|
1535
|
-
//
|
|
1536
|
-
// (10{ω^10}10 → !
|
|
1661
|
+
// aaZeroCount+1 letters — past the limit the diagonal reads as
|
|
1662
|
+
// one more symbol with the SINGLE letter of the finite level
|
|
1663
|
+
// aaZeroCount (10{ω^10}10 → !1.000N10)
|
|
1537
1664
|
if (aaZeroCount + 1 > Math.max(2, FORMAT_OPTIONS.multiLetterLimit | 0)) {
|
|
1538
|
-
return "!" + regularFormat(
|
|
1539
|
-
|
|
1665
|
+
return "!" + regularFormat(1, precision4) +
|
|
1666
|
+
letterName(aaZeroCount) + formatR0Arg(10, precision4)
|
|
1540
1667
|
}
|
|
1541
1668
|
return regularFormat(diagMant, precision4) + letter + commaFormat(diagBeta)
|
|
1542
1669
|
}
|
|
1543
1670
|
}
|
|
1544
1671
|
}
|
|
1545
|
-
|
|
1672
|
+
|
|
1673
|
+
// ── Unnamed-level display (v2.0.2) ──
|
|
1674
|
+
// The letter grid only names ordinals with coefficients ≤ 25 (top ≤ 26).
|
|
1675
|
+
// When the TOP row's ordinal is above that (ω*2+96, ω*99, ω²*100, …), every
|
|
1676
|
+
// row of the cascade snaps up to the SAME next nameable level, and summing
|
|
1677
|
+
// their counts would collapse the letter further (243 Ca's → Cb(244)) —
|
|
1678
|
+
// overstating the level and hiding the argument. Instead the value reads
|
|
1679
|
+
// as level-arg: the snapped level with the operation's own argument —
|
|
1680
|
+
// 10{ω*2+100}100 → "1.000Ca100", 10{ω*100}100 → "1.000Aaa100",
|
|
1681
|
+
// 10{ω²*100+ω*100+100}1000 → "1.000Aaaa1,000".
|
|
1682
|
+
let gapStr = null
|
|
1683
|
+
{
|
|
1684
|
+
let topRow = ordRows[ordRows.length - 1]
|
|
1685
|
+
if (topRow && topRow.length >= 3) {
|
|
1686
|
+
let oc = topRow.slice(1)
|
|
1687
|
+
let carried = false
|
|
1688
|
+
for (let gi = 0; gi < oc.length; gi++) {
|
|
1689
|
+
let gc = oc[gi]
|
|
1690
|
+
let gbound = (gi === oc.length - 1) ? 26 : 25
|
|
1691
|
+
if (typeof gc !== 'number' || !isFinite(gc) || gc < 0 ||
|
|
1692
|
+
Math.round(gc) > gbound) { carried = true; break }
|
|
1693
|
+
}
|
|
1694
|
+
if (carried) {
|
|
1695
|
+
let g = snapCoeffsToGrid(oc)
|
|
1696
|
+
let gdiag = g[g.length - 1]
|
|
1697
|
+
let gvals = g.slice(0, g.length - 1)
|
|
1698
|
+
let gh = 23 + (gdiag - 1) * 26 + (gvals.length ? gvals[0] : 0)
|
|
1699
|
+
if (gvals.length >= 2) {
|
|
1700
|
+
let goffset = 0
|
|
1701
|
+
for (let gk = 2; gk <= gvals.length; gk++) goffset += Math.pow(26, gk)
|
|
1702
|
+
let gn = (gdiag - 1) * Math.pow(26, gvals.length)
|
|
1703
|
+
for (let gj = 0; gj < gvals.length; gj++) gn += gvals[gj] * Math.pow(26, gj)
|
|
1704
|
+
gh = 23 + goffset + gn
|
|
1705
|
+
}
|
|
1706
|
+
// β = the fundamental-sequence index that recovers the ordinal
|
|
1707
|
+
// from the snapped level. When the carry is in the TOP
|
|
1708
|
+
// coefficient (ω^p·c → ω^(p+1)) that index is c+1:
|
|
1709
|
+
// 10{ω²·50+ω·100+100}100 → "2.000Aaaa51"
|
|
1710
|
+
// 10{ω²·100+ω·100+100}100 → "2.000Aaaa101"
|
|
1711
|
+
// 10{ω²·200+…}100 → "2.000Aaaa201"
|
|
1712
|
+
// (taking the largest coefficient instead collapsed all three
|
|
1713
|
+
// of 50/60/100 into one string). When the carry is BELOW the
|
|
1714
|
+
// top (ω·2+100 → ω·3) the index is the operation's argument.
|
|
1715
|
+
let gbeta = (topRow[0] || 1) + 2
|
|
1716
|
+
for (let g2 = oc.length - 1; g2 >= 0; g2--) {
|
|
1717
|
+
let gc2 = Math.round(oc[g2])
|
|
1718
|
+
let gbound = (g2 === oc.length - 1) ? 26 : 25
|
|
1719
|
+
if (isFinite(gc2) && gc2 > gbound) { gbeta = gc2 + 1; break }
|
|
1720
|
+
}
|
|
1721
|
+
let gtok = letterTokenOf(gh)
|
|
1722
|
+
let gbase = r0[0]
|
|
1723
|
+
let galpha = Math.pow(10, gbase - Math.floor(gbase))
|
|
1724
|
+
if (galpha < 1) galpha *= 10
|
|
1725
|
+
if (gtok.sym) {
|
|
1726
|
+
// the carried !αAaβ form: β is the ω-EXPONENT of the
|
|
1727
|
+
// snapped level (ω³·50+… → ω⁴ → "!2.000Aa4"), not the
|
|
1728
|
+
// fundamental-sequence index — !2.000Aa51 would read as
|
|
1729
|
+
// the ω⁵¹ level (README: !αAaβ = ω^β + digits of α)
|
|
1730
|
+
gapStr = emitLetterToken(gtok, regularFormat(gtok.mant, precision4),
|
|
1731
|
+
formatR0Arg(gbeta, precision4), precision4)
|
|
1732
|
+
} else {
|
|
1733
|
+
// a diagonal letter ("Ca", "Aaa", …) carries α = 2·5^f ∈
|
|
1734
|
+
// [2,10) — never the [1,10) mantissa of the other letters
|
|
1735
|
+
gapStr = regularFormat(
|
|
1736
|
+
diagonalMantissa(gtok.letter, Math.log10(galpha)), precision4) +
|
|
1737
|
+
gtok.letter + formatR0Arg(gbeta, precision4)
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
if (gapStr !== null) return gapStr
|
|
1743
|
+
|
|
1546
1744
|
// ── Check for repeated non-ω letters → advance to next letter ──
|
|
1547
1745
|
// AbAb→Ac, AcAc→Ad, ..., AzAz→Ba, BaBa→Bb, etc.
|
|
1548
1746
|
// Only applies when repeat count >= multiLetterRepeatThreshold
|
|
@@ -1553,19 +1751,20 @@ function formatOrdinal(num, precision, precision4) {
|
|
|
1553
1751
|
for (let i = 0; i < ordRows.length; i++) {
|
|
1554
1752
|
let row = ordRows[i]
|
|
1555
1753
|
if (row.length < 3) { allSameLetter = false; break }
|
|
1556
|
-
|
|
1557
|
-
|
|
1754
|
+
// The letter grid only names ordinals whose coefficients are ≤ 25 (the
|
|
1755
|
+
// top one ≤ 26); anything larger (ω*2+96, ω*99, ω²*100, …) is rounded UP
|
|
1756
|
+
// to the next nameable ordinal — ω*2+96 → ω*3 (Ca), ω*99 → ω² (Aaa),
|
|
1757
|
+
// ω²*100 → ω³ (Aaaa) — never clamped down, never wrapped.
|
|
1758
|
+
let g = snapCoeffsToGrid(row.slice(1))
|
|
1759
|
+
let diag = g[g.length - 1]
|
|
1760
|
+
let vals = g.slice(0, g.length - 1)
|
|
1558
1761
|
let nVals = vals.length
|
|
1559
1762
|
|
|
1560
1763
|
// Calculate height for this letter
|
|
1561
1764
|
let height = 0
|
|
1562
1765
|
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
|
|
1766
|
+
// 2-letter: Aa=23, Ab=24, ..., Az=48, Ba=49, ...
|
|
1767
|
+
height = 23 + (diag - 1) * 26 + vals[0]
|
|
1569
1768
|
} else {
|
|
1570
1769
|
// 3+ letters: need to calculate offset
|
|
1571
1770
|
// vals are stored in reverse letter order (e.g. "bc" -> [2,1]),
|
|
@@ -1612,18 +1811,18 @@ function formatOrdinal(num, precision, precision4) {
|
|
|
1612
1811
|
let letter = letterName(baseLetterHeight)
|
|
1613
1812
|
let tokAdv = letterTokenOf(baseLetterHeight + 1)
|
|
1614
1813
|
if (totalRepeat >= collapseAt && tokAdv.sym) {
|
|
1615
|
-
// Collapse to next letter — carried when it exceeds the limit
|
|
1616
|
-
//
|
|
1617
|
-
|
|
1618
|
-
|
|
1814
|
+
// Collapse to next letter — carried when it exceeds the limit.
|
|
1815
|
+
// !αAaβ takes the ω-EXPONENT as β (README: !αAaβ = ω^β + the
|
|
1816
|
+
// digits of α), not the r0 chain.
|
|
1817
|
+
return emitLetterToken(tokAdv, regularFormat(tokAdv.mant, precision4),
|
|
1818
|
+
r0Str, precision4)
|
|
1619
1819
|
}
|
|
1620
1820
|
if (totalRepeat >= collapseAt) {
|
|
1621
1821
|
// Collapse to next letter — the repeat count shifts the
|
|
1622
1822
|
// argument, so the mantissa keeps the [1,10) convention
|
|
1623
1823
|
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
1824
|
let newLetter = letterName(baseLetterHeight + 1)
|
|
1825
|
+
let mantVal = diagonalMantissa(newLetter, mantissa)
|
|
1627
1826
|
return regularFormat(mantVal, precision4) + newLetter + r0Str
|
|
1628
1827
|
} else if (totalRepeat > 1) {
|
|
1629
1828
|
// Repeated letters + chain: one token per application, the
|
|
@@ -1646,16 +1845,9 @@ function formatOrdinal(num, precision, precision4) {
|
|
|
1646
1845
|
return emitLetterToken(tokAdv, regularFormat(tokAdv.mant, precision4), betaStr, precision4)
|
|
1647
1846
|
}
|
|
1648
1847
|
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
1848
|
let newHeight = baseLetterHeight + 1
|
|
1658
1849
|
let letter = letterName(newHeight)
|
|
1850
|
+
let mantVal = diagonalMantissa(letter, mantissa)
|
|
1659
1851
|
return regularFormat(mantVal, precision4) + letter +
|
|
1660
1852
|
formatR0Arg(totalRepeat + 1, precision4)
|
|
1661
1853
|
} else if (totalRepeat > 1) {
|
|
@@ -1709,17 +1901,15 @@ function formatOrdinal(num, precision, precision4) {
|
|
|
1709
1901
|
if (row.length === 2) {
|
|
1710
1902
|
height = 23 // ω-level (Aa)
|
|
1711
1903
|
} else {
|
|
1712
|
-
|
|
1713
|
-
|
|
1904
|
+
// round the ordinal UP to the next one the letter grid can name
|
|
1905
|
+
// (ω*2+96 → ω*3 = Ca, ω*99 → ω² = Aaa, ω²*100 → ω³ = Aaaa)
|
|
1906
|
+
let g2 = snapCoeffsToGrid(row.slice(1))
|
|
1907
|
+
let diag = g2[g2.length - 1]
|
|
1908
|
+
let vals = g2.slice(0, g2.length - 1)
|
|
1714
1909
|
let nVals = vals.length
|
|
1715
1910
|
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
|
|
1911
|
+
// 2-letter: Aa=23, Ab=24, ..., Az=48, Ba=49, ...
|
|
1912
|
+
height = 23 + (diag - 1) * 26 + vals[0]
|
|
1723
1913
|
} else {
|
|
1724
1914
|
// vals are the CNF coefficients a1, a2, …, a(x-1) of
|
|
1725
1915
|
// ord = ω^(x-1)·ax + … + ω·a2 + a1 — little-endian, the same
|