gracio 1.0.0 → 1.0.1
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/dist/examples/benchmark.js +94 -0
- package/dist/examples/collision-example.js +28 -0
- package/dist/examples/memory-audit.js +71 -0
- package/dist/examples/precision-demo.js +17 -0
- package/dist/examples/validate-precision.js +79 -0
- package/dist/gracio.bundle.js +1 -0
- package/dist/precise-calculator.bundle.js +1 -0
- package/dist/src/audit-util.js +39 -0
- package/dist/src/calc-number.js +415 -0
- package/dist/src/constants.js +32 -0
- package/dist/src/demo.js +55 -0
- package/dist/src/gracio.js +415 -0
- package/dist/src/index.js +1 -0
- package/dist/src/lexer.js +51 -0
- package/dist/src/library-entry.js +4 -0
- package/dist/src/parser.js +187 -0
- package/dist/src/types.js +1 -0
- package/dist/test/advanced-arithmetic.test.js +63 -0
- package/dist/test/basic-arithmetic.test.js +40 -0
- package/dist/test/precision-comparison.test.js +40 -0
- package/dist/test/src/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
const SMALL_PRIMES = [
|
|
2
|
+
2n, 3n, 5n, 7n, 11n, 13n, 17n, 19n, 23n, 29n, 31n, 37n, 41n, 43n, 47n, 53n, 59n, 61n, 67n, 71n,
|
|
3
|
+
73n, 79n, 83n, 89n, 97n, 101n, 103n, 107n, 109n, 113n, 127n, 131n, 137n, 139n, 149n, 151n,
|
|
4
|
+
157n, 163n, 167n, 173n, 179n, 181n, 191n, 193n, 197n, 199n, 211n, 223n, 227n, 229n, 233n,
|
|
5
|
+
239n, 241n, 251n, 257n, 263n, 269n, 271n, 277n, 281n, 283n, 293n, 307n, 311n, 313n, 317n,
|
|
6
|
+
331n, 337n, 347n, 349n, 353n, 359n, 367n, 373n, 379n, 383n, 389n, 397n, 401n, 409n, 419n,
|
|
7
|
+
421n, 431n, 433n, 439n, 443n, 449n, 457n, 461n, 463n, 467n, 479n, 487n, 491n, 499n
|
|
8
|
+
];
|
|
9
|
+
export class CalcNumber {
|
|
10
|
+
numerator;
|
|
11
|
+
denominator;
|
|
12
|
+
precisionLimit; // Max decimal digits of precision to maintain
|
|
13
|
+
static MIN_SIMPLIFY_THRESHOLD = 10n ** 20n;
|
|
14
|
+
lastSimplifiedDigits = 0;
|
|
15
|
+
constructor(numerator, denominator = 1n, precisionLimit) {
|
|
16
|
+
if (denominator === 0n)
|
|
17
|
+
throw new Error("Denominator cannot be zero");
|
|
18
|
+
// Ensure the denominator is always positive
|
|
19
|
+
if (denominator < 0n) {
|
|
20
|
+
numerator *= -1n;
|
|
21
|
+
denominator *= -1n;
|
|
22
|
+
}
|
|
23
|
+
this.numerator = numerator;
|
|
24
|
+
this.denominator = denominator;
|
|
25
|
+
this.precisionLimit = precisionLimit;
|
|
26
|
+
this.simplify();
|
|
27
|
+
}
|
|
28
|
+
static fromInt(value) {
|
|
29
|
+
return new CalcNumber(BigInt(value), 1n);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Creates a PreciseNumber from a floating-point representation.
|
|
33
|
+
* Uses string parsing to ensure "human-intuitive" exactness (e.g., "0.1" -> 1/10).
|
|
34
|
+
*/
|
|
35
|
+
static fromFloat(value) {
|
|
36
|
+
const s = String(value).trim().toLowerCase();
|
|
37
|
+
if (s === 'nan' || s === 'infinity' || s === '-infinity') {
|
|
38
|
+
throw new Error("Cannot represent NaN or Infinity as a ratio");
|
|
39
|
+
}
|
|
40
|
+
// Handle scientific notation: e.g., 1.23e-4
|
|
41
|
+
const eIndex = s.indexOf('e');
|
|
42
|
+
if (eIndex !== -1) {
|
|
43
|
+
const coefficientStr = s.substring(0, eIndex);
|
|
44
|
+
const exponentStr = s.substring(eIndex + 1);
|
|
45
|
+
const exponent = parseInt(exponentStr, 10);
|
|
46
|
+
if (isNaN(exponent))
|
|
47
|
+
throw new Error(`Invalid exponent in float: ${s}`);
|
|
48
|
+
const coeff = CalcNumber.fromFloat(coefficientStr);
|
|
49
|
+
const scaleNum = exponent >= 0 ? 10n ** BigInt(exponent) : 1n;
|
|
50
|
+
const scaleDen = exponent < 0 ? 10n ** BigInt(-exponent) : 1n;
|
|
51
|
+
return new CalcNumber(coeff.numerator * scaleNum, coeff.denominator * scaleDen);
|
|
52
|
+
}
|
|
53
|
+
// Handle standard decimal notation: e.g., 123.456
|
|
54
|
+
const dotIndex = s.indexOf('.');
|
|
55
|
+
if (dotIndex === -1) {
|
|
56
|
+
return new CalcNumber(BigInt(s), 1n);
|
|
57
|
+
}
|
|
58
|
+
const wholePart = s.substring(0, dotIndex);
|
|
59
|
+
const fractionPart = s.substring(dotIndex + 1);
|
|
60
|
+
// Combine parts into a single integer (e.g., "123" + "456" -> 123456)
|
|
61
|
+
// We must handle signs carefully: if wholePart is "-0", the sign still matters.
|
|
62
|
+
const isNegative = s.startsWith('-');
|
|
63
|
+
const cleanWhole = wholePart.replace('-', '');
|
|
64
|
+
const combinedDigits = cleanWhole + fractionPart;
|
|
65
|
+
const numerator = BigInt(combinedDigits || '0');
|
|
66
|
+
const denominator = 10n ** BigInt(fractionPart.length);
|
|
67
|
+
return new CalcNumber(isNegative ? -numerator : numerator, denominator);
|
|
68
|
+
}
|
|
69
|
+
abs(value) {
|
|
70
|
+
return value < 0n ? -value : value;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Creates a deep copy of the current number.
|
|
74
|
+
* Use this when you need to perform operations without mutating the original object.
|
|
75
|
+
*/
|
|
76
|
+
clone() {
|
|
77
|
+
return new CalcNumber(this.numerator, this.denominator);
|
|
78
|
+
}
|
|
79
|
+
lastSimplifiedBits = 0;
|
|
80
|
+
shouldSimplify() {
|
|
81
|
+
const currentDigits = this.abs(this.numerator).toString().length;
|
|
82
|
+
// Simplify if we've crossed the minimum threshold AND grown by at least 20 digits since last simplification
|
|
83
|
+
return this.abs(this.numerator) > CalcNumber.MIN_SIMPLIFY_THRESHOLD &&
|
|
84
|
+
currentDigits > (this.lastSimplifiedDigits + 20);
|
|
85
|
+
}
|
|
86
|
+
checkPrecisionLimit() {
|
|
87
|
+
if (this.precisionLimit !== undefined) {
|
|
88
|
+
const currentDigits = this.abs(this.denominator).toString().length;
|
|
89
|
+
// Trigger approximation when growth exceeds 2x the limit to avoid jittery performance
|
|
90
|
+
if (currentDigits > this.precisionLimit * 2) {
|
|
91
|
+
this.pureApproximate(this.precisionLimit);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
simplify() {
|
|
96
|
+
const start = performance.now();
|
|
97
|
+
let a = this.abs(this.numerator);
|
|
98
|
+
let b = this.abs(this.denominator);
|
|
99
|
+
if (a === 0n)
|
|
100
|
+
return this;
|
|
101
|
+
if (b === 0n)
|
|
102
|
+
throw new Error("Denominator cannot be zero");
|
|
103
|
+
// Trial Division: Prune small prime factors first to reduce BigInt magnitude
|
|
104
|
+
for (const p of SMALL_PRIMES) {
|
|
105
|
+
while (a % p === 0n && b % p === 0n) {
|
|
106
|
+
a /= p;
|
|
107
|
+
b /= p;
|
|
108
|
+
this.numerator /= p;
|
|
109
|
+
this.denominator /= p; // denominator is always positive
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Final Binary GCD for any remaining larger factors
|
|
113
|
+
const gcd = this.calculateGCD(a, b);
|
|
114
|
+
if (gcd > 1n) {
|
|
115
|
+
this.numerator /= gcd;
|
|
116
|
+
this.denominator /= gcd;
|
|
117
|
+
}
|
|
118
|
+
const end = performance.now();
|
|
119
|
+
if (end - start > 10) { // Log if simplification takes more than 10ms
|
|
120
|
+
console.log(`[Simplify] Took ${(end - start).toFixed(2)}ms | Digits: ${a.toString().length}`);
|
|
121
|
+
}
|
|
122
|
+
this.lastSimplifiedDigits = this.abs(this.numerator).toString().length;
|
|
123
|
+
this.lastSimplifiedBits = this.abs(this.numerator).toString(2).length;
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
// --- Mutable Operations (Modifies current object and returns 'this' for chaining) ---
|
|
127
|
+
add(other) {
|
|
128
|
+
if (this.denominator === other.denominator) {
|
|
129
|
+
this.numerator += other.numerator;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
this.numerator = this.numerator * other.denominator + other.numerator * this.denominator;
|
|
133
|
+
this.denominator = this.denominator * other.denominator;
|
|
134
|
+
}
|
|
135
|
+
if (this.shouldSimplify())
|
|
136
|
+
this.simplify();
|
|
137
|
+
this.checkPrecisionLimit();
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
subtract(other) {
|
|
141
|
+
if (this.denominator === other.denominator) {
|
|
142
|
+
this.numerator -= other.numerator;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
this.numerator = this.numerator * other.denominator - other.numerator * this.denominator;
|
|
146
|
+
this.denominator = this.denominator * other.denominator;
|
|
147
|
+
}
|
|
148
|
+
if (this.shouldSimplify())
|
|
149
|
+
this.simplify();
|
|
150
|
+
this.checkPrecisionLimit();
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
multiply(other) {
|
|
154
|
+
const n1 = this.abs(this.numerator);
|
|
155
|
+
const d1 = this.abs(this.denominator);
|
|
156
|
+
const n2 = this.abs(other.numerator);
|
|
157
|
+
const d2 = this.abs(other.denominator);
|
|
158
|
+
// ASYMMETRIC FAST PATH: If at least one operand is small, we can afford raw multiplication.
|
|
159
|
+
// We only cross-simplify when BOTH numbers are large to prevent explosive digit growth.
|
|
160
|
+
const SMALL_THRESHOLD = 18446744073709551616n; // 2^64
|
|
161
|
+
if (n1 < SMALL_THRESHOLD || d1 < SMALL_THRESHOLD || n2 < SMALL_THRESHOLD || d2 < SMALL_THRESHOLD) {
|
|
162
|
+
this.numerator *= other.numerator;
|
|
163
|
+
this.denominator *= other.denominator;
|
|
164
|
+
if (this.shouldSimplify())
|
|
165
|
+
this.simplify();
|
|
166
|
+
this.checkPrecisionLimit();
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
// CROSS-SIMPLIFICATION: Prevents explosive growth for very large BigInts.
|
|
170
|
+
const g1 = this.calculateGCD(n1, d2);
|
|
171
|
+
const g2 = this.calculateGCD(n2, d1);
|
|
172
|
+
this.numerator = (this.numerator / g1) * (other.numerator / g2);
|
|
173
|
+
this.denominator = (this.denominator / g2) * (other.denominator / g1);
|
|
174
|
+
this.checkPrecisionLimit();
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
divide(other) {
|
|
178
|
+
if (other.numerator === 0n)
|
|
179
|
+
throw new Error("Cannot divide by zero");
|
|
180
|
+
const n1 = this.abs(this.numerator);
|
|
181
|
+
const d1 = this.abs(this.denominator);
|
|
182
|
+
const n2 = this.abs(other.numerator);
|
|
183
|
+
const d2 = this.abs(other.denominator);
|
|
184
|
+
// ASYMMETRIC FAST PATH: If at least one operand is small, avoid expensive GCDs.
|
|
185
|
+
const SMALL_THRESHOLD = 18446744073709551616n; // 2^64
|
|
186
|
+
if (n1 < SMALL_THRESHOLD || d1 < SMALL_THRESHOLD || n2 < SMALL_THRESHOLD || d2 < SMALL_THRESHOLD) {
|
|
187
|
+
this.numerator *= other.denominator;
|
|
188
|
+
this.denominator *= other.numerator;
|
|
189
|
+
if (this.denominator < 0n) {
|
|
190
|
+
this.numerator *= -1n;
|
|
191
|
+
this.denominator *= -1n;
|
|
192
|
+
}
|
|
193
|
+
if (this.shouldSimplify())
|
|
194
|
+
this.simplify();
|
|
195
|
+
this.checkPrecisionLimit();
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
// CROSS-SIMPLIFICATION: multiply by reciprocal with GCD pruning.
|
|
199
|
+
const g1 = this.calculateGCD(n1, n2); // numerator1 vs reciprocalDenominator (other.numerator)
|
|
200
|
+
const g2 = this.calculateGCD(d2, d1); // reciprocalNumerator (other.denominator) vs denominator1
|
|
201
|
+
this.numerator = (this.numerator / g1) * (d2 / g2);
|
|
202
|
+
this.denominator = (this.denominator / g2) * (n2 / g1);
|
|
203
|
+
if (this.denominator < 0n) {
|
|
204
|
+
this.numerator *= -1n;
|
|
205
|
+
this.denominator *= -1n;
|
|
206
|
+
}
|
|
207
|
+
this.checkPrecisionLimit();
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
calculateGCD(a, b) {
|
|
211
|
+
let n = a;
|
|
212
|
+
let d = b;
|
|
213
|
+
if (n === 0n)
|
|
214
|
+
return d;
|
|
215
|
+
if (d === 0n)
|
|
216
|
+
return n;
|
|
217
|
+
let shift = 0n;
|
|
218
|
+
while (((n | d) & 1n) === 0n) {
|
|
219
|
+
n >>= 1n;
|
|
220
|
+
d >>= 1n;
|
|
221
|
+
shift++;
|
|
222
|
+
}
|
|
223
|
+
while ((n & 1n) === 0n)
|
|
224
|
+
n >>= 1n;
|
|
225
|
+
do {
|
|
226
|
+
while ((d & 1n) === 0n)
|
|
227
|
+
d >>= 1n;
|
|
228
|
+
if (n > d) {
|
|
229
|
+
let t = n;
|
|
230
|
+
n = d;
|
|
231
|
+
d = t;
|
|
232
|
+
}
|
|
233
|
+
d = d - n;
|
|
234
|
+
} while (d !== 0n);
|
|
235
|
+
return n << shift;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Raises the number to the power of the given exponent.
|
|
239
|
+
* Handles negative exponents by inverting the ratio.
|
|
240
|
+
*/
|
|
241
|
+
pow(exponent) {
|
|
242
|
+
if (exponent === 0n) {
|
|
243
|
+
this.numerator = 1n;
|
|
244
|
+
this.denominator = 1n;
|
|
245
|
+
return this;
|
|
246
|
+
}
|
|
247
|
+
const absExp = exponent < 0n ? -exponent : exponent;
|
|
248
|
+
let newNum = this.numerator ** absExp;
|
|
249
|
+
let newDen = this.denominator ** absExp;
|
|
250
|
+
if (exponent < 0n) {
|
|
251
|
+
// Swap numerator and denominator for negative exponents
|
|
252
|
+
[newNum, newDen] = [newDen, newNum];
|
|
253
|
+
}
|
|
254
|
+
this.numerator = newNum;
|
|
255
|
+
this.denominator = newDen;
|
|
256
|
+
return this;
|
|
257
|
+
}
|
|
258
|
+
toFloat() {
|
|
259
|
+
this.simplify();
|
|
260
|
+
const n = this.abs(this.numerator);
|
|
261
|
+
const d = this.abs(this.denominator);
|
|
262
|
+
if (n === 0n)
|
|
263
|
+
return 0;
|
|
264
|
+
// Avoid overflow by scaling down if numbers exceed the safe range for Number (approx 1024 bits)
|
|
265
|
+
const nBits = n.toString(2).length;
|
|
266
|
+
const dBits = d.toString(2).length;
|
|
267
|
+
const maxBits = Math.max(nBits, dBits);
|
|
268
|
+
if (maxBits > 1000) {
|
|
269
|
+
const nShift = BigInt(nBits - 60);
|
|
270
|
+
const dShift = BigInt(dBits - 60);
|
|
271
|
+
// Scale both independently to fit in double-precision floats, then adjust by the difference in exponents
|
|
272
|
+
const nSmall = Number(this.numerator >> nShift);
|
|
273
|
+
const dSmall = Number(this.denominator >> dShift);
|
|
274
|
+
return (nSmall / dSmall) * Math.pow(2, nBits - dBits);
|
|
275
|
+
}
|
|
276
|
+
return Number(this.numerator) / Number(this.denominator);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Approximates the current ratio to a simpler fraction that maintains
|
|
280
|
+
* precision up to the specified number of decimal places.
|
|
281
|
+
* Uses Continued Fraction convergents to find the best rational approximation.
|
|
282
|
+
*/
|
|
283
|
+
/**
|
|
284
|
+
* Approximates the current ratio to a simpler fraction using continued fractions.
|
|
285
|
+
* @param digits Maximum number of digits allowed in the denominator.
|
|
286
|
+
*/
|
|
287
|
+
approximate(digits = 15) {
|
|
288
|
+
return this.pureApproximate(digits);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Pure rational approximation without converting to float.
|
|
292
|
+
* Finds the best convergent where the denominator does not exceed maxDenominatorDigits.
|
|
293
|
+
*/
|
|
294
|
+
pureApproximate(maxDenominatorDigits = 50) {
|
|
295
|
+
console.log(`[PrecisionLimit] Approximating to ${maxDenominatorDigits} digits...`);
|
|
296
|
+
let a = this.abs(this.numerator);
|
|
297
|
+
let b = this.abs(this.denominator);
|
|
298
|
+
const originalSign = (this.numerator < 0n) !== (this.denominator < 0n);
|
|
299
|
+
if (a === 0n)
|
|
300
|
+
return this;
|
|
301
|
+
let h_prev2 = 0n, h_prev1 = 1n;
|
|
302
|
+
let k_prev2 = 1n, k_prev1 = 0n;
|
|
303
|
+
while (b !== 0n) {
|
|
304
|
+
const q = a / b;
|
|
305
|
+
const r = a % b;
|
|
306
|
+
const h = q * h_prev1 + h_prev2;
|
|
307
|
+
const k = q * k_prev1 + k_prev2;
|
|
308
|
+
// If the new denominator exceeds our digit limit, stop and use the last valid convergent.
|
|
309
|
+
if (k.toString().length > maxDenominatorDigits) {
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
h_prev2 = h_prev1;
|
|
313
|
+
h_prev1 = h;
|
|
314
|
+
k_prev2 = k_prev1;
|
|
315
|
+
k_prev1 = k;
|
|
316
|
+
a = b;
|
|
317
|
+
b = r;
|
|
318
|
+
}
|
|
319
|
+
this.numerator = originalSign ? -h_prev1 : h_prev1;
|
|
320
|
+
this.denominator = k_prev1;
|
|
321
|
+
// Convergents are already in simplest form, no need to simplify().
|
|
322
|
+
return this;
|
|
323
|
+
}
|
|
324
|
+
toString() {
|
|
325
|
+
this.simplify();
|
|
326
|
+
return `${this.numerator}/${this.denominator}`;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Calculates the n-th root of a number.
|
|
330
|
+
* 1. Checks for perfect powers to return exact ratios.
|
|
331
|
+
* 2. Uses continued fractions for high-precision rational approximations of irrationals.
|
|
332
|
+
*/
|
|
333
|
+
static root(index, value) {
|
|
334
|
+
console.log(`[Debug] Calculating root(${index}, ${value.toString()})`);
|
|
335
|
+
if (index === 0n)
|
|
336
|
+
throw new Error("Root index cannot be zero");
|
|
337
|
+
if (index === 1n)
|
|
338
|
+
return value.clone();
|
|
339
|
+
const n = value.numerator;
|
|
340
|
+
const d = value.denominator;
|
|
341
|
+
// --- Step 1: Perfect Power Check ---
|
|
342
|
+
const rootN = this.integerRoot(n, index);
|
|
343
|
+
const rootD = this.integerRoot(d, index);
|
|
344
|
+
if (rootN !== null && rootD !== null) {
|
|
345
|
+
return new CalcNumber(rootN, rootD);
|
|
346
|
+
}
|
|
347
|
+
// --- Step 2: Continued Fraction Approximation ---
|
|
348
|
+
const target = value.toFloat();
|
|
349
|
+
const rootVal = Math.pow(target, 1 / Number(index));
|
|
350
|
+
let p0 = 0n, q0 = 1n; // Convergent -1
|
|
351
|
+
let p1 = 1n, q1 = 0n; // Not quite standard, let's use the iterative approach
|
|
352
|
+
// Standard Continued Fraction algorithm for a float x
|
|
353
|
+
let x = rootVal;
|
|
354
|
+
let a = Math.floor(x);
|
|
355
|
+
let h_prev2 = 0n, h_prev1 = 1n;
|
|
356
|
+
let k_prev2 = 1n, k_prev1 = 0n;
|
|
357
|
+
// We iterate for a few steps to get a very high precision convergent
|
|
358
|
+
for (let i = 0; i < 15; i++) {
|
|
359
|
+
const h = BigInt(Math.floor(x)) * h_prev1 + h_prev2;
|
|
360
|
+
const k = BigInt(Math.floor(x)) * k_prev1 + k_prev2;
|
|
361
|
+
h_prev2 = h_prev1;
|
|
362
|
+
h_prev1 = h;
|
|
363
|
+
k_prev2 = k_prev1;
|
|
364
|
+
k_prev1 = k;
|
|
365
|
+
if (x - Math.floor(x) === 0)
|
|
366
|
+
break;
|
|
367
|
+
x = 1 / (x - Math.floor(x));
|
|
368
|
+
}
|
|
369
|
+
return new CalcNumber(h_prev1, k_prev1);
|
|
370
|
+
}
|
|
371
|
+
static integerRoot(value, index) {
|
|
372
|
+
if (value === 0n)
|
|
373
|
+
return 0n;
|
|
374
|
+
if (value < 0n && index % 2n === 0n)
|
|
375
|
+
return null; // Even root of negative
|
|
376
|
+
const isNegative = value < 0n;
|
|
377
|
+
const absVal = value < 0n ? -value : value;
|
|
378
|
+
// Fast-path: Prime Sieve. If a prime divides absVal, it must divide it at least 'index' times.
|
|
379
|
+
for (const p of SMALL_PRIMES) {
|
|
380
|
+
if (absVal % p === 0n) {
|
|
381
|
+
let count = 0;
|
|
382
|
+
let temp = absVal;
|
|
383
|
+
while (temp % p === 0n) {
|
|
384
|
+
temp /= p;
|
|
385
|
+
count++;
|
|
386
|
+
}
|
|
387
|
+
if (count % Number(index) !== 0)
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
let low = 1n;
|
|
392
|
+
let high = absVal;
|
|
393
|
+
// Optimization: for index >= 2, the root is at most sqrt(absVal).
|
|
394
|
+
// For very large numbers, this significantly narrows the search space.
|
|
395
|
+
if (index >= 2n) {
|
|
396
|
+
// We can't easily get a starting 'high' without Math.sqrt,
|
|
397
|
+
// but we can use a smaller bound for common cases if needed.
|
|
398
|
+
}
|
|
399
|
+
while (low <= high) {
|
|
400
|
+
const mid = (low + high) / 2n;
|
|
401
|
+
if (mid === 0n) {
|
|
402
|
+
low = 1n;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const p = mid ** index;
|
|
406
|
+
if (p === absVal)
|
|
407
|
+
return isNegative ? -mid : mid;
|
|
408
|
+
if (p < absVal)
|
|
409
|
+
low = mid + 1n;
|
|
410
|
+
else
|
|
411
|
+
high = mid - 1n;
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Gracio } from './gracio.js';
|
|
2
|
+
export class Constants {
|
|
3
|
+
/**
|
|
4
|
+
* Pi approximation using a high-precision convergent (355/113 is the most famous,
|
|
5
|
+
* but we can go further for scientific use).
|
|
6
|
+
* Convergent: 5419351 / 1724137 (~10 digits)
|
|
7
|
+
*/
|
|
8
|
+
static get PI() {
|
|
9
|
+
return new Gracio(5419351n, 1724137n);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Euler's number e approximation.
|
|
13
|
+
* Convergent: 2718281828459 / 1000000000000
|
|
14
|
+
*/
|
|
15
|
+
static get E() {
|
|
16
|
+
return new Gracio(2718281828459n, 1000000000000n);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Golden Ratio phi = (1 + sqrt(5)) / 2.
|
|
20
|
+
* We calculate this using the engine's root approximation for maximum consistency.
|
|
21
|
+
*/
|
|
22
|
+
static get PHI() {
|
|
23
|
+
const sqrt5 = Gracio.root(2n, new Gracio(5n, 1n));
|
|
24
|
+
return new Gracio(1n, 1n).add(sqrt5).divide(new Gracio(2n, 1n));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Gravitational Constant G approx 6.67430e-11
|
|
28
|
+
*/
|
|
29
|
+
static get GRAVITY() {
|
|
30
|
+
return Gracio.fromFloat('6.67430e-11');
|
|
31
|
+
}
|
|
32
|
+
}
|
package/dist/src/demo.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Parser } from './parser.js';
|
|
2
|
+
import { Lexer } from './lexer.js';
|
|
3
|
+
import { Gracio } from './gracio.js';
|
|
4
|
+
import { Constants } from './constants.js';
|
|
5
|
+
async function main() {
|
|
6
|
+
console.log("=== Basic Functionality Tests ===");
|
|
7
|
+
const expressions = [
|
|
8
|
+
"1 + 0.1", // Testing Float Bridge: should be 11/10
|
|
9
|
+
"0.3333333333 * 3", // Testing Precision: should be 0.9999999999
|
|
10
|
+
"root(2, 2)", // Standard root
|
|
11
|
+
"root(2, 0.25)", // Root of a float (0.5)
|
|
12
|
+
"1 / 3 + 1 / 6", // Classic ratio addition: should be 1/2
|
|
13
|
+
];
|
|
14
|
+
for (const expr of expressions) {
|
|
15
|
+
console.log(`Evaluating: ${expr}`);
|
|
16
|
+
const lexer = new Lexer();
|
|
17
|
+
const tokens = lexer.tokenize(expr);
|
|
18
|
+
const parser = new Parser(tokens);
|
|
19
|
+
try {
|
|
20
|
+
const result = parser.parse();
|
|
21
|
+
console.log(` Ratio: ${result.toString()} | Float: ${result.toFloat()}`);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error(` Error: ${error.message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
console.log("\n=== Precision Audit: Gracio vs IEEE 754 Floats ===");
|
|
28
|
+
const audit = (label, floatResult, preciseResult) => {
|
|
29
|
+
const pFloat = preciseResult.toFloat();
|
|
30
|
+
const diff = Math.abs(floatResult - pFloat);
|
|
31
|
+
console.log(`${label.padEnd(30)} | Float: ${floatResult} | Precise: ${pFloat} | Drift: ${diff}`);
|
|
32
|
+
};
|
|
33
|
+
// Case 1: Representation Error (The Classic)
|
|
34
|
+
audit("Classic Drift (0.1 + 0.2)", 0.1 + 0.2, new Gracio(1n, 10n).add(new Gracio(2n, 10n)));
|
|
35
|
+
// Case 2: The Integer Wall (Beyond MAX_SAFE_INTEGER)
|
|
36
|
+
const bigNum = 2n ** 60n;
|
|
37
|
+
audit("The Integer Wall (2^60 + 1 - 2^60)", (Math.pow(2, 60) + 1) - Math.pow(2, 60), new Gracio(bigNum + 1n, 1n).subtract(new Gracio(bigNum, 1n)));
|
|
38
|
+
// Case 3: Cumulative Absorption (Adding small to large)
|
|
39
|
+
let floatSum = 1.0;
|
|
40
|
+
const incrementFloat = 0.0000001;
|
|
41
|
+
for (let i = 0; i < 10_000_000; i++) {
|
|
42
|
+
floatSum += incrementFloat;
|
|
43
|
+
}
|
|
44
|
+
const preciseSum = new Gracio(1n, 1n);
|
|
45
|
+
const incrementPrecise = new Gracio(1n, 10000000n);
|
|
46
|
+
for (let i = 0; i < 10_000_000; i++) {
|
|
47
|
+
preciseSum.add(incrementPrecise);
|
|
48
|
+
}
|
|
49
|
+
audit("Cumulative Absorption", floatSum, preciseSum);
|
|
50
|
+
console.log("\n=== Testing Constants ===");
|
|
51
|
+
console.log(`PI: ${Constants.PI.toString()} (~${Constants.PI.toFloat()})`);
|
|
52
|
+
console.log(`E : ${Constants.E.toString()} (~${Constants.E.toFloat()})`);
|
|
53
|
+
console.log(`PHI: ${Constants.PHI.toString()} (~${Constants.PHI.toFloat()})`);
|
|
54
|
+
}
|
|
55
|
+
void main();
|