picocalc 0.0.1 → 0.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/LICENSE +21 -21
- package/dist/index.d.mts +14 -6
- package/dist/index.mjs +704 -673
- package/package.json +13 -8
package/dist/index.mjs
CHANGED
|
@@ -1,26 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
const PI = Math.PI.toString();
|
|
3
|
-
const constants = {
|
|
4
|
-
e: Math.E.toString(),
|
|
5
|
-
pi: PI
|
|
6
|
-
};
|
|
7
|
-
//#endregion
|
|
1
|
+
import "bigint.bitlength";
|
|
8
2
|
//#region src/lib/errors.ts
|
|
9
|
-
var
|
|
3
|
+
var PicocalcErrror = class extends Error {
|
|
10
4
|
pos;
|
|
11
5
|
constructor(name, message, pos) {
|
|
12
|
-
if (pos === void 0) super(
|
|
13
|
-
else super(`${
|
|
6
|
+
if (pos === void 0) super(message);
|
|
7
|
+
else super(`${message} at position ${pos}`);
|
|
14
8
|
this.pos = pos;
|
|
15
9
|
this.name = name;
|
|
16
10
|
}
|
|
17
11
|
};
|
|
18
|
-
var
|
|
19
|
-
constructor(message, pos) {
|
|
20
|
-
super("LexerError", message, pos);
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
var InterpreterError = class extends GenericMathErrror {
|
|
12
|
+
var InterpreterError = class extends PicocalcErrror {
|
|
24
13
|
constructor(message, pos) {
|
|
25
14
|
super("InterpreterError", message, pos);
|
|
26
15
|
}
|
|
@@ -30,6 +19,11 @@ var MaximumPrecisionError = class extends InterpreterError {
|
|
|
30
19
|
super(`Exceeded maximum precision of ${maxPrecision} digits (${precision})`);
|
|
31
20
|
}
|
|
32
21
|
};
|
|
22
|
+
var NotImplementedError = class extends InterpreterError {
|
|
23
|
+
constructor(message, pos) {
|
|
24
|
+
super(message, pos);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
33
27
|
var OverflowError = class extends InterpreterError {
|
|
34
28
|
constructor() {
|
|
35
29
|
super("Overflow");
|
|
@@ -45,56 +39,152 @@ var EmptyExpressionError = class extends InterpreterError {
|
|
|
45
39
|
super("Empty expression");
|
|
46
40
|
}
|
|
47
41
|
};
|
|
48
|
-
var UnexpectedEndOfExpressionError = class extends InterpreterError {
|
|
49
|
-
constructor() {
|
|
50
|
-
super("Unexpected end of expression");
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
42
|
var MismatchedParenthesisError = class extends InterpreterError {
|
|
54
43
|
constructor(pos, message = "Mismatched parenthesis") {
|
|
55
44
|
super(message, pos);
|
|
56
45
|
}
|
|
57
46
|
};
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/lib/types.ts
|
|
49
|
+
const OverflowValue = { n: "OVERFLOW" };
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/lib/utils/constants.ts
|
|
52
|
+
const PI = Math.PI.toString();
|
|
53
|
+
const E = Math.E.toString();
|
|
54
|
+
const ZERO = {
|
|
55
|
+
n: 0n,
|
|
56
|
+
d: 1n
|
|
62
57
|
};
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
58
|
+
const ONE = {
|
|
59
|
+
n: 1n,
|
|
60
|
+
d: 1n
|
|
67
61
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
62
|
+
const constants = {
|
|
63
|
+
e: E,
|
|
64
|
+
pi: PI
|
|
72
65
|
};
|
|
66
|
+
function getConst(id) {
|
|
67
|
+
const c = constants[id];
|
|
68
|
+
return {
|
|
69
|
+
n: BigInt(c.replace(".", "")),
|
|
70
|
+
d: 10n ** BigInt(c.length - c.indexOf(".") - 1)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
73
|
//#endregion
|
|
74
74
|
//#region src/lib/utils/gcd.ts
|
|
75
75
|
/**
|
|
76
|
-
*
|
|
76
|
+
* The Euclidean GCD Algorithm for BigInt.
|
|
77
77
|
*/
|
|
78
78
|
function gcd(a, b) {
|
|
79
79
|
if (a === b) return a;
|
|
80
80
|
if (a === 1n || b === 1n) return 1n;
|
|
81
81
|
if (a === 0n) return b;
|
|
82
82
|
if (b === 0n) return a;
|
|
83
|
-
a = a <
|
|
84
|
-
b = b <
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
a
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
a = a < 0 ? -a : a;
|
|
84
|
+
b = b < 0 ? -b : b;
|
|
85
|
+
while (b > 0) {
|
|
86
|
+
a %= b;
|
|
87
|
+
[a, b] = [b, a];
|
|
88
|
+
}
|
|
89
|
+
return a;
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/lib/utils/multiply.ts
|
|
93
|
+
function multiply(a, b) {
|
|
94
|
+
const aN = a.n;
|
|
95
|
+
const bN = b.n;
|
|
96
|
+
if (aN === 0n || bN === 0n) return ZERO;
|
|
97
|
+
if (aN === "OVERFLOW") return a;
|
|
98
|
+
if (bN === "OVERFLOW") return b;
|
|
99
|
+
const { d: aD, c: aC, e: aE } = a;
|
|
100
|
+
const { d: bD, c: bC, e: bE } = b;
|
|
101
|
+
const g1 = gcd(aN, bD);
|
|
102
|
+
const g2 = gcd(bN, aD);
|
|
103
|
+
const n = aN / g1 * (bN / g2);
|
|
104
|
+
const d = aD / g2 * (bD / g1);
|
|
105
|
+
if (aC === void 0 && bC === void 0) return {
|
|
106
|
+
n,
|
|
107
|
+
d
|
|
108
|
+
};
|
|
109
|
+
if (aC === bC) return {
|
|
110
|
+
n,
|
|
111
|
+
d,
|
|
112
|
+
c: aC,
|
|
113
|
+
e: simplify(add({
|
|
114
|
+
n: aE?.n ?? 1n,
|
|
115
|
+
d: aE?.d ?? 1n
|
|
116
|
+
}, {
|
|
117
|
+
n: bE?.n ?? 1n,
|
|
118
|
+
d: bE?.d ?? 1n
|
|
119
|
+
}))
|
|
120
|
+
};
|
|
121
|
+
if (aC !== void 0 && bC !== void 0) return multiply(toSimpleFraction(a), toSimpleFraction(b));
|
|
122
|
+
return {
|
|
123
|
+
n,
|
|
124
|
+
d,
|
|
125
|
+
c: aC ?? bC,
|
|
126
|
+
e: aE ?? bE
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/lib/utils/nthroot.ts
|
|
131
|
+
/**
|
|
132
|
+
* Calculates the integer nth root of a BigInt.
|
|
133
|
+
*/
|
|
134
|
+
function iNthRoot(value, n) {
|
|
135
|
+
if (value < 0 && n % 2n === 0n) throw new NotImplementedError(`Even (${n}th) root of negative numbers is not implemented yet.`);
|
|
136
|
+
if (value < 0) return -iNthRoot(-value, n);
|
|
137
|
+
if (value < 2n) return value;
|
|
138
|
+
if (n === 1n) return value;
|
|
139
|
+
let x = 1n << BigInt.bitLength(value) / n + 1n;
|
|
140
|
+
const nMinus1 = n - 1n;
|
|
141
|
+
while (true) {
|
|
142
|
+
const nextX = (nMinus1 * x + value / x ** nMinus1) / n;
|
|
143
|
+
if (nextX >= x) break;
|
|
144
|
+
x = nextX;
|
|
145
|
+
}
|
|
146
|
+
return x;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Calculates the n-th root of a fraction.
|
|
150
|
+
*/
|
|
151
|
+
function nthRoot(v, n, precision) {
|
|
152
|
+
if (n === 0n) throw new DivisionByZeroError();
|
|
153
|
+
if (n === 1n) return v;
|
|
154
|
+
if (v.n < 0) {
|
|
155
|
+
if (n % 2n === 0n) throw new NotImplementedError(`Even (${n}th) root of negative numbers is not implemented yet.`);
|
|
156
|
+
const positiveResult = nthRoot({
|
|
157
|
+
n: -v.n,
|
|
158
|
+
d: v.d
|
|
159
|
+
}, n, precision);
|
|
160
|
+
return {
|
|
161
|
+
n: -positiveResult.n,
|
|
162
|
+
d: positiveResult.d
|
|
163
|
+
};
|
|
90
164
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
165
|
+
if (v.n === 0n) return ZERO;
|
|
166
|
+
if (precision.format === "precise") {
|
|
167
|
+
const rootN = iNthRoot(v.n, n);
|
|
168
|
+
const rootD = iNthRoot(v.d, n);
|
|
169
|
+
if (rootN ** n === v.n && rootD ** n === v.d) {
|
|
170
|
+
const e = simplify({
|
|
171
|
+
n: v.e?.n ?? 1n,
|
|
172
|
+
d: (v.e?.d ?? 1n) * n
|
|
173
|
+
});
|
|
174
|
+
return simplify({
|
|
175
|
+
n: rootN,
|
|
176
|
+
d: rootD,
|
|
177
|
+
c: v.c,
|
|
178
|
+
e
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const precisionDigits = Math.max(10, (precision.maxDecimals ?? 30) * 3);
|
|
183
|
+
const scaleFactor = 10n ** (BigInt(precisionDigits) * n);
|
|
184
|
+
return simplify({
|
|
185
|
+
n: iNthRoot(v.n * scaleFactor / v.d, n),
|
|
186
|
+
d: 10n ** BigInt(precisionDigits)
|
|
187
|
+
});
|
|
98
188
|
}
|
|
99
189
|
//#endregion
|
|
100
190
|
//#region src/lib/utils/simplify.ts
|
|
@@ -103,34 +193,277 @@ function gcd(a, b) {
|
|
|
103
193
|
*/
|
|
104
194
|
function simplify(v) {
|
|
105
195
|
if (v.d === 0n) throw new DivisionByZeroError();
|
|
106
|
-
if (v.n === 0n) return
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
196
|
+
if (v.n === 0n) return ZERO;
|
|
197
|
+
if (v.d === 1n) {
|
|
198
|
+
if (v.e?.n === 0n) return {
|
|
199
|
+
n: v.n,
|
|
200
|
+
d: v.d
|
|
201
|
+
};
|
|
202
|
+
return v;
|
|
203
|
+
}
|
|
112
204
|
const common = gcd(v.n, v.d);
|
|
113
|
-
const sign = v.d <
|
|
205
|
+
const sign = v.d < 0 ? -1n : 1n;
|
|
206
|
+
const n = v.n / common * sign;
|
|
207
|
+
const d = v.d / common * sign;
|
|
208
|
+
if (v.e?.n === 0n) return {
|
|
209
|
+
n,
|
|
210
|
+
d
|
|
211
|
+
};
|
|
212
|
+
return {
|
|
213
|
+
n,
|
|
214
|
+
d,
|
|
215
|
+
c: v.c,
|
|
216
|
+
e: v.e
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Resolves any constants and exponents to return a simple fraction.
|
|
221
|
+
*/
|
|
222
|
+
function toSimpleFraction(val) {
|
|
223
|
+
if (!val.c) return val;
|
|
224
|
+
const c = getConst(val.c);
|
|
225
|
+
const expN = val.e?.n ?? 1n;
|
|
226
|
+
const expD = val.e?.d ?? 1n;
|
|
227
|
+
const absExpN = expN < 0 ? -expN : expN;
|
|
228
|
+
let poweredConstant = {
|
|
229
|
+
n: expN < 0 ? c.d ** absExpN : c.n ** absExpN,
|
|
230
|
+
d: expN < 0 ? c.n ** absExpN : c.d ** absExpN
|
|
231
|
+
};
|
|
232
|
+
if (expD !== 1n) poweredConstant = nthRoot(poweredConstant, expD, { format: "precise" });
|
|
233
|
+
return multiply(poweredConstant, {
|
|
234
|
+
n: val.n,
|
|
235
|
+
d: val.d
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/lib/utils/add.ts
|
|
240
|
+
function add(left, right, subtract = false) {
|
|
241
|
+
const lN = left.n;
|
|
242
|
+
const rN = right.n;
|
|
243
|
+
if (lN === "OVERFLOW") return left;
|
|
244
|
+
if (rN === "OVERFLOW") return right;
|
|
245
|
+
const { d: lD, c, e } = left;
|
|
246
|
+
const { d: rD, c: rC, e: rE } = right;
|
|
247
|
+
if (lN === 0n) return {
|
|
248
|
+
n: subtract ? -rN : rN,
|
|
249
|
+
d: rD,
|
|
250
|
+
c: rC,
|
|
251
|
+
e: rE
|
|
252
|
+
};
|
|
253
|
+
if (rN === 0n) return {
|
|
254
|
+
n: lN,
|
|
255
|
+
d: lD,
|
|
256
|
+
c,
|
|
257
|
+
e
|
|
258
|
+
};
|
|
259
|
+
let n;
|
|
260
|
+
let d;
|
|
261
|
+
if (lD === rD) {
|
|
262
|
+
n = subtract ? lN - rN : lN + rN;
|
|
263
|
+
d = lD;
|
|
264
|
+
} else {
|
|
265
|
+
const common = gcd(lD, rD);
|
|
266
|
+
const mLeft = rD / common;
|
|
267
|
+
const mRight = lD / common;
|
|
268
|
+
n = subtract ? lN * mLeft - rN * mRight : lN * mLeft + rN * mRight;
|
|
269
|
+
d = lD * mLeft;
|
|
270
|
+
}
|
|
271
|
+
if (n === 0n) return ZERO;
|
|
272
|
+
if (c !== rC || e?.n !== rE?.n) return add(toSimpleFraction(left), toSimpleFraction(right), subtract);
|
|
114
273
|
return {
|
|
115
|
-
n
|
|
116
|
-
d
|
|
117
|
-
c
|
|
274
|
+
n,
|
|
275
|
+
d,
|
|
276
|
+
c,
|
|
277
|
+
e
|
|
118
278
|
};
|
|
119
279
|
}
|
|
120
280
|
//#endregion
|
|
121
281
|
//#region src/lib/utils/ceil.ts
|
|
122
282
|
/**
|
|
123
|
-
* Returns the smallest integer greater than or equal to
|
|
283
|
+
* Returns the smallest integer greater than or equal to the value.
|
|
124
284
|
*/
|
|
125
285
|
function ceil(v) {
|
|
126
286
|
const { n, d } = simplify(v);
|
|
127
287
|
if (d === 1n) return n;
|
|
128
|
-
const isPositive = n >
|
|
288
|
+
const isPositive = n > 0;
|
|
129
289
|
const result = n / d;
|
|
130
290
|
const hasRemainder = n % d !== 0n;
|
|
131
291
|
return isPositive && hasRemainder ? result + 1n : result;
|
|
132
292
|
}
|
|
133
293
|
//#endregion
|
|
294
|
+
//#region src/lib/utils/divide.ts
|
|
295
|
+
function divide(left, right) {
|
|
296
|
+
const lN = left.n;
|
|
297
|
+
const rN = right.n;
|
|
298
|
+
if (rN === 0n) throw new DivisionByZeroError();
|
|
299
|
+
if (lN === 0n) return ZERO;
|
|
300
|
+
if (lN === "OVERFLOW" || rN === "OVERFLOW") return OverflowValue;
|
|
301
|
+
const { d: lD, c: lC, e: lE } = left;
|
|
302
|
+
const { d: rD, c: rC, e: rE } = right;
|
|
303
|
+
const g1 = gcd(lN, rN);
|
|
304
|
+
const g2 = gcd(rD, lD);
|
|
305
|
+
const n = lN / g1 * (rD / g2);
|
|
306
|
+
const d = lD / g2 * (rN / g1);
|
|
307
|
+
if (lC !== void 0 && lC === rC) return {
|
|
308
|
+
n,
|
|
309
|
+
d,
|
|
310
|
+
c: lC,
|
|
311
|
+
e: simplify(add({
|
|
312
|
+
n: lE?.n ?? 1n,
|
|
313
|
+
d: lE?.d ?? 1n
|
|
314
|
+
}, {
|
|
315
|
+
n: rE?.n ?? 1n,
|
|
316
|
+
d: rE?.d ?? 1n
|
|
317
|
+
}, true))
|
|
318
|
+
};
|
|
319
|
+
const c = lC ?? rC;
|
|
320
|
+
let e;
|
|
321
|
+
if (lC !== void 0) e = lE ? {
|
|
322
|
+
n: lE.n,
|
|
323
|
+
d: lE.d ?? 1n
|
|
324
|
+
} : {
|
|
325
|
+
n: 1n,
|
|
326
|
+
d: 1n
|
|
327
|
+
};
|
|
328
|
+
else if (rC !== void 0) e = {
|
|
329
|
+
n: -(rE?.n ?? 1n),
|
|
330
|
+
d: rE?.d ?? 1n
|
|
331
|
+
};
|
|
332
|
+
return {
|
|
333
|
+
n,
|
|
334
|
+
d,
|
|
335
|
+
c,
|
|
336
|
+
e
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/lib/utils/sqrt.ts
|
|
341
|
+
/**
|
|
342
|
+
* Calculates the integer square root of a BigInt.
|
|
343
|
+
*/
|
|
344
|
+
function isqrt(value) {
|
|
345
|
+
if (value < 0) throw new NotImplementedError("Square root of negative numbers is not implemented yet.");
|
|
346
|
+
if (value < 2n) return value;
|
|
347
|
+
let res = 0n;
|
|
348
|
+
let bit = 1n << (BigInt.bitLength(value) & -2n);
|
|
349
|
+
while (bit !== 0n) {
|
|
350
|
+
if (value >= res + bit) {
|
|
351
|
+
value -= res + bit;
|
|
352
|
+
res = (res >> 1n) + bit;
|
|
353
|
+
} else res >>= 1n;
|
|
354
|
+
bit >>= 2n;
|
|
355
|
+
}
|
|
356
|
+
return res;
|
|
357
|
+
}
|
|
358
|
+
function isPerfectSquare(val) {
|
|
359
|
+
if (val < 0) return [false, 0n];
|
|
360
|
+
const root = isqrt(val);
|
|
361
|
+
return [root * root === val, root];
|
|
362
|
+
}
|
|
363
|
+
function sqrt(v, precision) {
|
|
364
|
+
if (v.n === "OVERFLOW") return v;
|
|
365
|
+
if (v.n < 0) throw new NotImplementedError("Square root of negative numbers is not implemented yet.");
|
|
366
|
+
if (v.n === 0n) return ZERO;
|
|
367
|
+
if (precision.format === "precise") {
|
|
368
|
+
const [nIsSquare, n] = isPerfectSquare(v.n);
|
|
369
|
+
const [dIsSquare, d] = isPerfectSquare(v.d);
|
|
370
|
+
if (nIsSquare && dIsSquare) {
|
|
371
|
+
const e = simplify({
|
|
372
|
+
n: v.e?.n ?? 1n,
|
|
373
|
+
d: (v.e?.d ?? 1n) * 2n
|
|
374
|
+
});
|
|
375
|
+
return simplify({
|
|
376
|
+
n,
|
|
377
|
+
d,
|
|
378
|
+
c: v.c,
|
|
379
|
+
e
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const precisionDigits = Math.max(10, (precision.maxDecimals ?? 30) * 3);
|
|
384
|
+
const scaleFactor = 10n ** BigInt(precisionDigits * 2);
|
|
385
|
+
return simplify({
|
|
386
|
+
n: isqrt(v.n * scaleFactor / v.d),
|
|
387
|
+
d: 10n ** BigInt(precisionDigits)
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region src/lib/utils/exponentiate.ts
|
|
392
|
+
/**
|
|
393
|
+
* Maximum digits allowed for the result of exponentiation
|
|
394
|
+
*/
|
|
395
|
+
const MAX_EXPONENT_RESULT_DIGITS = 10000000n;
|
|
396
|
+
function exponentiate(left, right, precision) {
|
|
397
|
+
const rN = right.n;
|
|
398
|
+
if (rN === 0n) return ONE;
|
|
399
|
+
const lN = left.n;
|
|
400
|
+
if (lN === "OVERFLOW") return left;
|
|
401
|
+
const lD = left.d;
|
|
402
|
+
const c = left.c;
|
|
403
|
+
if (lN === lD && c === void 0) return ONE;
|
|
404
|
+
if (rN === "OVERFLOW") return right;
|
|
405
|
+
if (lN === -lD && c === void 0) {
|
|
406
|
+
if (simplify(right).n % 2n) return {
|
|
407
|
+
n: -1n,
|
|
408
|
+
d: 1n
|
|
409
|
+
};
|
|
410
|
+
return ONE;
|
|
411
|
+
}
|
|
412
|
+
const normalizedExp = toSimpleFraction(simplify(right));
|
|
413
|
+
let exponent = normalizedExp.n;
|
|
414
|
+
if (lN === 0n) {
|
|
415
|
+
if (exponent < 0) throw new DivisionByZeroError();
|
|
416
|
+
return ZERO;
|
|
417
|
+
}
|
|
418
|
+
const dExp = normalizedExp.d;
|
|
419
|
+
if (dExp >= 100) {
|
|
420
|
+
let baseFloat = Number(lN) / Number(lD);
|
|
421
|
+
if (c) baseFloat *= Number(constants[c]);
|
|
422
|
+
const expFloat = Number(exponent) / Number(dExp);
|
|
423
|
+
const resultFloat = baseFloat ** expFloat;
|
|
424
|
+
if (!Number.isFinite(resultFloat)) return OverflowValue;
|
|
425
|
+
const [integerPart, fractionalPart] = resultFloat.toString().split(".");
|
|
426
|
+
if (!fractionalPart) return simplify({
|
|
427
|
+
n: BigInt(integerPart),
|
|
428
|
+
d: 1n
|
|
429
|
+
});
|
|
430
|
+
const denominator = 10n ** BigInt(fractionalPart.length);
|
|
431
|
+
return simplify({
|
|
432
|
+
n: BigInt(integerPart + fractionalPart),
|
|
433
|
+
d: denominator
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
const eN = (left.e?.n ?? 1n) * exponent;
|
|
437
|
+
let baseN = lN;
|
|
438
|
+
let baseD = lD;
|
|
439
|
+
if (exponent < 0) {
|
|
440
|
+
exponent = -exponent;
|
|
441
|
+
[baseN, baseD] = [baseD, baseN];
|
|
442
|
+
}
|
|
443
|
+
const baseNDigits = BigInt.bitLength(baseN);
|
|
444
|
+
const baseDDigits = BigInt.bitLength(baseD);
|
|
445
|
+
if (baseNDigits * exponent > MAX_EXPONENT_RESULT_DIGITS || baseDDigits * exponent > MAX_EXPONENT_RESULT_DIGITS || (baseNDigits > 5e3 || baseDDigits > 5e3) && exponent > 10) return OverflowValue;
|
|
446
|
+
const e = c ? simplify({
|
|
447
|
+
n: eN,
|
|
448
|
+
d: left.e?.d ?? 1n
|
|
449
|
+
}) : void 0;
|
|
450
|
+
if (dExp === exponent) return {
|
|
451
|
+
n: baseN,
|
|
452
|
+
d: baseD,
|
|
453
|
+
c,
|
|
454
|
+
e
|
|
455
|
+
};
|
|
456
|
+
const v = {
|
|
457
|
+
n: baseN ** exponent,
|
|
458
|
+
d: baseD ** exponent,
|
|
459
|
+
c,
|
|
460
|
+
e
|
|
461
|
+
};
|
|
462
|
+
if (dExp === 1n) return v;
|
|
463
|
+
if (dExp === 2n) return sqrt(v, precision);
|
|
464
|
+
return nthRoot(v, dExp, precision);
|
|
465
|
+
}
|
|
466
|
+
//#endregion
|
|
134
467
|
//#region src/lib/utils/factorial.ts
|
|
135
468
|
function binaryProduct(arr, low, high) {
|
|
136
469
|
if (low > high) return 1n;
|
|
@@ -150,7 +483,7 @@ function getPrimes(limit) {
|
|
|
150
483
|
* Calculates factorial.
|
|
151
484
|
*/
|
|
152
485
|
function factorial(n) {
|
|
153
|
-
if (n <
|
|
486
|
+
if (n < 0) return null;
|
|
154
487
|
if (n === 0n || n === 1n) return 1n;
|
|
155
488
|
if (n < 1000n) {
|
|
156
489
|
let result = 1n;
|
|
@@ -174,12 +507,12 @@ function factorial(n) {
|
|
|
174
507
|
//#endregion
|
|
175
508
|
//#region src/lib/utils/floor.ts
|
|
176
509
|
/**
|
|
177
|
-
* Returns the largest integer less than or equal to
|
|
510
|
+
* Returns the largest integer less than or equal to the value.
|
|
178
511
|
*/
|
|
179
512
|
function floor(v) {
|
|
180
513
|
const { n, d } = simplify(v);
|
|
181
514
|
if (d === 1n) return n;
|
|
182
|
-
const isNegative = n <
|
|
515
|
+
const isNegative = n < 0;
|
|
183
516
|
const result = n / d;
|
|
184
517
|
const hasRemainder = n % d !== 0n;
|
|
185
518
|
return isNegative && hasRemainder ? result - 1n : result;
|
|
@@ -190,6 +523,7 @@ function floor(v) {
|
|
|
190
523
|
* Calculates remainder of division.
|
|
191
524
|
*/
|
|
192
525
|
function mod(a, b) {
|
|
526
|
+
if (b.n === 0n) throw new DivisionByZeroError();
|
|
193
527
|
const d = a.d * b.d / gcd(a.d, b.d);
|
|
194
528
|
const n1 = a.n * (d / a.d);
|
|
195
529
|
const n2 = b.n * (d / b.d);
|
|
@@ -199,131 +533,6 @@ function mod(a, b) {
|
|
|
199
533
|
};
|
|
200
534
|
}
|
|
201
535
|
//#endregion
|
|
202
|
-
//#region src/lib/utils/multiply.ts
|
|
203
|
-
function multiply(a, b) {
|
|
204
|
-
if (a.n === 0n || b.n === 0n) return {
|
|
205
|
-
n: 0n,
|
|
206
|
-
d: 1n
|
|
207
|
-
};
|
|
208
|
-
let n;
|
|
209
|
-
let d;
|
|
210
|
-
let c;
|
|
211
|
-
if (a.d === 1n && b.d === 1n) {
|
|
212
|
-
n = a.n * b.n;
|
|
213
|
-
d = 1n;
|
|
214
|
-
} else {
|
|
215
|
-
const g1 = gcd(a.n, b.d);
|
|
216
|
-
const g2 = gcd(b.n, a.d);
|
|
217
|
-
n = a.n / g1 * (b.n / g2);
|
|
218
|
-
d = a.d / g2 * (b.d / g1);
|
|
219
|
-
}
|
|
220
|
-
if (a.c === void 0 && b.c !== void 0) c = b.c;
|
|
221
|
-
else if (a.c !== void 0 && b.c === void 0) c = a.c;
|
|
222
|
-
return {
|
|
223
|
-
n,
|
|
224
|
-
d,
|
|
225
|
-
c
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
//#endregion
|
|
229
|
-
//#region src/lib/utils/nthroot.ts
|
|
230
|
-
/**
|
|
231
|
-
* Calculates the integer nth root of a BigInt.
|
|
232
|
-
*/
|
|
233
|
-
function iNthRoot(value, n) {
|
|
234
|
-
if (value < 0n && n % 2n === 0n) throw new InterpreterError("Even root of negative number is not supported yet.");
|
|
235
|
-
if (value < 0n) return -iNthRoot(-value, n);
|
|
236
|
-
if (value < 2n) return value;
|
|
237
|
-
if (n === 1n) return value;
|
|
238
|
-
let x = 1n << BigInt(value.toString(2).length) / n + 1n;
|
|
239
|
-
const nMinus1 = n - 1n;
|
|
240
|
-
while (true) {
|
|
241
|
-
const nextX = (nMinus1 * x + value / x ** nMinus1) / n;
|
|
242
|
-
if (nextX >= x) break;
|
|
243
|
-
x = nextX;
|
|
244
|
-
}
|
|
245
|
-
return x;
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Calculates the n-th root of a Value (fraction).
|
|
249
|
-
*/
|
|
250
|
-
function nthRoot(v, root, precise = false, precisionDigits = 100) {
|
|
251
|
-
if (root === 0n) throw new DivisionByZeroError();
|
|
252
|
-
if (root === 1n) return v;
|
|
253
|
-
if (v.n < 0n) {
|
|
254
|
-
if (root % 2n === 0n) throw new InterpreterError(`${root}-th root of negative not supported.`);
|
|
255
|
-
const positiveResult = nthRoot({
|
|
256
|
-
n: -v.n,
|
|
257
|
-
d: v.d
|
|
258
|
-
}, root, precise, precisionDigits);
|
|
259
|
-
return {
|
|
260
|
-
n: -positiveResult.n,
|
|
261
|
-
d: positiveResult.d
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
if (v.n === 0n) return {
|
|
265
|
-
n: 0n,
|
|
266
|
-
d: 1n
|
|
267
|
-
};
|
|
268
|
-
if (precise) {
|
|
269
|
-
const rootN = iNthRoot(v.n, root);
|
|
270
|
-
const rootD = iNthRoot(v.d, root);
|
|
271
|
-
if (rootN ** root === v.n && rootD ** root === v.d) return simplify({
|
|
272
|
-
n: rootN,
|
|
273
|
-
d: rootD
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
const scaleFactor = 10n ** (BigInt(precisionDigits) * root);
|
|
277
|
-
return simplify({
|
|
278
|
-
n: iNthRoot(v.n * scaleFactor / v.d, root),
|
|
279
|
-
d: 10n ** BigInt(precisionDigits)
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
//#endregion
|
|
283
|
-
//#region src/lib/utils/sqrt.ts
|
|
284
|
-
/**
|
|
285
|
-
* Calculates the integer square root of a BigInt.
|
|
286
|
-
*/
|
|
287
|
-
function isqrt(value) {
|
|
288
|
-
if (value < 0n) throw new InterpreterError("Square root of negative not supported yet.");
|
|
289
|
-
if (value < 2n) return value;
|
|
290
|
-
let res = 0n;
|
|
291
|
-
let bit = 1n << BigInt(value.toString(2).length - 1 & -2);
|
|
292
|
-
while (bit !== 0n) {
|
|
293
|
-
if (value >= res + bit) {
|
|
294
|
-
value -= res + bit;
|
|
295
|
-
res = (res >> 1n) + bit;
|
|
296
|
-
} else res >>= 1n;
|
|
297
|
-
bit >>= 2n;
|
|
298
|
-
}
|
|
299
|
-
return res;
|
|
300
|
-
}
|
|
301
|
-
function isPerfectSquare(val) {
|
|
302
|
-
if (val < 0n) return [false, 0n];
|
|
303
|
-
const root = isqrt(val);
|
|
304
|
-
return [root * root === val, root];
|
|
305
|
-
}
|
|
306
|
-
function sqrt(v, precise = false, precisionDigits = 100) {
|
|
307
|
-
if (v.n < 0n) throw new InterpreterError("Square root of negative not supported yet.");
|
|
308
|
-
if (v.n === 0n) return {
|
|
309
|
-
n: 0n,
|
|
310
|
-
d: 1n
|
|
311
|
-
};
|
|
312
|
-
if (precise) {
|
|
313
|
-
const [nIsSquare, nRoot] = isPerfectSquare(v.n);
|
|
314
|
-
const [dIsSquare, dRoot] = isPerfectSquare(v.d);
|
|
315
|
-
if (nIsSquare && dIsSquare) return simplify({
|
|
316
|
-
n: nRoot,
|
|
317
|
-
d: dRoot
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
const scaleFactor = 10n ** BigInt(precisionDigits * 2);
|
|
321
|
-
return simplify({
|
|
322
|
-
n: isqrt(v.n * scaleFactor / v.d),
|
|
323
|
-
d: 10n ** BigInt(precisionDigits)
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
//#endregion
|
|
327
536
|
//#region src/lib/interpreter.ts
|
|
328
537
|
const precedence = {
|
|
329
538
|
LPAREN: 0,
|
|
@@ -343,80 +552,96 @@ const precedence = {
|
|
|
343
552
|
SQRT_FN: 8,
|
|
344
553
|
FACTORIAL: 10
|
|
345
554
|
};
|
|
346
|
-
function
|
|
347
|
-
return op === "
|
|
555
|
+
function isFunctionOp(op) {
|
|
556
|
+
return op === "ABS_FN" || op === "CEIL_FN" || op === "FLOOR_FN" || op === "SQRT_FN";
|
|
348
557
|
}
|
|
349
|
-
function
|
|
350
|
-
|
|
351
|
-
return {
|
|
352
|
-
n: BigInt(c.replace(".", "")),
|
|
353
|
-
d: 10n ** BigInt(c.length - c.indexOf(".") - 1)
|
|
354
|
-
};
|
|
558
|
+
function isUnaryOperation(op) {
|
|
559
|
+
return op === "UNARY_PLUS" || op === "UNARY_MINUS" || isFunctionOp(op);
|
|
355
560
|
}
|
|
356
561
|
/**
|
|
357
562
|
* Maximum allowed precision
|
|
358
563
|
*/
|
|
359
564
|
const MAX_PRECISION = 5e4;
|
|
360
565
|
/**
|
|
361
|
-
*
|
|
566
|
+
* Maximum allowed factorial
|
|
567
|
+
*/
|
|
568
|
+
const MAX_FACTORIAL = 2e4;
|
|
569
|
+
/**
|
|
570
|
+
* Maximum allowed exponent for scientific number notation
|
|
362
571
|
*/
|
|
363
|
-
const
|
|
572
|
+
const MAX_SCIENTIFIC_NUMBER_EXPONENT = 3e5;
|
|
364
573
|
function evaluate(tokens, options = {}) {
|
|
365
574
|
if (tokens.length === 0) throw new EmptyExpressionError();
|
|
366
|
-
const { format } = options;
|
|
367
575
|
const values = [];
|
|
368
576
|
const ops = [];
|
|
369
577
|
const applyOp = (pos) => {
|
|
370
578
|
const op = ops.pop();
|
|
371
579
|
if (!op || op === "LPAREN" || op === "ABS_OPEN") return;
|
|
372
580
|
const right = values.pop();
|
|
373
|
-
if (right === void 0) throw new UnexpectedEndOfExpressionError();
|
|
374
581
|
if (isUnaryOperation(op)) switch (op) {
|
|
375
582
|
case "UNARY_PLUS":
|
|
376
583
|
values.push(right);
|
|
377
584
|
return;
|
|
378
|
-
case "UNARY_MINUS":
|
|
585
|
+
case "UNARY_MINUS": {
|
|
586
|
+
const rN = right.n;
|
|
587
|
+
if (rN === "OVERFLOW") {
|
|
588
|
+
values.push(OverflowValue);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
379
591
|
values.push({
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
c: right.c
|
|
592
|
+
...right,
|
|
593
|
+
n: -rN
|
|
383
594
|
});
|
|
384
595
|
return;
|
|
596
|
+
}
|
|
385
597
|
case "ABS_FN": {
|
|
386
598
|
const rN = right.n;
|
|
599
|
+
if (rN === "OVERFLOW") {
|
|
600
|
+
values.push(OverflowValue);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const n = rN < 0 ? -rN : rN;
|
|
387
604
|
values.push({
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
c: right.c
|
|
605
|
+
...right,
|
|
606
|
+
n
|
|
391
607
|
});
|
|
392
608
|
return;
|
|
393
609
|
}
|
|
394
610
|
case "CEIL_FN":
|
|
611
|
+
if (right.n === "OVERFLOW") {
|
|
612
|
+
values.push(OverflowValue);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
395
615
|
values.push({
|
|
396
|
-
n: ceil(right),
|
|
616
|
+
n: ceil(toSimpleFraction(right)),
|
|
397
617
|
d: 1n
|
|
398
618
|
});
|
|
399
619
|
return;
|
|
400
620
|
case "FLOOR_FN":
|
|
621
|
+
if (right.n === "OVERFLOW") {
|
|
622
|
+
values.push(OverflowValue);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
401
625
|
values.push({
|
|
402
|
-
n: floor(right),
|
|
626
|
+
n: floor(toSimpleFraction(right)),
|
|
403
627
|
d: 1n
|
|
404
628
|
});
|
|
405
629
|
return;
|
|
406
|
-
case "SQRT_FN":
|
|
407
|
-
|
|
408
|
-
values.push(sqrt(right, format === "precise"));
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
const v = multiply(right, getConst(right.c));
|
|
412
|
-
values.push(sqrt(v, format === "precise"));
|
|
630
|
+
case "SQRT_FN":
|
|
631
|
+
values.push(sqrt(right, options));
|
|
413
632
|
return;
|
|
414
|
-
}
|
|
415
633
|
}
|
|
416
634
|
if (op === "FACTORIAL") {
|
|
635
|
+
if (right.n === "OVERFLOW") {
|
|
636
|
+
values.push(OverflowValue);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
417
639
|
const reduced = simplify(right);
|
|
418
|
-
if (reduced.d !== 1n || reduced.n <
|
|
419
|
-
if (reduced.n >=
|
|
640
|
+
if (reduced.d !== 1n || reduced.n < 0) throw new NotImplementedError("Factorial is only implemented for non-negative integers.", pos);
|
|
641
|
+
if (reduced.n >= MAX_FACTORIAL) {
|
|
642
|
+
values.push(OverflowValue);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
420
645
|
values.push({
|
|
421
646
|
n: factorial(reduced.n),
|
|
422
647
|
d: 1n
|
|
@@ -424,284 +649,151 @@ function evaluate(tokens, options = {}) {
|
|
|
424
649
|
return;
|
|
425
650
|
}
|
|
426
651
|
const left = values.pop();
|
|
427
|
-
if (left === void 0) throw new InsufficientOperandsError(pos);
|
|
428
|
-
let resN;
|
|
429
|
-
let resD;
|
|
430
|
-
let resC;
|
|
431
|
-
const lN = left.n;
|
|
432
|
-
const lD = left.d;
|
|
433
|
-
const lC = left.c;
|
|
434
|
-
const rN = right.n;
|
|
435
|
-
const rD = right.d;
|
|
436
|
-
const rC = right.c;
|
|
437
652
|
switch (op) {
|
|
438
653
|
case "ADD":
|
|
439
|
-
case "SUBTRACT":
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
resN = isSub ? -rN : rN;
|
|
443
|
-
resD = rD;
|
|
444
|
-
resC = rC;
|
|
445
|
-
break;
|
|
446
|
-
}
|
|
447
|
-
if (rN === 0n) {
|
|
448
|
-
resN = lN;
|
|
449
|
-
resD = lD;
|
|
450
|
-
resC = lC;
|
|
451
|
-
break;
|
|
452
|
-
}
|
|
453
|
-
if (lD === rD) {
|
|
454
|
-
resN = isSub ? lN - rN : lN + rN;
|
|
455
|
-
resD = lD;
|
|
456
|
-
} else {
|
|
457
|
-
const common = gcd(lD, rD);
|
|
458
|
-
if (common === 1n) {
|
|
459
|
-
resN = isSub ? lN * rD - rN * lD : lN * rD + rN * lD;
|
|
460
|
-
resD = lD * rD;
|
|
461
|
-
} else {
|
|
462
|
-
const mLeft = rD / common;
|
|
463
|
-
const mRight = lD / common;
|
|
464
|
-
resN = isSub ? lN * mLeft - rN * mRight : lN * mLeft + rN * mRight;
|
|
465
|
-
resD = lD * mLeft;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
if (lC === rC && resN !== 0n) resC = lC;
|
|
469
|
-
break;
|
|
470
|
-
}
|
|
654
|
+
case "SUBTRACT":
|
|
655
|
+
values.push(add(left, right, op === "SUBTRACT"));
|
|
656
|
+
return;
|
|
471
657
|
case "MULTIPLY":
|
|
472
|
-
case "IMPLICIT_MUL":
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
resN = lN / g1 * (rD / g2);
|
|
483
|
-
resD = lD / g2 * (rN / g1);
|
|
484
|
-
if (lC !== void 0 && rC === void 0) resC = lC;
|
|
485
|
-
break;
|
|
486
|
-
}
|
|
487
|
-
case "MOD": {
|
|
488
|
-
if (rN === 0n) throw new DivisionByZeroError();
|
|
489
|
-
const { n, d } = mod({
|
|
490
|
-
n: lN,
|
|
491
|
-
d: lD
|
|
492
|
-
}, {
|
|
493
|
-
n: rN,
|
|
494
|
-
d: rD
|
|
495
|
-
});
|
|
496
|
-
resN = n;
|
|
497
|
-
resD = d;
|
|
498
|
-
break;
|
|
499
|
-
}
|
|
500
|
-
case "EXP": {
|
|
501
|
-
const normalizedExponent = simplify(right);
|
|
502
|
-
let exponent = normalizedExponent.n;
|
|
503
|
-
if (exponent === 0n) {
|
|
504
|
-
resN = 1n;
|
|
505
|
-
resD = 1n;
|
|
506
|
-
break;
|
|
507
|
-
}
|
|
508
|
-
if (lN === 0n) {
|
|
509
|
-
if (rN < 0) throw new DivisionByZeroError();
|
|
510
|
-
resN = 0n;
|
|
511
|
-
resD = 1n;
|
|
512
|
-
break;
|
|
513
|
-
}
|
|
514
|
-
if (lN === lD && lC === void 0) {
|
|
515
|
-
resN = 1n;
|
|
516
|
-
resD = 1n;
|
|
517
|
-
break;
|
|
518
|
-
}
|
|
519
|
-
const exponentD = normalizedExponent.d;
|
|
520
|
-
if (exponentD === 2n) {
|
|
521
|
-
const rootResult = sqrt({
|
|
522
|
-
n: lN ** exponent,
|
|
523
|
-
d: lD ** exponent
|
|
524
|
-
}, format === "precise");
|
|
525
|
-
resN = rootResult.n;
|
|
526
|
-
resD = rootResult.d;
|
|
527
|
-
resC = rootResult.c;
|
|
528
|
-
break;
|
|
529
|
-
}
|
|
530
|
-
if (exponentD !== 1n) {
|
|
531
|
-
const rootResult = nthRoot({
|
|
532
|
-
n: lN ** exponent,
|
|
533
|
-
d: lD ** exponent
|
|
534
|
-
}, exponentD, format === "precise");
|
|
535
|
-
resN = rootResult.n;
|
|
536
|
-
resD = rootResult.d;
|
|
537
|
-
resC = rootResult.c;
|
|
538
|
-
break;
|
|
539
|
-
}
|
|
540
|
-
let baseN = lN;
|
|
541
|
-
let baseD = lD;
|
|
542
|
-
if (exponent < 0n) {
|
|
543
|
-
[baseN, baseD] = [baseD, baseN];
|
|
544
|
-
exponent = -exponent;
|
|
545
|
-
}
|
|
546
|
-
if (exponent === 1n) {
|
|
547
|
-
resN = baseN;
|
|
548
|
-
resD = baseD;
|
|
549
|
-
if (normalizedExponent.c === void 0) resC = lC;
|
|
550
|
-
break;
|
|
551
|
-
}
|
|
552
|
-
if (exponent > 1e4 && (baseN * exponent > 6e6 || baseD * exponent > 6e6)) throw new OverflowError();
|
|
553
|
-
if (!lC) {
|
|
554
|
-
resN = baseN ** exponent;
|
|
555
|
-
resD = baseD ** exponent;
|
|
556
|
-
break;
|
|
658
|
+
case "IMPLICIT_MUL":
|
|
659
|
+
values.push(multiply(left, right));
|
|
660
|
+
return;
|
|
661
|
+
case "DIVIDE":
|
|
662
|
+
values.push(divide(left, right));
|
|
663
|
+
return;
|
|
664
|
+
case "MOD":
|
|
665
|
+
if (left.n === "OVERFLOW" || right.n === "OVERFLOW") {
|
|
666
|
+
values.push(OverflowValue);
|
|
667
|
+
return;
|
|
557
668
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
669
|
+
values.push(mod(left, right));
|
|
670
|
+
return;
|
|
671
|
+
case "EXP":
|
|
672
|
+
values.push(exponentiate(left, right, options));
|
|
673
|
+
return;
|
|
563
674
|
}
|
|
564
|
-
if (resD > SIMPLIFY_THRESHOLD) values.push(simplify({
|
|
565
|
-
n: resN,
|
|
566
|
-
d: resD,
|
|
567
|
-
c: resC
|
|
568
|
-
}));
|
|
569
|
-
else values.push({
|
|
570
|
-
n: resN,
|
|
571
|
-
d: resD,
|
|
572
|
-
c: resC
|
|
573
|
-
});
|
|
574
675
|
};
|
|
575
676
|
const pushOpWithPrecedence = (currentOp, pos) => {
|
|
576
677
|
const isUnary = isUnaryOperation(currentOp);
|
|
577
678
|
const isRightAssociative = currentOp === "EXP" || currentOp === "IMPLICIT_MUL";
|
|
578
|
-
while (ops.length > 0 && !isUnary && (isRightAssociative ? precedence[ops
|
|
679
|
+
while (ops.length > 0 && !isUnary && (isRightAssociative ? precedence[ops.at(-1)] > precedence[currentOp] : precedence[ops.at(-1)] >= precedence[currentOp])) applyOp(pos);
|
|
579
680
|
ops.push(currentOp);
|
|
580
681
|
};
|
|
581
|
-
for (
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if (expValue >= 0n) n *= 10n ** expValue;
|
|
595
|
-
else d *= 10n ** -expValue;
|
|
682
|
+
for (const token of tokens) switch (token.type) {
|
|
683
|
+
case "NUMBER": {
|
|
684
|
+
const frac = token.fraction || "";
|
|
685
|
+
const fl = frac.length;
|
|
686
|
+
if (fl > MAX_PRECISION) throw new MaximumPrecisionError(fl, MAX_PRECISION);
|
|
687
|
+
let n = BigInt(token.whole + frac);
|
|
688
|
+
let d = fl === 0 ? 1n : 10n ** BigInt(fl);
|
|
689
|
+
if (token.exponent) {
|
|
690
|
+
if (token.exponent.length > MAX_PRECISION) throw new MaximumPrecisionError(token.exponent.length, MAX_PRECISION);
|
|
691
|
+
const expValue = BigInt(token.exponent);
|
|
692
|
+
if (expValue > MAX_SCIENTIFIC_NUMBER_EXPONENT) {
|
|
693
|
+
values.push(OverflowValue);
|
|
694
|
+
break;
|
|
596
695
|
}
|
|
597
|
-
if (
|
|
598
|
-
|
|
599
|
-
d: 1n
|
|
600
|
-
});
|
|
601
|
-
else values.push(simplify({
|
|
602
|
-
n,
|
|
603
|
-
d
|
|
604
|
-
}));
|
|
605
|
-
break;
|
|
696
|
+
if (expValue >= 0) n *= 10n ** expValue;
|
|
697
|
+
else d *= 10n ** -expValue;
|
|
606
698
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
699
|
+
if (d === 1n) values.push({
|
|
700
|
+
n,
|
|
701
|
+
d: 1n
|
|
702
|
+
});
|
|
703
|
+
else values.push({
|
|
704
|
+
n,
|
|
705
|
+
d
|
|
706
|
+
});
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
case "CONST":
|
|
710
|
+
if (options.format === "precise") values.push({
|
|
711
|
+
n: 1n,
|
|
712
|
+
d: 1n,
|
|
713
|
+
c: token.id
|
|
714
|
+
});
|
|
715
|
+
else values.push(getConst(token.id));
|
|
716
|
+
break;
|
|
717
|
+
case "LPAREN":
|
|
718
|
+
case "ABS_OPEN":
|
|
719
|
+
ops.push(token.type);
|
|
720
|
+
break;
|
|
721
|
+
case "RPAREN": {
|
|
722
|
+
let foundMatch = false;
|
|
723
|
+
while (ops.length > 0) {
|
|
724
|
+
if (ops.at(-1) === "LPAREN") {
|
|
725
|
+
foundMatch = true;
|
|
726
|
+
break;
|
|
626
727
|
}
|
|
627
|
-
|
|
628
|
-
ops.pop();
|
|
629
|
-
break;
|
|
728
|
+
applyOp(token.pos);
|
|
630
729
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
}
|
|
643
|
-
if (!foundMatch) throw new InterpreterError("Mismatched absolute value pipe", token.pos);
|
|
644
|
-
ops.pop();
|
|
645
|
-
const val = values.pop();
|
|
646
|
-
if (val === void 0) throw new UnexpectedEndOfExpressionError();
|
|
647
|
-
values.push({
|
|
648
|
-
n: val.n < 0n ? -val.n : val.n,
|
|
649
|
-
d: val.d,
|
|
650
|
-
c: val.c
|
|
651
|
-
});
|
|
652
|
-
break;
|
|
730
|
+
if (!foundMatch) throw new MismatchedParenthesisError(token.pos);
|
|
731
|
+
ops.pop();
|
|
732
|
+
const nextOp = ops.at(-1);
|
|
733
|
+
if (!nextOp) break;
|
|
734
|
+
if (isFunctionOp(nextOp)) applyOp(token.pos);
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
737
|
+
case "ABS_CLOSE": {
|
|
738
|
+
while (ops.length > 0) {
|
|
739
|
+
if (ops.at(-1) === "ABS_OPEN") break;
|
|
740
|
+
applyOp(token.pos);
|
|
653
741
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
pushOpWithPrecedence("SUBTRACT", token.pos);
|
|
659
|
-
break;
|
|
660
|
-
case "MUL":
|
|
661
|
-
pushOpWithPrecedence("MULTIPLY", token.pos);
|
|
662
|
-
break;
|
|
663
|
-
case "DIV":
|
|
664
|
-
pushOpWithPrecedence("DIVIDE", token.pos);
|
|
665
|
-
break;
|
|
666
|
-
case "POW":
|
|
667
|
-
pushOpWithPrecedence("EXP", token.pos);
|
|
668
|
-
break;
|
|
669
|
-
case "UNARY_PLUS":
|
|
670
|
-
pushOpWithPrecedence("UNARY_PLUS", token.pos);
|
|
671
|
-
break;
|
|
672
|
-
case "UNARY_MINUS":
|
|
673
|
-
pushOpWithPrecedence("UNARY_MINUS", token.pos);
|
|
674
|
-
break;
|
|
675
|
-
case "IMPLICIT_MUL":
|
|
676
|
-
pushOpWithPrecedence("IMPLICIT_MUL", token.pos);
|
|
677
|
-
break;
|
|
678
|
-
case "FACTORIAL":
|
|
679
|
-
pushOpWithPrecedence("FACTORIAL", token.pos);
|
|
680
|
-
break;
|
|
681
|
-
case "MOD":
|
|
682
|
-
pushOpWithPrecedence("MOD", token.pos);
|
|
683
|
-
break;
|
|
684
|
-
case "FUNC":
|
|
685
|
-
switch (token.id) {
|
|
686
|
-
case "abs":
|
|
687
|
-
pushOpWithPrecedence("ABS_FN", token.pos);
|
|
688
|
-
break;
|
|
689
|
-
case "ceil":
|
|
690
|
-
pushOpWithPrecedence("CEIL_FN", token.pos);
|
|
691
|
-
break;
|
|
692
|
-
case "floor":
|
|
693
|
-
pushOpWithPrecedence("FLOOR_FN", token.pos);
|
|
694
|
-
break;
|
|
695
|
-
case "sqrt":
|
|
696
|
-
pushOpWithPrecedence("SQRT_FN", token.pos);
|
|
697
|
-
break;
|
|
698
|
-
}
|
|
742
|
+
ops.pop();
|
|
743
|
+
const val = values.pop();
|
|
744
|
+
if (val.n === "OVERFLOW") {
|
|
745
|
+
values.push(OverflowValue);
|
|
699
746
|
break;
|
|
747
|
+
}
|
|
748
|
+
values.push({
|
|
749
|
+
n: val.n < 0 ? -val.n : val.n,
|
|
750
|
+
d: val.d,
|
|
751
|
+
c: val.c
|
|
752
|
+
});
|
|
753
|
+
break;
|
|
700
754
|
}
|
|
755
|
+
case "PLUS":
|
|
756
|
+
pushOpWithPrecedence("ADD", token.pos);
|
|
757
|
+
break;
|
|
758
|
+
case "MINUS":
|
|
759
|
+
pushOpWithPrecedence("SUBTRACT", token.pos);
|
|
760
|
+
break;
|
|
761
|
+
case "MUL":
|
|
762
|
+
pushOpWithPrecedence("MULTIPLY", token.pos);
|
|
763
|
+
break;
|
|
764
|
+
case "DIV":
|
|
765
|
+
pushOpWithPrecedence("DIVIDE", token.pos);
|
|
766
|
+
break;
|
|
767
|
+
case "POW":
|
|
768
|
+
pushOpWithPrecedence("EXP", token.pos);
|
|
769
|
+
break;
|
|
770
|
+
case "UNARY_PLUS":
|
|
771
|
+
case "UNARY_MINUS":
|
|
772
|
+
case "IMPLICIT_MUL":
|
|
773
|
+
case "FACTORIAL":
|
|
774
|
+
case "MOD":
|
|
775
|
+
pushOpWithPrecedence(token.type, token.pos);
|
|
776
|
+
break;
|
|
777
|
+
case "FUNC":
|
|
778
|
+
switch (token.id) {
|
|
779
|
+
case "abs":
|
|
780
|
+
pushOpWithPrecedence("ABS_FN", token.pos);
|
|
781
|
+
break;
|
|
782
|
+
case "ceil":
|
|
783
|
+
pushOpWithPrecedence("CEIL_FN", token.pos);
|
|
784
|
+
break;
|
|
785
|
+
case "floor":
|
|
786
|
+
pushOpWithPrecedence("FLOOR_FN", token.pos);
|
|
787
|
+
break;
|
|
788
|
+
case "sqrt":
|
|
789
|
+
pushOpWithPrecedence("SQRT_FN", token.pos);
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
break;
|
|
701
793
|
}
|
|
702
794
|
while (ops.length > 0) {
|
|
703
|
-
const top = ops
|
|
704
|
-
const lastPos = tokens
|
|
795
|
+
const top = ops.at(-1);
|
|
796
|
+
const lastPos = tokens.at(-1)?.pos ?? 0;
|
|
705
797
|
if (top === "LPAREN") {
|
|
706
798
|
ops.pop();
|
|
707
799
|
continue;
|
|
@@ -709,14 +801,16 @@ function evaluate(tokens, options = {}) {
|
|
|
709
801
|
applyOp(lastPos);
|
|
710
802
|
}
|
|
711
803
|
const finalValue = values.pop();
|
|
712
|
-
if (finalValue ===
|
|
804
|
+
if (finalValue.n === "OVERFLOW") throw new OverflowError();
|
|
713
805
|
return simplify(finalValue);
|
|
714
806
|
}
|
|
715
807
|
//#endregion
|
|
716
808
|
//#region src/lib/lexer.ts
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
809
|
+
var LexerError = class extends PicocalcErrror {
|
|
810
|
+
constructor(message, pos) {
|
|
811
|
+
super("LexerError", message, pos);
|
|
812
|
+
}
|
|
813
|
+
};
|
|
720
814
|
function isDigit(ch) {
|
|
721
815
|
if (ch === void 0) return false;
|
|
722
816
|
return ch >= "0" && ch <= "9";
|
|
@@ -726,49 +820,52 @@ function isAlpha(ch) {
|
|
|
726
820
|
return ch >= "a" && ch <= "z";
|
|
727
821
|
}
|
|
728
822
|
/**
|
|
823
|
+
* Maximum allowed expression length
|
|
824
|
+
*/
|
|
825
|
+
const MAX_EXPRESSION_LENGTH = 75e6;
|
|
826
|
+
const CHAR_MAP = {
|
|
827
|
+
"(": "LPAREN",
|
|
828
|
+
")": "RPAREN",
|
|
829
|
+
"+": "PLUS",
|
|
830
|
+
"-": "MINUS",
|
|
831
|
+
"*": "MUL",
|
|
832
|
+
"/": "DIV",
|
|
833
|
+
"^": "POW",
|
|
834
|
+
"!": "FACTORIAL",
|
|
835
|
+
"|": "PIPE",
|
|
836
|
+
"%": "MOD"
|
|
837
|
+
};
|
|
838
|
+
/**
|
|
729
839
|
* Simple lexer (tokenizer).
|
|
730
840
|
*/
|
|
731
841
|
function tokenize(expression, options = {}) {
|
|
732
842
|
const { decimalSeparator = "." } = options;
|
|
733
843
|
const tokens = [];
|
|
734
|
-
let index = 0;
|
|
735
844
|
const length = expression.length;
|
|
736
|
-
|
|
845
|
+
if (length > MAX_EXPRESSION_LENGTH) throw new LexerError(`Expression length (${length}) exceeds the maximum allowed limit of ${MAX_EXPRESSION_LENGTH}`, 0);
|
|
846
|
+
for (let index = 0; index < length; index++) {
|
|
737
847
|
const ch = expression[index];
|
|
738
|
-
const
|
|
739
|
-
if (
|
|
740
|
-
index++;
|
|
741
|
-
continue;
|
|
742
|
-
}
|
|
848
|
+
const pos = index;
|
|
849
|
+
if (ch === " ") continue;
|
|
743
850
|
if (isDigit(ch) || ch === decimalSeparator) {
|
|
744
851
|
let whole = "";
|
|
745
852
|
let fraction = void 0;
|
|
746
853
|
let exponent = void 0;
|
|
854
|
+
const consumeDigits = () => {
|
|
855
|
+
const start = index;
|
|
856
|
+
while (index < length && isDigit(expression[index])) index++;
|
|
857
|
+
return expression.slice(start, index);
|
|
858
|
+
};
|
|
747
859
|
if (ch === decimalSeparator) {
|
|
748
860
|
whole = "0";
|
|
749
861
|
index++;
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
while (index < length && isDigit(expression[index])) {
|
|
753
|
-
fracBuffer += expression[index];
|
|
754
|
-
index++;
|
|
755
|
-
}
|
|
756
|
-
if (index === fracStart) throw new LexerError("Expected digit after decimal separator", index);
|
|
757
|
-
fraction = fracBuffer;
|
|
862
|
+
fraction = consumeDigits();
|
|
863
|
+
if (fraction.length === 0) throw new LexerError("Expected digit after decimal separator", index);
|
|
758
864
|
} else {
|
|
759
|
-
|
|
760
|
-
whole += expression[index];
|
|
761
|
-
index++;
|
|
762
|
-
}
|
|
865
|
+
whole = consumeDigits();
|
|
763
866
|
if (index < length && expression[index] === decimalSeparator) {
|
|
764
867
|
index++;
|
|
765
|
-
|
|
766
|
-
const fracStart = index;
|
|
767
|
-
while (index < length && isDigit(expression[index])) {
|
|
768
|
-
fracBuffer += expression[index];
|
|
769
|
-
index++;
|
|
770
|
-
}
|
|
771
|
-
fraction = index === fracStart ? "0" : fracBuffer;
|
|
868
|
+
fraction = consumeDigits();
|
|
772
869
|
}
|
|
773
870
|
}
|
|
774
871
|
if (index < length && (expression[index] === "e" || expression[index] === "E")) {
|
|
@@ -781,115 +878,37 @@ function tokenize(expression, options = {}) {
|
|
|
781
878
|
}
|
|
782
879
|
if (lookahead < length && isDigit(expression[lookahead])) {
|
|
783
880
|
index = lookahead;
|
|
784
|
-
|
|
785
|
-
while (index < length && isDigit(expression[index])) {
|
|
786
|
-
expBuffer += expression[index];
|
|
787
|
-
index++;
|
|
788
|
-
}
|
|
789
|
-
exponent = expBuffer;
|
|
881
|
+
exponent = sign + consumeDigits();
|
|
790
882
|
}
|
|
791
883
|
}
|
|
792
884
|
if (index < length && expression[index] === decimalSeparator) throw new LexerError("Invalid decimal number format", index);
|
|
885
|
+
index--;
|
|
793
886
|
tokens.push({
|
|
794
887
|
type: "NUMBER",
|
|
795
888
|
whole,
|
|
796
889
|
fraction,
|
|
797
890
|
exponent,
|
|
798
|
-
pos
|
|
891
|
+
pos
|
|
799
892
|
});
|
|
800
893
|
continue;
|
|
801
894
|
}
|
|
802
895
|
if (isAlpha(ch)) {
|
|
803
896
|
let id = "";
|
|
804
897
|
while (index < length && isAlpha(expression[index])) id += expression[index++];
|
|
898
|
+
index--;
|
|
805
899
|
tokens.push({
|
|
806
900
|
type: "IDENTIFIER",
|
|
807
901
|
id,
|
|
808
|
-
pos
|
|
809
|
-
});
|
|
810
|
-
continue;
|
|
811
|
-
}
|
|
812
|
-
if (ch === "(") {
|
|
813
|
-
tokens.push({
|
|
814
|
-
type: "LPAREN",
|
|
815
|
-
pos: startPos
|
|
816
|
-
});
|
|
817
|
-
index++;
|
|
818
|
-
continue;
|
|
819
|
-
}
|
|
820
|
-
if (ch === ")") {
|
|
821
|
-
tokens.push({
|
|
822
|
-
type: "RPAREN",
|
|
823
|
-
pos: startPos
|
|
824
|
-
});
|
|
825
|
-
index++;
|
|
826
|
-
continue;
|
|
827
|
-
}
|
|
828
|
-
if (ch === "+") {
|
|
829
|
-
tokens.push({
|
|
830
|
-
type: "PLUS",
|
|
831
|
-
pos: startPos
|
|
832
|
-
});
|
|
833
|
-
index++;
|
|
834
|
-
continue;
|
|
835
|
-
}
|
|
836
|
-
if (ch === "-") {
|
|
837
|
-
tokens.push({
|
|
838
|
-
type: "MINUS",
|
|
839
|
-
pos: startPos
|
|
840
|
-
});
|
|
841
|
-
index++;
|
|
842
|
-
continue;
|
|
843
|
-
}
|
|
844
|
-
if (ch === "*") {
|
|
845
|
-
tokens.push({
|
|
846
|
-
type: "MUL",
|
|
847
|
-
pos: startPos
|
|
902
|
+
pos
|
|
848
903
|
});
|
|
849
|
-
index++;
|
|
850
904
|
continue;
|
|
851
905
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
continue;
|
|
859
|
-
}
|
|
860
|
-
if (ch === "^") {
|
|
861
|
-
tokens.push({
|
|
862
|
-
type: "POW",
|
|
863
|
-
pos: startPos
|
|
864
|
-
});
|
|
865
|
-
index++;
|
|
866
|
-
continue;
|
|
867
|
-
}
|
|
868
|
-
if (ch === "!") {
|
|
869
|
-
tokens.push({
|
|
870
|
-
type: "FACTORIAL",
|
|
871
|
-
pos: startPos
|
|
872
|
-
});
|
|
873
|
-
index++;
|
|
874
|
-
continue;
|
|
875
|
-
}
|
|
876
|
-
if (ch === "|") {
|
|
877
|
-
tokens.push({
|
|
878
|
-
type: "PIPE",
|
|
879
|
-
pos: startPos
|
|
880
|
-
});
|
|
881
|
-
index++;
|
|
882
|
-
continue;
|
|
883
|
-
}
|
|
884
|
-
if (ch === "%") {
|
|
885
|
-
tokens.push({
|
|
886
|
-
type: "MOD",
|
|
887
|
-
pos: startPos
|
|
888
|
-
});
|
|
889
|
-
index++;
|
|
890
|
-
continue;
|
|
891
|
-
}
|
|
892
|
-
throw new LexerError(`Unexpected character '${ch}'`, index);
|
|
906
|
+
const type = CHAR_MAP[ch];
|
|
907
|
+
if (!type) throw new LexerError(`Unexpected character '${ch}'`, index);
|
|
908
|
+
tokens.push({
|
|
909
|
+
type,
|
|
910
|
+
pos
|
|
911
|
+
});
|
|
893
912
|
}
|
|
894
913
|
return tokens;
|
|
895
914
|
}
|
|
@@ -915,7 +934,7 @@ const symbolMap = {
|
|
|
915
934
|
const prettifyNumber = (t) => t.fraction ? `${t.whole}.${t.fraction}` : t.whole;
|
|
916
935
|
function getSym(t) {
|
|
917
936
|
if (t.type === "NUMBER") return prettifyNumber(t);
|
|
918
|
-
if (t.type === "IDENTIFIER") return t.id;
|
|
937
|
+
if (t.type === "IDENTIFIER" || t.type === "FUNC" || t.type === "CONST") return t.id;
|
|
919
938
|
return symbolMap[t.type];
|
|
920
939
|
}
|
|
921
940
|
//#endregion
|
|
@@ -930,22 +949,35 @@ function isOperand(last) {
|
|
|
930
949
|
function isBinaryOperator(token) {
|
|
931
950
|
return token.type === "MUL" || token.type === "DIV" || token.type === "POW" || token.type === "MOD";
|
|
932
951
|
}
|
|
933
|
-
/**
|
|
934
|
-
* Helper: Does the current context allow a +/- to be unary?
|
|
935
|
-
*/
|
|
936
952
|
function isUnaryContext(last) {
|
|
937
953
|
return !isOperand(last);
|
|
938
954
|
}
|
|
955
|
+
var ParserError = class extends PicocalcErrror {
|
|
956
|
+
constructor(message, pos) {
|
|
957
|
+
super("ParserError", message, pos);
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
var IncompleteExpressionError = class extends ParserError {
|
|
961
|
+
constructor(message, pos) {
|
|
962
|
+
super(`Incomplete expression: ${message}`, pos);
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
var UnexpectedOperatorError = class extends ParserError {
|
|
966
|
+
constructor(message, pos) {
|
|
967
|
+
super(`Unexpected operator: ${message}`, pos);
|
|
968
|
+
}
|
|
969
|
+
};
|
|
939
970
|
function resolveIdentifier(token) {
|
|
971
|
+
const pos = token.pos;
|
|
940
972
|
if (token.id === "pi" || token.id === "e") return {
|
|
941
973
|
type: "CONST",
|
|
942
974
|
id: token.id,
|
|
943
|
-
pos
|
|
975
|
+
pos
|
|
944
976
|
};
|
|
945
977
|
if (token.id === "abs" || token.id === "ceil" || token.id === "floor" || token.id === "sqrt") return {
|
|
946
978
|
type: "FUNC",
|
|
947
979
|
id: token.id,
|
|
948
|
-
pos
|
|
980
|
+
pos
|
|
949
981
|
};
|
|
950
982
|
throw new ParserError(`Unknown identifier '${token.id}'`, token.pos);
|
|
951
983
|
}
|
|
@@ -957,107 +989,106 @@ function parse(tokens) {
|
|
|
957
989
|
let token = tokens[i];
|
|
958
990
|
const prev = tokens[i - 1];
|
|
959
991
|
if (token.type === "IDENTIFIER") token = resolveIdentifier(token);
|
|
960
|
-
|
|
992
|
+
const pos = token.pos;
|
|
993
|
+
const isPrevOperand = isOperand(result.at(-1));
|
|
961
994
|
if (token.type === "PIPE") {
|
|
962
|
-
if (absStack > 0 &&
|
|
995
|
+
if (absStack > 0 && isPrevOperand) {
|
|
963
996
|
result.push({
|
|
964
997
|
type: "ABS_CLOSE",
|
|
965
|
-
pos
|
|
998
|
+
pos
|
|
966
999
|
});
|
|
967
1000
|
absStack--;
|
|
968
|
-
|
|
969
|
-
if (isOperand(prevParsed)) result.push({
|
|
970
|
-
type: "IMPLICIT_MUL",
|
|
971
|
-
pos: token.pos
|
|
972
|
-
});
|
|
973
|
-
result.push({
|
|
974
|
-
type: "ABS_OPEN",
|
|
975
|
-
pos: token.pos
|
|
976
|
-
});
|
|
977
|
-
absStack++;
|
|
1001
|
+
continue;
|
|
978
1002
|
}
|
|
979
|
-
|
|
980
|
-
}
|
|
981
|
-
if (isOperand(prevParsed)) {
|
|
982
|
-
if (token.type === "LPAREN" || token.type === "FUNC" || token.type === "NUMBER" || token.type === "CONST") result.push({
|
|
1003
|
+
if (isPrevOperand) result.push({
|
|
983
1004
|
type: "IMPLICIT_MUL",
|
|
984
|
-
pos
|
|
1005
|
+
pos
|
|
985
1006
|
});
|
|
1007
|
+
result.push({
|
|
1008
|
+
type: "ABS_OPEN",
|
|
1009
|
+
pos
|
|
1010
|
+
});
|
|
1011
|
+
absStack++;
|
|
1012
|
+
continue;
|
|
986
1013
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
if (prevParsed?.type === "LPAREN") throw new ParserError("Unexpected ')' after '('", token.pos);
|
|
1014
|
+
if ((token.type === "LPAREN" || token.type === "FUNC" || token.type === "NUMBER" || token.type === "CONST") && isPrevOperand) result.push({
|
|
1015
|
+
type: "IMPLICIT_MUL",
|
|
1016
|
+
pos
|
|
1017
|
+
});
|
|
1018
|
+
const prevParsed = result.at(-1);
|
|
1019
|
+
if (isBinaryOperator(token) && isUnaryContext(prevParsed)) throw new UnexpectedOperatorError(`Unexpected operator '${getSym(token)}'`, token.pos);
|
|
1020
|
+
if ((token.type === "FACTORIAL" || token.type === "RPAREN") && isUnaryContext(prevParsed)) {
|
|
1021
|
+
if (!prevParsed) throw new UnexpectedOperatorError(`Unexpected '${getSym(token)}' at the beginning of expression`, token.pos);
|
|
1022
|
+
throw new UnexpectedOperatorError(`Unexpected '${getSym(token)}' after '${getSym(prevParsed)}'`, token.pos);
|
|
997
1023
|
}
|
|
998
1024
|
if (prev && (prev.type === "NUMBER" || prev.type === "IDENTIFIER" && resolveIdentifier(prev).type === "CONST") && token.type === "NUMBER") throw new ParserError("Missing operator between numbers", token.pos);
|
|
999
1025
|
if ((token.type === "PLUS" || token.type === "MINUS") && isUnaryContext(prevParsed)) result.push({
|
|
1000
1026
|
type: token.type === "PLUS" ? "UNARY_PLUS" : "UNARY_MINUS",
|
|
1001
|
-
pos
|
|
1027
|
+
pos
|
|
1002
1028
|
});
|
|
1003
1029
|
else result.push(token);
|
|
1004
1030
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1031
|
+
const last = result.at(-1);
|
|
1032
|
+
if (absStack > 0) throw new ParserError("Unclosed absolute value '|'", last?.pos ?? 0);
|
|
1007
1033
|
if (last && last.type !== "CONST" && (isBinaryOperator(last) || [
|
|
1008
1034
|
"PLUS",
|
|
1009
1035
|
"MINUS",
|
|
1010
1036
|
"UNARY_PLUS",
|
|
1011
1037
|
"UNARY_MINUS",
|
|
1012
1038
|
"FUNC",
|
|
1013
|
-
"ABS_OPEN"
|
|
1039
|
+
"ABS_OPEN",
|
|
1040
|
+
"LPAREN"
|
|
1014
1041
|
].includes(last.type))) throw new IncompleteExpressionError(`trailing operator '${last.type === "FUNC" ? last.id : getSym(last)}'`, last.pos);
|
|
1015
1042
|
return result;
|
|
1016
1043
|
}
|
|
1017
1044
|
//#endregion
|
|
1018
1045
|
//#region src/utils/format-result.ts
|
|
1019
|
-
|
|
1020
|
-
if (
|
|
1021
|
-
if (
|
|
1022
|
-
|
|
1023
|
-
if (
|
|
1024
|
-
|
|
1025
|
-
|
|
1046
|
+
function getConstantStr(coeff, c, e) {
|
|
1047
|
+
if (coeff === 0n) return "0";
|
|
1048
|
+
if (!c) return `${coeff}`;
|
|
1049
|
+
let constantStr = c;
|
|
1050
|
+
if (e) {
|
|
1051
|
+
const num = e.n;
|
|
1052
|
+
const den = e.d ?? 1n;
|
|
1053
|
+
if (num !== den) {
|
|
1054
|
+
const expStr = den === 1n ? `${num}` : `(${num}/${den})`;
|
|
1055
|
+
constantStr = den === 2n * num ? `sqrt(${c})` : `${c}^${expStr}`;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
if (coeff === 1n) return constantStr;
|
|
1059
|
+
if (coeff === -1n) return `-${constantStr}`;
|
|
1060
|
+
return `${coeff}${constantStr}`;
|
|
1061
|
+
}
|
|
1062
|
+
function formatPrecise(v) {
|
|
1063
|
+
const { n, d, c, e } = v;
|
|
1064
|
+
if (n === 0n) return "0";
|
|
1065
|
+
const isNegative = n < 0;
|
|
1066
|
+
const absN = isNegative ? -n : n;
|
|
1067
|
+
const numSign = isNegative ? "-" : "";
|
|
1068
|
+
if (c && e && e.n < 0n) return `${numSign}${absN}/${getConstantStr(d, c, {
|
|
1069
|
+
n: -e.n,
|
|
1070
|
+
d: e.d
|
|
1071
|
+
})}`;
|
|
1072
|
+
const numeratorStr = getConstantStr(absN, c, e);
|
|
1073
|
+
if (d === 1n) return `${numSign}${numeratorStr}`;
|
|
1074
|
+
return `${numSign}${numeratorStr}/${d}`;
|
|
1075
|
+
}
|
|
1026
1076
|
/**
|
|
1027
1077
|
* Converts Result to a Decimal or Fraction String.
|
|
1028
1078
|
*/
|
|
1029
1079
|
function formatResult(v, options = {}) {
|
|
1030
|
-
const { n, d
|
|
1031
|
-
if (
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
const
|
|
1038
|
-
const
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
const res = (isNegative ? "-" : "") + integerPart;
|
|
1043
|
-
return res === "-0" ? "0" : res;
|
|
1044
|
-
}
|
|
1045
|
-
let fractionalPart = "";
|
|
1046
|
-
let count = 0;
|
|
1047
|
-
while (remainder !== 0n && count < maxDecimals) {
|
|
1048
|
-
remainder *= 10n;
|
|
1049
|
-
fractionalPart += (remainder / d).toString();
|
|
1050
|
-
remainder %= d;
|
|
1051
|
-
count++;
|
|
1052
|
-
}
|
|
1053
|
-
let lastNonZero = -1;
|
|
1054
|
-
for (let i = fractionalPart.length - 1; i >= 0; i--) if (fractionalPart[i] !== "0") {
|
|
1055
|
-
lastNonZero = i;
|
|
1056
|
-
break;
|
|
1057
|
-
}
|
|
1058
|
-
const finalFraction = lastNonZero === -1 ? "" : fractionalPart.slice(0, lastNonZero + 1);
|
|
1059
|
-
const result = finalFraction === "" ? integerPart : `${integerPart}.${finalFraction}`;
|
|
1060
|
-
return (isNegative && result !== "0" ? "-" : "") + result;
|
|
1080
|
+
const { n, d } = v;
|
|
1081
|
+
if (options.format === "precise") return formatPrecise(v);
|
|
1082
|
+
const { maxDecimals = 30, roundingMode = "round" } = options;
|
|
1083
|
+
const isNegative = n < 0;
|
|
1084
|
+
const factor = 10n ** BigInt(maxDecimals);
|
|
1085
|
+
let resultStr = (((isNegative ? -n : n) * factor + (roundingMode === "round" ? d / 2n : 0n)) / d).toString();
|
|
1086
|
+
if (resultStr.length <= maxDecimals) resultStr = resultStr.padStart(maxDecimals + 1, "0");
|
|
1087
|
+
const splitIdx = resultStr.length - maxDecimals;
|
|
1088
|
+
const integerPart = resultStr.slice(0, splitIdx);
|
|
1089
|
+
const fractionalPart = resultStr.slice(splitIdx).replace(/0+$/, "");
|
|
1090
|
+
const finalResult = fractionalPart ? `${integerPart}.${fractionalPart}` : integerPart;
|
|
1091
|
+
return (isNegative && finalResult !== "0" ? "-" : "") + finalResult;
|
|
1061
1092
|
}
|
|
1062
1093
|
//#endregion
|
|
1063
1094
|
//#region src/index.ts
|
|
@@ -1068,4 +1099,4 @@ function calculate(expression, options) {
|
|
|
1068
1099
|
return formatResult(evaluate(parse(tokenize(expression, options)), options), options);
|
|
1069
1100
|
}
|
|
1070
1101
|
//#endregion
|
|
1071
|
-
export { calculate };
|
|
1102
|
+
export { PicocalcErrror, calculate };
|