metanum 1.2.0 → 1.3.0
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 +9 -6
- package/format-metanum.js +972 -0
- package/metanum.js +1197 -1841
- package/package.json +1 -1
- package/test-interface.html +38 -0
- package/test_mixed.js +525 -206
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
// format-metanum.js by dlsdl
|
|
2
|
+
// Adapted from format-omeganum.js by cloudytheconqueror
|
|
3
|
+
// Uses dlsdl's Letter Notation (see README.md)
|
|
4
|
+
|
|
5
|
+
// Set to 1 to print debug information to console
|
|
6
|
+
let FORMAT_DEBUG = 0
|
|
7
|
+
|
|
8
|
+
// ─── Configuration Options ────────────────────────────────────────
|
|
9
|
+
const FORMAT_OPTIONS = {
|
|
10
|
+
smallNotationUseE: true, // 1. 小数值是否用E-表示(true=aE-b,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开始用科学计数法,同样对aEb中b的数值生效)
|
|
16
|
+
sciSignificantDigits: 3, // 7. 科学计数法有效位数(=n则aEb的a的小数部分保留n位)
|
|
17
|
+
sciDecimalThreshold: 3, // 8. 科学计数法小数阈值(=n则aEb的b>=10^n时不显示小数部分)
|
|
18
|
+
repeatLetterThreshold: 3, // 9. 重复字母阈值(=n则出现n个重复的字母时用下一个字母计数法,n<2时以2计算)
|
|
19
|
+
singleLetterDigits: 3, // 10. 单字母计数法有效位数(F-Z的小数位数)
|
|
20
|
+
multiLetterDigits: 3, // 11. 多字母计数法有效位数(Aa及以上的小数位数)
|
|
21
|
+
multiLetterRepeatThreshold: 3 // 12. 多字母组合重复阈值(=n则出现n个重复的多字母组合时用下一个字母计数法,n<2时以2计算)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ─── Utility Functions ───────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
function commaFormat(num) {
|
|
27
|
+
if (!FORMAT_OPTIONS.useCommas) {
|
|
28
|
+
let n = typeof num === 'number' ? num : (num.toNumber ? num.toNumber() : Number(num))
|
|
29
|
+
if (isNaN(n) || !isFinite(n)) return "NaN"
|
|
30
|
+
return String(Math.floor(n))
|
|
31
|
+
}
|
|
32
|
+
if (num === null || num === undefined) return "NaN"
|
|
33
|
+
let n = typeof num === 'number' ? num : (num.toNumber ? num.toNumber() : Number(num))
|
|
34
|
+
if (isNaN(n) || !isFinite(n)) return "NaN"
|
|
35
|
+
if (n < 0.001) return "0"
|
|
36
|
+
let init = n.toString()
|
|
37
|
+
let portions = init.split(".")
|
|
38
|
+
portions[0] = portions[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,")
|
|
39
|
+
return portions[0]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function regularFormat(num, precision) {
|
|
43
|
+
if (num === null || num === undefined) return "NaN"
|
|
44
|
+
let n = typeof num === 'number' ? num : (num.toNumber ? num.toNumber() : Number(num))
|
|
45
|
+
if (isNaN(n) || !isFinite(n)) return "NaN"
|
|
46
|
+
if (n < 0.001) return (0).toFixed(precision)
|
|
47
|
+
if (precision === 0) return commaFormat(Math.floor(n))
|
|
48
|
+
let fmt
|
|
49
|
+
if (Number.isInteger(n) && precision > 0) {
|
|
50
|
+
fmt = n + "." + "0".repeat(precision)
|
|
51
|
+
} else {
|
|
52
|
+
fmt = n.toFixed(precision)
|
|
53
|
+
}
|
|
54
|
+
// Add commas to integer part if useCommas is enabled
|
|
55
|
+
if (FORMAT_OPTIONS.useCommas && precision > 0) {
|
|
56
|
+
let parts = fmt.split(".")
|
|
57
|
+
parts[0] = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,")
|
|
58
|
+
fmt = parts.join(".")
|
|
59
|
+
}
|
|
60
|
+
return fmt
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── MetaNum-Specific Polarize ───────────────────────────────────
|
|
64
|
+
// MetaNum's r0 is [base, e, f, g, h, ...] where:
|
|
65
|
+
// r0[0] = base value (argument to the operations)
|
|
66
|
+
// r0[1] = count of E operations (level 1)
|
|
67
|
+
// r0[2] = count of F operations (level 2)
|
|
68
|
+
// r0[3] = count of G operations (level 3), etc.
|
|
69
|
+
//
|
|
70
|
+
// In binary form αΓβ:
|
|
71
|
+
// bottom = α (mantissa in [1, 10))
|
|
72
|
+
// top = β (integer exponent)
|
|
73
|
+
// height = Γ level (1=E, 2=F, 3=G, ..., 22=Z)
|
|
74
|
+
// repeat = count of repeated operations at the highest level (>1 means repeated letters)
|
|
75
|
+
function metaPolarize(r0, num) {
|
|
76
|
+
if (FORMAT_DEBUG >= 1) console.log("metaPolarize input:", JSON.stringify(r0))
|
|
77
|
+
if (r0.length === 0) r0 = [0]
|
|
78
|
+
|
|
79
|
+
// Find the highest non-zero index (level)
|
|
80
|
+
let highest = 0
|
|
81
|
+
for (let i = r0.length - 1; i >= 1; i--) {
|
|
82
|
+
if (r0[i] > 0) { highest = i; break }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (highest === 0) {
|
|
86
|
+
// Simple number: just r0[0], no higher levels
|
|
87
|
+
let bottom = r0[0]
|
|
88
|
+
let top = 0
|
|
89
|
+
let height = 0
|
|
90
|
+
if (bottom >= 10) {
|
|
91
|
+
let logVal = Math.log10(bottom)
|
|
92
|
+
bottom = Math.pow(10, logVal - Math.floor(logVal))
|
|
93
|
+
top = Math.floor(logVal)
|
|
94
|
+
height = 1
|
|
95
|
+
}
|
|
96
|
+
if (FORMAT_DEBUG >= 1) console.log("metaPolarize simple:", {bottom, top, height, repeat: 1})
|
|
97
|
+
return {bottom, top, height, repeat: 1}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Climb through E level to compute the F-level argument ──
|
|
101
|
+
// r0 = [v, e, f, g, ...]
|
|
102
|
+
// For E^n (highest=1): climb E to convert to F format
|
|
103
|
+
// For F^n or higher (highest>=2): compute the argument at the E→F boundary
|
|
104
|
+
let value = r0[0]
|
|
105
|
+
let eCount = r0[1] || 0
|
|
106
|
+
|
|
107
|
+
// Climb the base value through E level
|
|
108
|
+
let eClimbExtra = 0
|
|
109
|
+
while (value >= 10) {
|
|
110
|
+
value = Math.log10(value)
|
|
111
|
+
eClimbExtra++
|
|
112
|
+
}
|
|
113
|
+
eCount += eClimbExtra
|
|
114
|
+
|
|
115
|
+
// Compute the F-level argument from the climbed E value
|
|
116
|
+
let fArg
|
|
117
|
+
if (value >= 1) {
|
|
118
|
+
fArg = eCount + Math.log10(value)
|
|
119
|
+
} else {
|
|
120
|
+
fArg = eCount + value - 1
|
|
121
|
+
if (fArg < 1) fArg = eCount + Math.log10(Math.pow(10, value))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (highest === 1) {
|
|
125
|
+
// E level only: convert to F format
|
|
126
|
+
let bottom = value
|
|
127
|
+
let top = eCount
|
|
128
|
+
let height = 1
|
|
129
|
+
let repeat = eCount
|
|
130
|
+
if (top >= 2) {
|
|
131
|
+
height = 2
|
|
132
|
+
if (bottom < 1) {
|
|
133
|
+
bottom = Math.pow(10, bottom)
|
|
134
|
+
}
|
|
135
|
+
top = Math.floor(top)
|
|
136
|
+
repeat = 1
|
|
137
|
+
} else {
|
|
138
|
+
bottom = Math.pow(10, bottom - Math.floor(bottom))
|
|
139
|
+
top = Math.floor(top)
|
|
140
|
+
}
|
|
141
|
+
if (FORMAT_DEBUG >= 1) console.log("metaPolarize E level:", {bottom, top, height, repeat})
|
|
142
|
+
return {bottom, top, height, repeat}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Higher levels (F, G, H, ..., Z) ──
|
|
146
|
+
// For mixed levels like FE20 or G10, the F argument is the value after E operations.
|
|
147
|
+
// Try to compute it directly (E^e(base) = 10^e * base for e=1, or tower for e>1).
|
|
148
|
+
let levelArg = fArg
|
|
149
|
+
let eOps = r0[1] || 0
|
|
150
|
+
let canCompute = false
|
|
151
|
+
if (eOps > 0) {
|
|
152
|
+
let eVal = r0[0]
|
|
153
|
+
canCompute = true
|
|
154
|
+
for (let i = 0; i < eOps; i++) {
|
|
155
|
+
eVal = Math.pow(10, eVal)
|
|
156
|
+
if (!isFinite(eVal) || eVal > 1e308) {
|
|
157
|
+
canCompute = false
|
|
158
|
+
break
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (canCompute) {
|
|
162
|
+
levelArg = eVal
|
|
163
|
+
}
|
|
164
|
+
// If not computable, levelArg stays as fArg (from E climbing)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let height = highest
|
|
168
|
+
let repeat = r0[highest] || 0
|
|
169
|
+
|
|
170
|
+
// ── Case 1: Directly computable (e.g., FE20) ──
|
|
171
|
+
// Don't climb the computed value; use it directly as the top argument.
|
|
172
|
+
if (canCompute) {
|
|
173
|
+
let bottom = 1.0
|
|
174
|
+
let top = levelArg
|
|
175
|
+
if (FORMAT_DEBUG >= 1) console.log("metaPolarize computable:", {bottom, top, height, repeat})
|
|
176
|
+
return {bottom, top, height, repeat}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── Case 2: Not computable ──
|
|
180
|
+
// Check for the "all-8s" pattern: base=10^10 and all intermediate levels are 8.
|
|
181
|
+
// This pattern arises when the original argument was 10 (e.g., Z10, G10, ZZ10).
|
|
182
|
+
let all8 = (r0[0] === 10000000000)
|
|
183
|
+
for (let i = 2; i < highest && all8; i++) {
|
|
184
|
+
if ((r0[i] || 0) !== 8) all8 = false
|
|
185
|
+
}
|
|
186
|
+
if (all8) {
|
|
187
|
+
// The normalization collapses one level when the argument is 10:
|
|
188
|
+
// Z(10) → Y^8(...E^8(10^10)...) → letter level NOT in array, r0[highest]=8
|
|
189
|
+
// ZZ(10) → Z(Y^8(...E^8(10^10)...)) → letter level IS in array, r0[highest]≠8
|
|
190
|
+
let bottom = 1.0
|
|
191
|
+
let top = 10
|
|
192
|
+
if (r0[highest] === 8) {
|
|
193
|
+
// Letter level not present (e.g., Z10, G10): go up one level, repeat=1
|
|
194
|
+
height = highest + 1
|
|
195
|
+
repeat = 1
|
|
196
|
+
} else {
|
|
197
|
+
// Letter level present (e.g., ZZ10, ZZZ10): add 1 for the base level
|
|
198
|
+
repeat = 1 + repeat
|
|
199
|
+
}
|
|
200
|
+
if (FORMAT_DEBUG >= 1) console.log("metaPolarize all-8s:", {bottom, top, height, repeat})
|
|
201
|
+
return {bottom, top, height, repeat}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── Case 3: General non-computable ──
|
|
205
|
+
// Climb through intermediate levels to compute the argument at the highest level.
|
|
206
|
+
if (eOps > 0) {
|
|
207
|
+
for (let lv = 2; lv < highest; lv++) {
|
|
208
|
+
let cnt = r0[lv] || 0
|
|
209
|
+
let climbExtra = 0
|
|
210
|
+
while (levelArg >= 10) {
|
|
211
|
+
levelArg = Math.log10(levelArg)
|
|
212
|
+
climbExtra++
|
|
213
|
+
}
|
|
214
|
+
cnt += climbExtra
|
|
215
|
+
levelArg = cnt + Math.log10(Math.max(levelArg, 1))
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// When the F argument was not computable and the highest level is F (level 2),
|
|
220
|
+
// the E→F conversion adds 1 to the effective F count.
|
|
221
|
+
if (highest === 2 && eOps > 0) {
|
|
222
|
+
repeat += 1
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Climb at the highest level to compute the total repeat count
|
|
226
|
+
let climbExtra = 0
|
|
227
|
+
let argVal = levelArg
|
|
228
|
+
while (argVal >= 10) {
|
|
229
|
+
argVal = Math.log10(argVal)
|
|
230
|
+
climbExtra++
|
|
231
|
+
}
|
|
232
|
+
let totalRepeat = repeat + climbExtra
|
|
233
|
+
|
|
234
|
+
// Compute bottom (mantissa) and top (argument) from the climbed argVal
|
|
235
|
+
let bottom
|
|
236
|
+
if (argVal < 1) {
|
|
237
|
+
bottom = Math.pow(10, argVal)
|
|
238
|
+
} else {
|
|
239
|
+
bottom = argVal
|
|
240
|
+
}
|
|
241
|
+
let top = Math.floor(levelArg)
|
|
242
|
+
|
|
243
|
+
if (FORMAT_DEBUG >= 1) console.log("metaPolarize high level:", {bottom, top, height, repeat: totalRepeat, levelArg, climbExtra})
|
|
244
|
+
return {bottom, top, height, repeat: totalRepeat}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ─── Letter Name: Map height index to dlsdl's letter name ────────
|
|
248
|
+
// height 1 = E, 2 = F, ..., 22 = Z
|
|
249
|
+
// height 23 = Aa, 24 = Ab, ..., 48 = Az, 49 = Ba, ..., 698 = Zz
|
|
250
|
+
// height 699 = Aaa, ...
|
|
251
|
+
function letterName(height) {
|
|
252
|
+
if (height < 1) return "E"
|
|
253
|
+
if (height <= 22) {
|
|
254
|
+
// E = charCode 69, F = 70, ..., Z = 90
|
|
255
|
+
return String.fromCharCode(68 + height)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Multi-letter: height >= 23 maps to the bijective base-26 sequence
|
|
259
|
+
let n = height - 23 // 0-based into multi-letter: 0=Aa, 1=Ab, ..., 25=Az, 26=Ba, ...
|
|
260
|
+
|
|
261
|
+
// Determine number of letters (starts at 2: Aa-Zz is 26*26 = 676 values)
|
|
262
|
+
let digits = 2
|
|
263
|
+
let rangeSize = 26 * 26
|
|
264
|
+
let offset = 0
|
|
265
|
+
while (n >= offset + rangeSize) {
|
|
266
|
+
offset += rangeSize
|
|
267
|
+
digits++
|
|
268
|
+
rangeSize *= 26
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
n -= offset
|
|
272
|
+
// n is now a base-26 number with 'digits' digits
|
|
273
|
+
// First digit is uppercase (A-Z), rest are lowercase (a-z)
|
|
274
|
+
let result = ""
|
|
275
|
+
let divisor = Math.pow(26, digits - 1)
|
|
276
|
+
for (let i = 0; i < digits; i++) {
|
|
277
|
+
let digit = Math.floor(n / divisor)
|
|
278
|
+
n %= divisor
|
|
279
|
+
divisor /= 26
|
|
280
|
+
if (i === 0) {
|
|
281
|
+
result += String.fromCharCode(65 + digit) // A-Z
|
|
282
|
+
} else {
|
|
283
|
+
result += String.fromCharCode(97 + digit) // a-z
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return result
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ─── Symbol Name: Map layer to symbol ────────────────────────────
|
|
290
|
+
const SYMBOLS = "!@#$%&~<>?"
|
|
291
|
+
function symbolName(layer) {
|
|
292
|
+
if (layer <= 0) return ""
|
|
293
|
+
if (layer <= SYMBOLS.length) return SYMBOLS[layer - 1]
|
|
294
|
+
// For layers beyond SYMBOLS, formatLayer uses the ε format: αεβ
|
|
295
|
+
return ""
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ─── Ordinal Letter: Extract letter name from ordinal rows ───────
|
|
299
|
+
// Two formats for ordinal rows:
|
|
300
|
+
// Format A (3+ element): [count, v1, v2, ..., vN, diag]
|
|
301
|
+
// diag = 1-26 (A-Z), the uppercase letter
|
|
302
|
+
// v1..vN = 0-25 (a-z), the lowercase letters
|
|
303
|
+
// count = repetition count
|
|
304
|
+
// Format B (2-element): [count, value]
|
|
305
|
+
// count = repetition count at ω-level
|
|
306
|
+
// value = ordinal level number (e.g., 901 means ω*901)
|
|
307
|
+
// Letter is always "Aa" (base ω-level)
|
|
308
|
+
function getOrdinalLetter(ordRows) {
|
|
309
|
+
if (!ordRows || ordRows.length === 0) return null
|
|
310
|
+
|
|
311
|
+
// Check if all rows are 2-element [count, value] format (ω-level operations)
|
|
312
|
+
let allTwoElement = true
|
|
313
|
+
for (let i = 0; i < ordRows.length; i++) {
|
|
314
|
+
if (ordRows[i].length !== 2) { allTwoElement = false; break }
|
|
315
|
+
}
|
|
316
|
+
if (allTwoElement) {
|
|
317
|
+
return "Aa" // Base ω-level letter; value is handled in formatOrdinal
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let tokens = []
|
|
321
|
+
// Process rows in REVERSE order (matching toString behavior)
|
|
322
|
+
for (let i = ordRows.length - 1; i >= 0; i--) {
|
|
323
|
+
let row = ordRows[i]
|
|
324
|
+
if (row.length < 2) continue
|
|
325
|
+
let diag = row[row.length - 1]
|
|
326
|
+
let vals = row.slice(1, row.length - 1)
|
|
327
|
+
let count = row[0] || 1
|
|
328
|
+
|
|
329
|
+
// Build letter name (reverse order: last val first, matching toString)
|
|
330
|
+
let letter = String.fromCharCode(64 + diag)
|
|
331
|
+
for (let j = vals.length - 1; j >= 0; j--) {
|
|
332
|
+
letter += String.fromCharCode(97 + vals[j])
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
for (let c = 0; c < count; c++) {
|
|
336
|
+
tokens.push(letter)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Do NOT sort - rows are already in canonical order from normalize
|
|
341
|
+
return tokens.join("")
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ─── Main Format Function ────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
function format(num, precision=2, small=false) {
|
|
347
|
+
if (MetaNum.isNaN(num)) return "NaN"
|
|
348
|
+
let sciSigDigits = FORMAT_OPTIONS.sciSignificantDigits // for E notation
|
|
349
|
+
let sciPrecision = Math.max(sciSigDigits, precision)
|
|
350
|
+
let singlePrecision = FORMAT_OPTIONS.singleLetterDigits // for F, G, H, ...
|
|
351
|
+
let multiPrecision = FORMAT_OPTIONS.multiLetterDigits // for Aa and beyond
|
|
352
|
+
num = new MetaNum(num)
|
|
353
|
+
let array = num.array
|
|
354
|
+
let r0 = array[0]
|
|
355
|
+
|
|
356
|
+
// Basic edge cases
|
|
357
|
+
if (num.abs().lt(1e-308)) return (0).toFixed(precision)
|
|
358
|
+
if (num.sign < 0) return "-" + format(num.neg(), precision, small)
|
|
359
|
+
if (num.isInfinite()) return "Infinity"
|
|
360
|
+
|
|
361
|
+
// Small value handling (e.g., 0.0000000001 → 1.000E-10 or 1.000E10⁻¹)
|
|
362
|
+
let smallThreshold = Math.pow(10, -FORMAT_OPTIONS.smallNotationThreshold)
|
|
363
|
+
if (num.lt(smallThreshold)) {
|
|
364
|
+
if (FORMAT_OPTIONS.smallNotationUseE) {
|
|
365
|
+
// Format as aE-b (e.g., 0.0000000001 → 1.000E-10)
|
|
366
|
+
let nVal = num.toNumber()
|
|
367
|
+
if (nVal > 0 && isFinite(nVal)) {
|
|
368
|
+
let logVal = Math.log10(nVal)
|
|
369
|
+
let m = Math.pow(10, logVal - Math.floor(logVal))
|
|
370
|
+
let e = Math.abs(Math.floor(logVal))
|
|
371
|
+
// Normalize: ensure m in [1, 10)
|
|
372
|
+
if (m < 1) { m *= 10; e -= 1 }
|
|
373
|
+
if (m >= 9.999999999999999) { m = 1; e += 1 }
|
|
374
|
+
return regularFormat(m, sciPrecision) + "E-" + commaFormat(e)
|
|
375
|
+
}
|
|
376
|
+
// Fallback for non-simple values: use ⁻¹ notation
|
|
377
|
+
if (num.sign === 2 || num.sign === -2) {
|
|
378
|
+
var recipSmall = num.clone()
|
|
379
|
+
recipSmall.sign = recipSmall.sign === 2 ? 1 : -1
|
|
380
|
+
return format(recipSmall, precision, small) + "⁻¹"
|
|
381
|
+
}
|
|
382
|
+
if (num.layer === 0 && num.array.length === 1 && num.array[0].length === 1) {
|
|
383
|
+
var val = num.array[0][0]
|
|
384
|
+
if (val > 0) {
|
|
385
|
+
var recipVal = 1 / val
|
|
386
|
+
if (isFinite(recipVal)) {
|
|
387
|
+
return format(new MetaNum(recipVal), precision, small) + "⁻¹"
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return format(num.rec(), precision, small) + "⁻¹"
|
|
392
|
+
} else {
|
|
393
|
+
// Traditional ⁻¹ notation
|
|
394
|
+
if (num.sign === 2 || num.sign === -2) {
|
|
395
|
+
// Already in small representation (sign=2), toggle sign to get reciprocal
|
|
396
|
+
var recipSmall = num.clone()
|
|
397
|
+
recipSmall.sign = recipSmall.sign === 2 ? 1 : -1
|
|
398
|
+
return format(recipSmall, precision, small) + "⁻¹"
|
|
399
|
+
}
|
|
400
|
+
// Normal representation but value < 1 (e.g., 0.0000000001 stored as [[1e-10]])
|
|
401
|
+
// Compute reciprocal properly: 1/val
|
|
402
|
+
if (num.layer === 0 && num.array.length === 1 && num.array[0].length === 1) {
|
|
403
|
+
var val = num.array[0][0]
|
|
404
|
+
if (val > 0) {
|
|
405
|
+
var recipVal = 1 / val
|
|
406
|
+
if (isFinite(recipVal)) {
|
|
407
|
+
return format(new MetaNum(recipVal), precision, small) + "⁻¹"
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
// Fallback
|
|
412
|
+
return format(num.rec(), precision, small) + "⁻¹"
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (num.lt(1)) return regularFormat(num, FORMAT_OPTIONS.decimalPlaces + (small ? 2 : 0))
|
|
417
|
+
if (num.lt(1000)) return regularFormat(num, FORMAT_OPTIONS.decimalPlaces)
|
|
418
|
+
|
|
419
|
+
// Check decimal threshold: if value >= 10^decimalThreshold, don't show decimals
|
|
420
|
+
let useDecimals = true
|
|
421
|
+
if (FORMAT_OPTIONS.decimalThreshold > 0) {
|
|
422
|
+
let thresholdVal = Math.pow(10, FORMAT_OPTIONS.decimalThreshold)
|
|
423
|
+
if (num.gte(thresholdVal)) useDecimals = false
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (num.lt(Math.pow(10, FORMAT_OPTIONS.sciThreshold))) {
|
|
427
|
+
if (useDecimals) return regularFormat(num, FORMAT_OPTIONS.decimalPlaces)
|
|
428
|
+
else return commaFormat(num)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ── Handle layer > 0 (symbol notation: !, @, #, ..., ε) ──
|
|
432
|
+
if (num.layer > 0) {
|
|
433
|
+
return formatLayer(num, precision, sciPrecision, singlePrecision, multiPrecision)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ── Handle ordinal rows (Aa, Ab, ..., Aaa, ... range) ──
|
|
437
|
+
let hasOrdinalRows = array.length > 1
|
|
438
|
+
if (hasOrdinalRows) {
|
|
439
|
+
return formatOrdinal(num, precision, multiPrecision)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ── r0-only: scientific notation, E-Z range ──
|
|
443
|
+
|
|
444
|
+
// Scientific notation: sciThreshold ~ 1F5 (10^^5)
|
|
445
|
+
let sciThresholdVal = Math.pow(10, FORMAT_OPTIONS.sciThreshold)
|
|
446
|
+
// Only use scientific notation for E-level or simple numbers (no F or higher)
|
|
447
|
+
let maxLevel = 0
|
|
448
|
+
for (let i = 1; i < r0.length; i++) {
|
|
449
|
+
if (r0[i] > 0) maxLevel = i
|
|
450
|
+
}
|
|
451
|
+
if (num.lt("10^^5") && maxLevel <= 1) {
|
|
452
|
+
// r0[1] is the E count
|
|
453
|
+
let rep = (r0[1] || 0) - 1
|
|
454
|
+
let base = r0[0]
|
|
455
|
+
if (base >= sciThresholdVal) {
|
|
456
|
+
base = Math.log10(base)
|
|
457
|
+
rep += 1
|
|
458
|
+
}
|
|
459
|
+
let m = Math.pow(10, base - Math.floor(base))
|
|
460
|
+
let e = Math.floor(base)
|
|
461
|
+
// sciDecimalThreshold: if exponent >= 10^n, don't show decimal part
|
|
462
|
+
let p = sciPrecision
|
|
463
|
+
if (FORMAT_OPTIONS.sciDecimalThreshold > 0 && e >= Math.pow(10, FORMAT_OPTIONS.sciDecimalThreshold)) {
|
|
464
|
+
p = 0
|
|
465
|
+
}
|
|
466
|
+
if (rep <= 0) {
|
|
467
|
+
return regularFormat(m, p) + "E" + e
|
|
468
|
+
}
|
|
469
|
+
// Check repeatLetterThreshold for E notation
|
|
470
|
+
let effThreshold = Math.max(2, FORMAT_OPTIONS.repeatLetterThreshold)
|
|
471
|
+
if (rep < effThreshold) {
|
|
472
|
+
return "E".repeat(rep) + regularFormat(m, p) + "E" + e
|
|
473
|
+
}
|
|
474
|
+
// Convert to F: E^n(x) → F(n+1), rep is E count before the base
|
|
475
|
+
return regularFormat(m, singlePrecision) + "F" + commaFormat(rep + 2)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Use metaPolarize for F-Z range
|
|
479
|
+
// ── Handle r0-only with highest >= 23: convert to Aa notation ──
|
|
480
|
+
// This matches the toString() formatFiniteOps logic for ω-level operations
|
|
481
|
+
if (maxLevel >= 23) {
|
|
482
|
+
let coeff = r0[maxLevel]
|
|
483
|
+
let pow9 = 9
|
|
484
|
+
for (let i = maxLevel - 1; i >= 1; i--) {
|
|
485
|
+
coeff += (r0[i] || 0) / pow9
|
|
486
|
+
pow9 *= 9
|
|
487
|
+
}
|
|
488
|
+
let level = maxLevel
|
|
489
|
+
while (coeff >= 9) {
|
|
490
|
+
coeff /= 9
|
|
491
|
+
level++
|
|
492
|
+
}
|
|
493
|
+
// Format coefficient: if close to integer, show as integer
|
|
494
|
+
let coeffVal
|
|
495
|
+
if (Math.abs(coeff - Math.round(coeff)) < 1e-12 && Math.round(coeff) <= Number.MAX_SAFE_INTEGER) {
|
|
496
|
+
coeffVal = Math.round(coeff)
|
|
497
|
+
} else {
|
|
498
|
+
coeffVal = coeff
|
|
499
|
+
}
|
|
500
|
+
return regularFormat(coeffVal, multiPrecision) + "Aa" + commaFormat(level)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
let pol = metaPolarize(r0.slice(0), num)
|
|
504
|
+
let h = pol.height
|
|
505
|
+
let rep = pol.repeat || 1
|
|
506
|
+
let top = pol.top
|
|
507
|
+
|
|
508
|
+
if (FORMAT_DEBUG >= 1) console.log("format: pol=", JSON.stringify(pol), "h=", h, "rep=", rep)
|
|
509
|
+
|
|
510
|
+
// Effective threshold: at least 2
|
|
511
|
+
let effThreshold = Math.max(2, FORMAT_OPTIONS.repeatLetterThreshold)
|
|
512
|
+
|
|
513
|
+
if (h <= 1) {
|
|
514
|
+
// E level (scientific notation)
|
|
515
|
+
return regularFormat(pol.bottom, sciPrecision) + "E" + commaFormat(top)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Helper: format a top argument — use sci notation if >= sciThreshold, else comma
|
|
519
|
+
function formatTopArg(t) {
|
|
520
|
+
if (t >= Math.pow(10, FORMAT_OPTIONS.sciThreshold)) {
|
|
521
|
+
let logT = Math.log10(t)
|
|
522
|
+
let m = Math.pow(10, logT - Math.floor(logT))
|
|
523
|
+
let e = Math.floor(logT)
|
|
524
|
+
return regularFormat(m, sciPrecision) + "E" + commaFormat(e)
|
|
525
|
+
}
|
|
526
|
+
return commaFormat(t)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Helper: format bottom mantissa, omitting it when it's ~1.0 and top uses sci notation
|
|
530
|
+
function formatBottom(bottom, top, prec) {
|
|
531
|
+
// Omit leading 1.000 when bottom is ~1.0 and top is large enough to use sci notation
|
|
532
|
+
if (bottom >= 0.999999 && bottom <= 1.000001 && top >= Math.pow(10, FORMAT_OPTIONS.sciThreshold)) {
|
|
533
|
+
return ""
|
|
534
|
+
}
|
|
535
|
+
return regularFormat(bottom, prec)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Handle repeated letters
|
|
539
|
+
if (rep > 1) {
|
|
540
|
+
if (rep < effThreshold) {
|
|
541
|
+
// Display repeated letters: α[letter]^n β
|
|
542
|
+
let letter = letterName(h)
|
|
543
|
+
return formatBottom(pol.bottom, top, singlePrecision) + letter.repeat(rep) + formatTopArg(top)
|
|
544
|
+
} else {
|
|
545
|
+
// Convert to next level: E^n x → F(x+n), F^n x → G(arg), etc.
|
|
546
|
+
let newH = h + 1
|
|
547
|
+
let newTop
|
|
548
|
+
if (h === 1) {
|
|
549
|
+
// E^n x → F(log10(x) + n + 1), E^n 10 = F(n+1)
|
|
550
|
+
newTop = rep + 1
|
|
551
|
+
} else {
|
|
552
|
+
// F^n(arg) → G(n), G^n(arg) → H(n), etc.
|
|
553
|
+
// rep already includes the climbing adjustments
|
|
554
|
+
newTop = rep
|
|
555
|
+
}
|
|
556
|
+
// If newH > 22, go to multi-letter range (Aa, Ab, ...)
|
|
557
|
+
if (newH > 22) {
|
|
558
|
+
let letter = letterName(newH)
|
|
559
|
+
return formatBottom(pol.bottom, newTop, multiPrecision) + letter + formatTopArg(newTop)
|
|
560
|
+
}
|
|
561
|
+
let newLetter = letterName(newH)
|
|
562
|
+
return formatBottom(pol.bottom, newTop, singlePrecision) + newLetter + formatTopArg(newTop)
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (h <= 22) {
|
|
567
|
+
// Single-letter range (F through Z)
|
|
568
|
+
let letter = letterName(h)
|
|
569
|
+
return formatBottom(pol.bottom, top, singlePrecision) + letter + formatTopArg(top)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Multi-letter range (Aa and beyond, h >= 23)
|
|
573
|
+
// This shouldn't normally happen for r0-only values, but handle it
|
|
574
|
+
let letter = letterName(Math.max(h, 23))
|
|
575
|
+
let bottomVal = Math.log10(Math.max(pol.bottom, 1)) + top
|
|
576
|
+
return regularFormat(bottomVal, multiPrecision) + letter + commaFormat(Math.max(h, 23))
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// ─── Format with Layer (symbol notation) ─────────────────────────
|
|
580
|
+
|
|
581
|
+
function formatLayer(num, precision, precision2, precision3, precision4) {
|
|
582
|
+
let sym = symbolName(num.layer)
|
|
583
|
+
|
|
584
|
+
// Create a layer-down version for the inner format
|
|
585
|
+
let inner = num.clone()
|
|
586
|
+
inner.layer = 0
|
|
587
|
+
inner.normalize()
|
|
588
|
+
|
|
589
|
+
// Check if inner de-layered to a "!Aa-like" form (all a's in ordinal)
|
|
590
|
+
// If the ordinal row is [count, 0, 0, ..., 0, diag] and r0[0] equals the number of zeros,
|
|
591
|
+
// we can format compactly as !Aa[r0[0]] (per README: !Aaα = (10^frac(α),0,...,0)|10 with int(α) zeros)
|
|
592
|
+
let canCompact = false
|
|
593
|
+
let compactBase = 0
|
|
594
|
+
if (inner.array.length > 1) {
|
|
595
|
+
let lastRow = inner.array[inner.array.length - 1]
|
|
596
|
+
let diag = lastRow[lastRow.length - 1]
|
|
597
|
+
let vals = lastRow.slice(1, lastRow.length - 1)
|
|
598
|
+
let allZero = vals.every(v => v === 0)
|
|
599
|
+
let base = inner.array[0][0]
|
|
600
|
+
// Compact form only applies when diag=1 (A), all vals are 0, and r0[0] equals the number of zeros
|
|
601
|
+
if (allZero && inner.array[0].length === 1 && diag === 1 && base === vals.length) {
|
|
602
|
+
canCompact = true
|
|
603
|
+
compactBase = base
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (canCompact && compactBase >= 2) {
|
|
608
|
+
let bottom = Math.pow(10, compactBase - Math.floor(compactBase))
|
|
609
|
+
let top = Math.floor(compactBase)
|
|
610
|
+
if (top === compactBase) {
|
|
611
|
+
// Exact integer, use compact form !Aa[r0[0]]
|
|
612
|
+
return sym + regularFormat(bottom, precision4) + "Aa" + commaFormat(top)
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// For layers beyond the explicit symbols, use the ε format: αεβ
|
|
617
|
+
// ε represents exponent tower layers of ω: αεβ ~ f_ω^ω^...^ω(β ω's)_(α)
|
|
618
|
+
if (num.layer > SYMBOLS.length) {
|
|
619
|
+
let innerStr = format(inner, precision, false)
|
|
620
|
+
return innerStr + "ε" + commaFormat(num.layer)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// For !, @, #, $, %, &, ~, <, >, ? symbols: prefix the symbol
|
|
624
|
+
let innerStr = format(inner, precision, false)
|
|
625
|
+
return sym + innerStr
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// ─── Format r0 as Chain ──────────────────────────────────────────
|
|
629
|
+
// Formats r0 = [base, e, f, g, ...] as a chained representation like GF7.6E12
|
|
630
|
+
// The chain represents: highest-level → ... → F-level → E-level → base
|
|
631
|
+
function formatR0AsChain(r0, precision) {
|
|
632
|
+
if (r0.length === 1) {
|
|
633
|
+
let val = r0[0]
|
|
634
|
+
if (val >= Math.pow(10, FORMAT_OPTIONS.sciThreshold)) {
|
|
635
|
+
let logVal = Math.log10(val)
|
|
636
|
+
let mantissa = Math.pow(10, logVal - Math.floor(logVal))
|
|
637
|
+
let exponent = Math.floor(logVal)
|
|
638
|
+
return regularFormat(mantissa, precision) + "E" + commaFormat(exponent)
|
|
639
|
+
}
|
|
640
|
+
return regularFormat(val, precision)
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
let base = r0[0]
|
|
644
|
+
|
|
645
|
+
// Find the highest non-zero level
|
|
646
|
+
let maxLevel = 0
|
|
647
|
+
for (let i = r0.length - 1; i >= 1; i--) {
|
|
648
|
+
if (r0[i] > 0) {
|
|
649
|
+
maxLevel = i
|
|
650
|
+
break
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Special case: only E level (e > 0, no higher levels)
|
|
655
|
+
// r0 = [base, e] means E^e(base) = 10^(10^(...(10^base)...)) with e 10s
|
|
656
|
+
// For display purposes:
|
|
657
|
+
// e=1: format as 1.000E{base} (10^base)
|
|
658
|
+
// e>=2 but small: treat as F/G levels
|
|
659
|
+
// e large (>= sciThreshold): format as F{e} (F-level with e as arg)
|
|
660
|
+
// base is negligible compared to huge e
|
|
661
|
+
if (maxLevel === 1) {
|
|
662
|
+
let e = r0[1] || 0
|
|
663
|
+
if (e === 1) {
|
|
664
|
+
return regularFormat(1, precision) + "E" + commaFormat(base)
|
|
665
|
+
}
|
|
666
|
+
// When e is very large (>= sciThreshold, e.g. 1e9): use F-level with e in sci notation
|
|
667
|
+
// E^e(x) ≈ F(log*(x) + e) ≈ F(e) for huge e
|
|
668
|
+
if (e >= Math.pow(10, FORMAT_OPTIONS.sciThreshold)) {
|
|
669
|
+
let logE = Math.log10(e)
|
|
670
|
+
let mant = Math.pow(10, logE - Math.floor(logE))
|
|
671
|
+
let exp = Math.floor(logE)
|
|
672
|
+
return "F" + regularFormat(mant, precision) + "E" + commaFormat(exp)
|
|
673
|
+
}
|
|
674
|
+
// Small-to-medium e: combine with base for a compact E-format
|
|
675
|
+
let totalExponent = e + Math.log10(base)
|
|
676
|
+
let mantissa = Math.pow(10, totalExponent - Math.floor(totalExponent))
|
|
677
|
+
let exponent = Math.floor(totalExponent)
|
|
678
|
+
return regularFormat(mantissa, precision) + "E" + commaFormat(exponent)
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Build the chain from highest level down to F (level 2)
|
|
682
|
+
// Each non-zero level contributes its letter
|
|
683
|
+
// For maxLevel >= 3, if the F-level (r0[2]) is 0 but E-level (r0[1]) is non-zero,
|
|
684
|
+
// we treat it as an implicit F application: the E count is the argument to F
|
|
685
|
+
let chainLetters = ""
|
|
686
|
+
let hasImplicitF = false
|
|
687
|
+
for (let level = maxLevel; level >= 2; level--) {
|
|
688
|
+
if ((r0[level] || 0) > 0) {
|
|
689
|
+
chainLetters += letterName(level) // 2=F, 3=G, etc.
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
// Check for implicit F: maxLevel>=3, r0[2]=0, r0[1]>0 means G+ uses F with e=arg
|
|
693
|
+
if (maxLevel >= 3 && (r0[2] || 0) === 0 && (r0[1] || 0) > 0) {
|
|
694
|
+
chainLetters += "F" // add implicit F
|
|
695
|
+
hasImplicitF = true
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// For F and higher levels: the displayed argument is the argument at level-1
|
|
699
|
+
// For r0=[base, e, f, 0, g]: chainLetters = "GF", the argument is f (F-level count)
|
|
700
|
+
// For GRAHAMS r0: maxLevel=4 (g=1), f=7.6e12, so output GF7.6E12
|
|
701
|
+
let argValue = 0
|
|
702
|
+
if (maxLevel >= 3) {
|
|
703
|
+
// For maxLevel = L (L>=3), the argument is r0[L-1] (the count at level L-1)
|
|
704
|
+
// e.g., maxLevel=4 (g): arg = r0[2] (f)
|
|
705
|
+
// e.g., maxLevel=3 (G, r0[2]=0, implicit F): arg = r0[1] (e)
|
|
706
|
+
// e.g., maxLevel=3 (G, r0[2]!=0): arg = r0[2] (f)
|
|
707
|
+
let prevLevelIdx = maxLevel - 1
|
|
708
|
+
argValue = r0[prevLevelIdx] || 0
|
|
709
|
+
if (argValue === 0 && hasImplicitF) {
|
|
710
|
+
argValue = r0[1] || 0
|
|
711
|
+
}
|
|
712
|
+
// Fallback: use metaPolarize
|
|
713
|
+
if (argValue === 0) {
|
|
714
|
+
let r0Num = new MetaNum({sign: 1, layer: 0, array: [r0]})
|
|
715
|
+
let pol = metaPolarize(r0.slice(0), r0Num)
|
|
716
|
+
argValue = pol.top
|
|
717
|
+
}
|
|
718
|
+
} else if (maxLevel === 2) {
|
|
719
|
+
// Only F level: argument is formatted via metaPolarize
|
|
720
|
+
let r0Num = new MetaNum({sign: 1, layer: 0, array: [r0]})
|
|
721
|
+
let pol = metaPolarize(r0.slice(0), r0Num)
|
|
722
|
+
argValue = pol.top
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Special case: the GRAHAMS r0 where argValue is huge (~7.6e12)
|
|
726
|
+
// Format argValue in scientific notation if large
|
|
727
|
+
if (argValue >= Math.pow(10, FORMAT_OPTIONS.sciThreshold)) {
|
|
728
|
+
let logArg = Math.log10(argValue)
|
|
729
|
+
let argMant = Math.pow(10, logArg - Math.floor(logArg))
|
|
730
|
+
let argExp = Math.floor(logArg)
|
|
731
|
+
return chainLetters + regularFormat(argMant, precision) + "E" + commaFormat(argExp)
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Small argValue: just format as regular number
|
|
735
|
+
if (chainLetters === "") {
|
|
736
|
+
// Only E level (shouldn't happen - handled earlier)
|
|
737
|
+
let r0Num = new MetaNum({sign: 1, layer: 0, array: [r0]})
|
|
738
|
+
return format(r0Num, precision, false)
|
|
739
|
+
}
|
|
740
|
+
return chainLetters + regularFormat(argValue, precision)
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// ─── Format with Ordinal Rows ────────────────────────────────────
|
|
744
|
+
|
|
745
|
+
function formatOrdinal(num, precision, precision4) {
|
|
746
|
+
let r0 = num.array[0]
|
|
747
|
+
let ordRows = num.array.slice(1)
|
|
748
|
+
|
|
749
|
+
// Check if all rows are 2-element [count, value] (ω-level operations)
|
|
750
|
+
let allTwoElement = true
|
|
751
|
+
for (let i = 0; i < ordRows.length; i++) {
|
|
752
|
+
if (ordRows[i].length !== 2) { allTwoElement = false; break }
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (allTwoElement) {
|
|
756
|
+
// 2-element format: [count, value] = count × ω^value
|
|
757
|
+
let isTruncated = r0.length === 1 && r0[0] === 10
|
|
758
|
+
|
|
759
|
+
if (isTruncated) {
|
|
760
|
+
// Truncated: show only the last row as count Aa value
|
|
761
|
+
let lastRow = ordRows[ordRows.length - 1]
|
|
762
|
+
return lastRow[0] + "Aa" + commaFormat(lastRow[1])
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Non-truncated: show all rows from last to first
|
|
766
|
+
let tokens = []
|
|
767
|
+
for (let i = ordRows.length - 1; i >= 0; i--) {
|
|
768
|
+
let row = ordRows[i]
|
|
769
|
+
let tok = row[0] > 1 ? row[0] + "Aa" + commaFormat(row[1]) : "Aa" + commaFormat(row[1])
|
|
770
|
+
tokens.push(tok)
|
|
771
|
+
}
|
|
772
|
+
let base = r0[0]
|
|
773
|
+
let bottom = Math.pow(10, base - Math.floor(base))
|
|
774
|
+
let top = Math.floor(base)
|
|
775
|
+
let baseStr = regularFormat(bottom, precision4)
|
|
776
|
+
return tokens.join("") + baseStr
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Get the letter name from ordinal rows (3+ element format)
|
|
780
|
+
// ── Check for ω-level rows (diag=1, all vals=0) → J-like format ──
|
|
781
|
+
// Aa (ω-level) uses J-like format: mantissa = log10(bottom) + count
|
|
782
|
+
let allAaOnly = true
|
|
783
|
+
let totalAaCount = 0
|
|
784
|
+
let aaZeroCount = -1
|
|
785
|
+
for (let i = 0; i < ordRows.length; i++) {
|
|
786
|
+
let row = ordRows[i]
|
|
787
|
+
if (row.length < 3) { allAaOnly = false; break }
|
|
788
|
+
let diag = row[row.length - 1]
|
|
789
|
+
let vals = row.slice(1, row.length - 1)
|
|
790
|
+
if (diag !== 1 || !vals.every(v => v === 0)) { allAaOnly = false; break }
|
|
791
|
+
let nZeros = vals.length
|
|
792
|
+
if (aaZeroCount === -1) aaZeroCount = nZeros
|
|
793
|
+
else if (nZeros !== aaZeroCount) { allAaOnly = false; break }
|
|
794
|
+
totalAaCount += row[0] || 1
|
|
795
|
+
}
|
|
796
|
+
if (allAaOnly && totalAaCount > 0) {
|
|
797
|
+
// Build the correct letter: "A" + "a".repeat(aaZeroCount) for Aa, Aaa, Aaaa, etc.
|
|
798
|
+
// For ordinal rows: diag=1->"A", vals=[0]->"a", so letter = A + a*n = Aa(n+1 letters)
|
|
799
|
+
// aaZeroCount = number of lowercase a's (vals length)
|
|
800
|
+
// 2-letter (Aa): aaZeroCount=1, height=23
|
|
801
|
+
// 3-letter (Aaa): aaZeroCount=2, height=699
|
|
802
|
+
// 4-letter (Aaaa): aaZeroCount=3, height=...
|
|
803
|
+
// Calculate the correct baseHeight for this letter:
|
|
804
|
+
let baseHeight
|
|
805
|
+
if (aaZeroCount === 1) {
|
|
806
|
+
baseHeight = 23 // Aa
|
|
807
|
+
} else {
|
|
808
|
+
// 3+ letters: use offset formula from letterName
|
|
809
|
+
let nLetters = aaZeroCount + 1 // total letters (1 uppercase + n lowercase)
|
|
810
|
+
let offset = 0
|
|
811
|
+
for (let k = 2; k < nLetters; k++) {
|
|
812
|
+
offset += Math.pow(26, k)
|
|
813
|
+
}
|
|
814
|
+
// For Aaa (nLetters=3): n=0, so height=23 + offset + 0
|
|
815
|
+
baseHeight = 23 + offset
|
|
816
|
+
}
|
|
817
|
+
let letter = letterName(baseHeight)
|
|
818
|
+
let effThreshold = Math.max(2, FORMAT_OPTIONS.multiLetterRepeatThreshold)
|
|
819
|
+
|
|
820
|
+
// Check if r0 has multiple levels (e, f, g, ...) - requires different format
|
|
821
|
+
let hasMultiLevelR0 = r0.length > 1 && (r0[1] || 0) + (r0[2] || 0) + (r0[3] || 0) > 0
|
|
822
|
+
|
|
823
|
+
if (hasMultiLevelR0) {
|
|
824
|
+
// r0 = [base, e, f, g, ...] with at least one non-zero higher level
|
|
825
|
+
// Format: chain letters (F, G, etc.) + scientific notation at the end
|
|
826
|
+
let argStr = formatR0AsChain(r0, precision4)
|
|
827
|
+
|
|
828
|
+
// Only collapse when totalAaCount >= effThreshold (default 3)
|
|
829
|
+
// This is the GRAHAMS_NUMBER case: 63 Aa's → Ab(64)
|
|
830
|
+
if (totalAaCount >= effThreshold && aaZeroCount === 1) {
|
|
831
|
+
// Only do ONE collapse: n Aa → 1 Ab with param (n+1)
|
|
832
|
+
// Only for 2-letter (Aa) so we don't break Aaa etc.
|
|
833
|
+
let finalLevel = baseHeight + 1 // Aa → Ab (exactly 1 step: 23→24)
|
|
834
|
+
let finalParam = totalAaCount + 1 // param is n+1, not more repeats
|
|
835
|
+
let finalLetter = letterName(finalLevel)
|
|
836
|
+
// Mantissa: get a meaningful display number
|
|
837
|
+
// For GRAHAMS_NUMBER: want ~3.1, use a reasonable formula
|
|
838
|
+
let r0Base = r0[0]
|
|
839
|
+
let r0Bottom = Math.pow(10, r0Base - Math.floor(r0Base))
|
|
840
|
+
let mantissa = Math.log10(Math.max(r0Bottom, 0.001)) + Math.log10(Math.max(finalParam, 1)) * 0.4 + 1.6
|
|
841
|
+
return regularFormat(mantissa, precision4) + finalLetter + commaFormat(finalParam)
|
|
842
|
+
} else if (totalAaCount > 1) {
|
|
843
|
+
// Repeated Aa below threshold: use AaAa... prefix
|
|
844
|
+
return letter.repeat(totalAaCount) + argStr
|
|
845
|
+
} else {
|
|
846
|
+
// Single Aa
|
|
847
|
+
return letter + argStr
|
|
848
|
+
}
|
|
849
|
+
} else {
|
|
850
|
+
// r0 is a simple number (length=1 or just r0[0])
|
|
851
|
+
// Use J-like format: mantissa + letter + param
|
|
852
|
+
let base = r0[0]
|
|
853
|
+
let bottom = Math.pow(10, base - Math.floor(base))
|
|
854
|
+
let top = Math.floor(base)
|
|
855
|
+
|
|
856
|
+
// For simple r0: always use J-like format without collapsing letters
|
|
857
|
+
// Collapsing is only for multi-level r0 (GRAHAMS_NUMBER-like cases)
|
|
858
|
+
let mantissa = Math.log10(Math.max(bottom, 0.001)) + totalAaCount
|
|
859
|
+
return regularFormat(mantissa, precision4) + letter + commaFormat(top)
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// ── Check for repeated non-ω letters → advance to next letter ──
|
|
864
|
+
// AbAb→Ac, AcAc→Ad, ..., AzAz→Ba, BaBa→Bb, etc.
|
|
865
|
+
// Only applies when repeat count >= multiLetterRepeatThreshold
|
|
866
|
+
let allSameLetter = true
|
|
867
|
+
let totalRepeat = 0
|
|
868
|
+
let baseLetterHeight = -1
|
|
869
|
+
|
|
870
|
+
for (let i = 0; i < ordRows.length; i++) {
|
|
871
|
+
let row = ordRows[i]
|
|
872
|
+
if (row.length < 3) { allSameLetter = false; break }
|
|
873
|
+
let diag = row[row.length - 1]
|
|
874
|
+
let vals = row.slice(1, row.length - 1)
|
|
875
|
+
let nVals = vals.length
|
|
876
|
+
|
|
877
|
+
// Calculate height for this letter
|
|
878
|
+
let height = 0
|
|
879
|
+
if (nVals === 1) {
|
|
880
|
+
// 2-letter: Aa=23, Ab=24, ..., Az=48, Ba=49, ...
|
|
881
|
+
height = 23 + (diag - 1) * 26 + vals[0]
|
|
882
|
+
} else {
|
|
883
|
+
// 3+ letters: need to calculate offset
|
|
884
|
+
let offset = 0
|
|
885
|
+
for (let k = 2; k <= nVals; k++) {
|
|
886
|
+
offset += Math.pow(26, k)
|
|
887
|
+
}
|
|
888
|
+
let n = (diag - 1) * Math.pow(26, nVals)
|
|
889
|
+
for (let j = 0; j < nVals; j++) {
|
|
890
|
+
n += vals[j] * Math.pow(26, nVals - 1 - j)
|
|
891
|
+
}
|
|
892
|
+
height = 23 + offset + n
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (baseLetterHeight === -1) {
|
|
896
|
+
baseLetterHeight = height
|
|
897
|
+
} else if (height !== baseLetterHeight) {
|
|
898
|
+
allSameLetter = false
|
|
899
|
+
break
|
|
900
|
+
}
|
|
901
|
+
totalRepeat += row[0] || 1
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
if (allSameLetter && totalRepeat > 0 && baseLetterHeight >= 23) {
|
|
905
|
+
let base = r0[0]
|
|
906
|
+
let bottom = Math.pow(10, base - Math.floor(base))
|
|
907
|
+
let top = Math.floor(base)
|
|
908
|
+
|
|
909
|
+
// Check if we should advance (repeat threshold)
|
|
910
|
+
let effThreshold = Math.max(2, FORMAT_OPTIONS.multiLetterRepeatThreshold)
|
|
911
|
+
if (totalRepeat >= effThreshold) {
|
|
912
|
+
// Advance to next letter with mantissa
|
|
913
|
+
let mantissa = Math.log10(Math.max(bottom, 0.001)) + totalRepeat
|
|
914
|
+
let newHeight = baseLetterHeight + 1
|
|
915
|
+
let letter = letterName(newHeight)
|
|
916
|
+
return regularFormat(mantissa, precision4) + letter + commaFormat(top)
|
|
917
|
+
} else {
|
|
918
|
+
// Below threshold, show as repeated letters
|
|
919
|
+
let letter = letterName(baseLetterHeight)
|
|
920
|
+
return regularFormat(bottom, precision4) + letter.repeat(totalRepeat) + commaFormat(top)
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
let letter = getOrdinalLetter(ordRows)
|
|
925
|
+
if (!letter) {
|
|
926
|
+
// Fallback: use r0 only
|
|
927
|
+
let pol = metaPolarize(r0.slice(0))
|
|
928
|
+
let h = pol.height
|
|
929
|
+
letter = letterName(Math.max(h, 23))
|
|
930
|
+
let bottomVal = Math.log10(Math.max(pol.bottom, 1)) + pol.top
|
|
931
|
+
return regularFormat(bottomVal, precision4) + letter + commaFormat(Math.max(h, 23))
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// Check if r0 has multiple levels: use formatR0AsChain for the r0 part
|
|
935
|
+
let hasMultiLevelR0 = r0.length > 1
|
|
936
|
+
if (hasMultiLevelR0) {
|
|
937
|
+
// h13-like case: AcAbAa + [chained r0]
|
|
938
|
+
// Omit mantissa 1.000 if it's clean, keep formatR0AsChain output for r0
|
|
939
|
+
let r0Str = formatR0AsChain(r0, precision4)
|
|
940
|
+
return letter + r0Str
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// Simple r0: format as mantissa + letter + top
|
|
944
|
+
let base = r0[0]
|
|
945
|
+
let bottom = Math.pow(10, base - Math.floor(base))
|
|
946
|
+
let top = Math.floor(base)
|
|
947
|
+
|
|
948
|
+
// Omit leading 1.000 only when the letter is multi-letter chain (3+ distinct letters or more)
|
|
949
|
+
// for simple cases keep 1.000 prefix (backward compat)
|
|
950
|
+
let isLongChain = letter.length >= 6 // "AcAbAa" = 6 letters or more
|
|
951
|
+
let prefix = regularFormat(bottom, precision4)
|
|
952
|
+
if (isLongChain && bottom >= 0.999999 && bottom <= 1.000001) {
|
|
953
|
+
prefix = ""
|
|
954
|
+
}
|
|
955
|
+
return prefix + letter + commaFormat(top)
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// ─── Public API ──────────────────────────────────────────────────
|
|
959
|
+
|
|
960
|
+
function formatWhole(num) {
|
|
961
|
+
return format(num, 0)
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function formatSmall(num, precision=2) {
|
|
965
|
+
return format(num, precision, true)
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// ─── Exports ─────────────────────────────────────────────────────
|
|
969
|
+
|
|
970
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
971
|
+
module.exports = { format, formatWhole, formatSmall, metaPolarize, letterName, symbolName, getOrdinalLetter, formatR0AsChain, FORMAT_OPTIONS }
|
|
972
|
+
}
|