picocalc 0.0.4 → 0.0.6
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/index.d.mts +4 -9
- package/dist/index.mjs +612 -292
- package/package.json +8 -8
package/dist/index.d.mts
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import "bigint.bitlength";
|
|
2
|
-
|
|
2
|
+
type RoundingMode = "round" | "truncate";
|
|
3
3
|
interface PrecisionOptions {
|
|
4
4
|
readonly format?: "decimal" | "precise";
|
|
5
5
|
readonly maxDecimals?: number;
|
|
6
|
-
readonly roundingMode?:
|
|
6
|
+
readonly roundingMode?: RoundingMode;
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
//#region src/lib/errors.d.ts
|
|
10
|
-
declare class PicocalcError extends Error {
|
|
8
|
+
export declare class PicocalcError extends Error {
|
|
11
9
|
pos?: number;
|
|
12
10
|
constructor(name: string, message: string, pos?: number);
|
|
13
11
|
}
|
|
14
|
-
//#endregion
|
|
15
|
-
//#region src/index.d.ts
|
|
16
12
|
interface CalculateOptions extends PrecisionOptions {
|
|
17
13
|
decimalSeparator?: "." | ",";
|
|
18
14
|
}
|
|
@@ -20,5 +16,4 @@ interface CalculateOptions extends PrecisionOptions {
|
|
|
20
16
|
* The main entry point for the library.
|
|
21
17
|
*/
|
|
22
18
|
declare function calculate(expression: string, options?: CalculateOptions): string;
|
|
23
|
-
|
|
24
|
-
export { PicocalcError, calculate };
|
|
19
|
+
export { calculate };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "bigint.bitlength";
|
|
2
|
-
|
|
2
|
+
// src/lib/errors.ts
|
|
3
3
|
var PicocalcError = class extends Error {
|
|
4
4
|
pos;
|
|
5
5
|
constructor(name, message, pos) {
|
|
@@ -39,39 +39,50 @@ var EmptyExpressionError = class extends InterpreterError {
|
|
|
39
39
|
super("Empty expression");
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
|
-
var
|
|
43
|
-
constructor(
|
|
44
|
-
super(message
|
|
42
|
+
var InterpreterRangeError = class extends InterpreterError {
|
|
43
|
+
constructor(message) {
|
|
44
|
+
super(message);
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
const ROUNDING_MODE = "round";
|
|
48
|
+
function getBitPrecision(maxDecimals = 30) {
|
|
49
|
+
return BigInt(maxDecimals * 4 + 32);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Maximum allowed precision
|
|
53
|
+
*/
|
|
54
|
+
const MAX_PRECISION = 5e4;
|
|
55
|
+
// src/lib/types.ts
|
|
49
56
|
const OverflowValue = { n: "OVERFLOW" };
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
n: 1n,
|
|
60
|
-
d: 1n
|
|
61
|
-
};
|
|
62
|
-
const constants = {
|
|
63
|
-
e: E,
|
|
64
|
-
pi: PI
|
|
65
|
-
};
|
|
66
|
-
function getConst(id) {
|
|
67
|
-
const c = constants[id];
|
|
57
|
+
// src/lib/utils/e.ts
|
|
58
|
+
function splitSeries(a, b) {
|
|
59
|
+
if (b - a === 1n) return {
|
|
60
|
+
n: 1n,
|
|
61
|
+
d: b
|
|
62
|
+
};
|
|
63
|
+
const mid = (a + b) / 2n;
|
|
64
|
+
const left = splitSeries(a, mid);
|
|
65
|
+
const right = splitSeries(mid, b);
|
|
68
66
|
return {
|
|
69
|
-
n:
|
|
70
|
-
d:
|
|
67
|
+
n: left.n * right.d + right.n,
|
|
68
|
+
d: left.d * right.d
|
|
71
69
|
};
|
|
72
70
|
}
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
function computeE(maxDecimals) {
|
|
72
|
+
let b = 1n;
|
|
73
|
+
let m = 1n;
|
|
74
|
+
const t = 1n << getBitPrecision(maxDecimals);
|
|
75
|
+
while (m < t) {
|
|
76
|
+
b++;
|
|
77
|
+
m *= b;
|
|
78
|
+
}
|
|
79
|
+
const { n, d } = splitSeries(0n, b);
|
|
80
|
+
return {
|
|
81
|
+
n: d + n,
|
|
82
|
+
d
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// src/lib/utils/gcd.ts
|
|
75
86
|
/**
|
|
76
87
|
* The Euclidean GCD Algorithm for BigInt.
|
|
77
88
|
*/
|
|
@@ -88,9 +99,8 @@ function gcd(a, b) {
|
|
|
88
99
|
}
|
|
89
100
|
return a;
|
|
90
101
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
function multiply(a, b) {
|
|
102
|
+
// src/lib/utils/multiply.ts
|
|
103
|
+
function multiply(a, b, maxDecimals) {
|
|
94
104
|
const aN = a.n;
|
|
95
105
|
const bN = b.n;
|
|
96
106
|
if (aN === 0n || bN === 0n) return ZERO;
|
|
@@ -116,9 +126,9 @@ function multiply(a, b) {
|
|
|
116
126
|
}, {
|
|
117
127
|
n: bE?.n ?? 1n,
|
|
118
128
|
d: bE?.d ?? 1n
|
|
119
|
-
}))
|
|
129
|
+
}, maxDecimals))
|
|
120
130
|
};
|
|
121
|
-
if (aC !== void 0 && bC !== void 0) return multiply(toSimpleFraction(a), toSimpleFraction(b));
|
|
131
|
+
if (aC !== void 0 && bC !== void 0) return multiply(toSimpleFraction(a, maxDecimals), toSimpleFraction(b, maxDecimals), maxDecimals);
|
|
122
132
|
return {
|
|
123
133
|
n,
|
|
124
134
|
d,
|
|
@@ -126,8 +136,7 @@ function multiply(a, b) {
|
|
|
126
136
|
e: aE ?? bE
|
|
127
137
|
};
|
|
128
138
|
}
|
|
129
|
-
|
|
130
|
-
//#region src/lib/utils/nthroot.ts
|
|
139
|
+
// src/lib/utils/nthroot.ts
|
|
131
140
|
/**
|
|
132
141
|
* Calculates the integer nth root of a BigInt.
|
|
133
142
|
*/
|
|
@@ -148,46 +157,44 @@ function iNthRoot(value, n) {
|
|
|
148
157
|
/**
|
|
149
158
|
* Calculates the n-th root of a fraction.
|
|
150
159
|
*/
|
|
151
|
-
function nthRoot(v, n,
|
|
160
|
+
function nthRoot(v, n, maxDecimals) {
|
|
152
161
|
if (n === 0n) throw new DivisionByZeroError();
|
|
153
162
|
if (n === 1n) return v;
|
|
154
163
|
if (v.n < 0) {
|
|
155
164
|
if (n % 2n === 0n) throw new NotImplementedError(`Even (${n}th) root of negative numbers is not implemented yet.`);
|
|
156
|
-
const
|
|
165
|
+
const r = nthRoot({
|
|
157
166
|
n: -v.n,
|
|
158
167
|
d: v.d
|
|
159
|
-
}, n,
|
|
168
|
+
}, n, maxDecimals);
|
|
160
169
|
return {
|
|
161
|
-
n: -
|
|
162
|
-
d:
|
|
170
|
+
n: -r.n,
|
|
171
|
+
d: r.d
|
|
163
172
|
};
|
|
164
173
|
}
|
|
165
174
|
if (v.n === 0n) return ZERO;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
});
|
|
180
|
-
}
|
|
175
|
+
const rootN = iNthRoot(v.n, n);
|
|
176
|
+
const rootD = iNthRoot(v.d, n);
|
|
177
|
+
if (rootN ** n === v.n && rootD ** n === v.d) {
|
|
178
|
+
const e = simplify({
|
|
179
|
+
n: v.e?.n ?? 1n,
|
|
180
|
+
d: (v.e?.d ?? 1n) * n
|
|
181
|
+
});
|
|
182
|
+
return simplify({
|
|
183
|
+
n: rootN,
|
|
184
|
+
d: rootD,
|
|
185
|
+
c: v.c,
|
|
186
|
+
e
|
|
187
|
+
});
|
|
181
188
|
}
|
|
182
|
-
const
|
|
183
|
-
const scaleFactor =
|
|
189
|
+
const precisionBits = getBitPrecision(maxDecimals);
|
|
190
|
+
const scaleFactor = 1n << precisionBits * n;
|
|
191
|
+
const s = toSimpleFraction(v);
|
|
184
192
|
return simplify({
|
|
185
|
-
n: iNthRoot(
|
|
186
|
-
d:
|
|
193
|
+
n: iNthRoot(s.n * scaleFactor / s.d, n),
|
|
194
|
+
d: 1n << precisionBits
|
|
187
195
|
});
|
|
188
196
|
}
|
|
189
|
-
|
|
190
|
-
//#region src/lib/utils/simplify.ts
|
|
197
|
+
// src/lib/utils/simplify.ts
|
|
191
198
|
/**
|
|
192
199
|
* Reduces a fraction to its simplest form.
|
|
193
200
|
*/
|
|
@@ -195,7 +202,7 @@ function simplify(v) {
|
|
|
195
202
|
if (v.d === 0n) throw new DivisionByZeroError();
|
|
196
203
|
if (v.n === 0n) return ZERO;
|
|
197
204
|
if (v.d === 1n) {
|
|
198
|
-
if (v.e?.n === 0n) return {
|
|
205
|
+
if (v.e?.n === 0n || !v.c) return {
|
|
199
206
|
n: v.n,
|
|
200
207
|
d: v.d
|
|
201
208
|
};
|
|
@@ -205,7 +212,7 @@ function simplify(v) {
|
|
|
205
212
|
const sign = v.d < 0 ? -1n : 1n;
|
|
206
213
|
const n = v.n / common * sign;
|
|
207
214
|
const d = v.d / common * sign;
|
|
208
|
-
if (v.e?.n === 0n) return {
|
|
215
|
+
if (v.e?.n === 0n || !v.c) return {
|
|
209
216
|
n,
|
|
210
217
|
d
|
|
211
218
|
};
|
|
@@ -219,9 +226,12 @@ function simplify(v) {
|
|
|
219
226
|
/**
|
|
220
227
|
* Resolves any constants and exponents to return a simple fraction.
|
|
221
228
|
*/
|
|
222
|
-
function toSimpleFraction(val) {
|
|
223
|
-
if (!val.c) return
|
|
224
|
-
|
|
229
|
+
function toSimpleFraction(val, maxDecimals) {
|
|
230
|
+
if (!val.c) return {
|
|
231
|
+
d: val.d,
|
|
232
|
+
n: val.n
|
|
233
|
+
};
|
|
234
|
+
const c = getConst(val.c, maxDecimals);
|
|
225
235
|
const expN = val.e?.n ?? 1n;
|
|
226
236
|
const expD = val.e?.d ?? 1n;
|
|
227
237
|
const absExpN = expN < 0 ? -expN : expN;
|
|
@@ -229,35 +239,123 @@ function toSimpleFraction(val) {
|
|
|
229
239
|
n: expN < 0 ? c.d ** absExpN : c.n ** absExpN,
|
|
230
240
|
d: expN < 0 ? c.n ** absExpN : c.d ** absExpN
|
|
231
241
|
};
|
|
232
|
-
if (expD !== 1n) poweredConstant = nthRoot(poweredConstant, expD
|
|
242
|
+
if (expD !== 1n) poweredConstant = nthRoot(poweredConstant, expD);
|
|
233
243
|
return multiply(poweredConstant, {
|
|
234
244
|
n: val.n,
|
|
235
245
|
d: val.d
|
|
246
|
+
}, maxDecimals);
|
|
247
|
+
}
|
|
248
|
+
// src/lib/utils/arctan.ts
|
|
249
|
+
function rawArctanSeries(xScaled, bitPrecision) {
|
|
250
|
+
const xSqScaled = xScaled * xScaled >> bitPrecision;
|
|
251
|
+
let currentTerm = xScaled;
|
|
252
|
+
let totalSum = xScaled;
|
|
253
|
+
let k = 1n;
|
|
254
|
+
while (true) {
|
|
255
|
+
currentTerm = currentTerm * xSqScaled >> bitPrecision;
|
|
256
|
+
const denominatorFactor = 2n * k + 1n;
|
|
257
|
+
const termValue = currentTerm / denominatorFactor;
|
|
258
|
+
if (termValue === 0n) break;
|
|
259
|
+
if (k % 2n === 1n) totalSum -= termValue;
|
|
260
|
+
else totalSum += termValue;
|
|
261
|
+
k++;
|
|
262
|
+
}
|
|
263
|
+
return totalSum;
|
|
264
|
+
}
|
|
265
|
+
function arctan(v, maxDecimals) {
|
|
266
|
+
const { n, d } = v;
|
|
267
|
+
if (n === 0n) return ZERO;
|
|
268
|
+
const bitPrecision = getBitPrecision(maxDecimals);
|
|
269
|
+
const isNegative = n < 0n !== d < 0n;
|
|
270
|
+
const absN = n < 0n ? -n : n;
|
|
271
|
+
const absD = d < 0n ? -d : d;
|
|
272
|
+
if (absN === absD) return {
|
|
273
|
+
c: "pi",
|
|
274
|
+
n: isNegative ? -1n : 1n,
|
|
275
|
+
d: 4n
|
|
276
|
+
};
|
|
277
|
+
if (absN > absD) {
|
|
278
|
+
const pi = computePi(maxDecimals);
|
|
279
|
+
const subArctan = arctan({
|
|
280
|
+
n: absD,
|
|
281
|
+
d: absN
|
|
282
|
+
}, maxDecimals);
|
|
283
|
+
const resultNum = pi.n * subArctan.d - 2n * subArctan.n * pi.d;
|
|
284
|
+
const resultDen = 2n * pi.d * subArctan.d;
|
|
285
|
+
return simplify({
|
|
286
|
+
n: isNegative ? -resultNum : resultNum,
|
|
287
|
+
d: resultDen
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
if (absN * 2n > absD) {
|
|
291
|
+
const halfArctan = arctan({
|
|
292
|
+
n: 1n,
|
|
293
|
+
d: 2n
|
|
294
|
+
}, maxDecimals);
|
|
295
|
+
const remainderArctan = arctan({
|
|
296
|
+
n: 2n * absN - absD,
|
|
297
|
+
d: 2n * absD + absN
|
|
298
|
+
}, maxDecimals);
|
|
299
|
+
const resultNum = halfArctan.n * remainderArctan.d + remainderArctan.n * halfArctan.d;
|
|
300
|
+
const resultDen = halfArctan.d * remainderArctan.d;
|
|
301
|
+
return simplify({
|
|
302
|
+
n: isNegative ? -resultNum : resultNum,
|
|
303
|
+
d: resultDen
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
const r = rawArctanSeries((absN << bitPrecision) / absD, bitPrecision);
|
|
307
|
+
return simplify({
|
|
308
|
+
n: isNegative ? -r : r,
|
|
309
|
+
d: 1n << bitPrecision
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
// src/lib/utils/pi.ts
|
|
313
|
+
function computePi(maxDecimals) {
|
|
314
|
+
const p5 = arctan({
|
|
315
|
+
n: 1n,
|
|
316
|
+
d: 5n
|
|
317
|
+
}, maxDecimals);
|
|
318
|
+
const p239 = arctan({
|
|
319
|
+
n: 1n,
|
|
320
|
+
d: 239n
|
|
321
|
+
}, maxDecimals);
|
|
322
|
+
return simplify({
|
|
323
|
+
n: 16n * p5.n * p239.d - 4n * p239.n * p5.d,
|
|
324
|
+
d: p5.d * p239.d
|
|
236
325
|
});
|
|
237
326
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
327
|
+
// src/lib/utils/constants.ts
|
|
328
|
+
const ZERO = {
|
|
329
|
+
n: 0n,
|
|
330
|
+
d: 1n
|
|
331
|
+
};
|
|
332
|
+
const ONE = {
|
|
333
|
+
n: 1n,
|
|
334
|
+
d: 1n
|
|
335
|
+
};
|
|
336
|
+
function getConst(id, maxDecimals) {
|
|
337
|
+
if (id === "pi") return computePi(maxDecimals);
|
|
338
|
+
return computeE(maxDecimals);
|
|
339
|
+
}
|
|
340
|
+
// src/lib/utils/add.ts
|
|
341
|
+
function negate(v) {
|
|
342
|
+
if (v.n === "OVERFLOW") return v;
|
|
343
|
+
const { n, d } = v;
|
|
344
|
+
return {
|
|
345
|
+
...v,
|
|
346
|
+
n: d < 0 ? n : -n,
|
|
347
|
+
d: d < 0 ? -d : d
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function add(left, right, maxDecimals, subtract = false) {
|
|
241
351
|
const lN = left.n;
|
|
242
352
|
const rN = right.n;
|
|
243
|
-
if (lN === "OVERFLOW") return left;
|
|
244
|
-
if (rN === "OVERFLOW") return right;
|
|
353
|
+
if (lN === "OVERFLOW" || rN === 0n) return left;
|
|
354
|
+
if (rN === "OVERFLOW" || lN === 0n) return subtract ? negate(right) : right;
|
|
245
355
|
const { d: lD, c, e } = left;
|
|
246
356
|
const { d: rD, c: rC, e: rE } = right;
|
|
247
|
-
if (
|
|
248
|
-
|
|
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;
|
|
357
|
+
if (c !== rC || e?.n !== rE?.n) return add(toSimpleFraction(left, maxDecimals), toSimpleFraction(right, maxDecimals), maxDecimals, subtract);
|
|
358
|
+
let n, d;
|
|
261
359
|
if (lD === rD) {
|
|
262
360
|
n = subtract ? lN - rN : lN + rN;
|
|
263
361
|
d = lD;
|
|
@@ -269,7 +367,10 @@ function add(left, right, subtract = false) {
|
|
|
269
367
|
d = lD * mLeft;
|
|
270
368
|
}
|
|
271
369
|
if (n === 0n) return ZERO;
|
|
272
|
-
if (
|
|
370
|
+
if (n < 0n && d < 0n) {
|
|
371
|
+
n = -n;
|
|
372
|
+
d = -d;
|
|
373
|
+
}
|
|
273
374
|
return {
|
|
274
375
|
n,
|
|
275
376
|
d,
|
|
@@ -277,8 +378,36 @@ function add(left, right, subtract = false) {
|
|
|
277
378
|
e
|
|
278
379
|
};
|
|
279
380
|
}
|
|
280
|
-
|
|
281
|
-
|
|
381
|
+
// src/lib/utils/arctanh.ts
|
|
382
|
+
function rawArctanhSeries(xScaled, bitPrecision) {
|
|
383
|
+
let currentTerm = xScaled;
|
|
384
|
+
let totalSum = xScaled;
|
|
385
|
+
const xSqScaled = xScaled * xScaled >> bitPrecision;
|
|
386
|
+
let k = 1n;
|
|
387
|
+
while (true) {
|
|
388
|
+
currentTerm = currentTerm * xSqScaled >> bitPrecision;
|
|
389
|
+
const denominatorFactor = 2n * k + 1n;
|
|
390
|
+
if (currentTerm < denominatorFactor) break;
|
|
391
|
+
totalSum += currentTerm / denominatorFactor;
|
|
392
|
+
k++;
|
|
393
|
+
}
|
|
394
|
+
return totalSum;
|
|
395
|
+
}
|
|
396
|
+
function arctanh(v, maxDecimals) {
|
|
397
|
+
const { n, d } = v;
|
|
398
|
+
if (n === 0n) return ZERO;
|
|
399
|
+
const bitPrecision = getBitPrecision(maxDecimals);
|
|
400
|
+
const isNegative = n < 0n !== d < 0n;
|
|
401
|
+
const currentN = n < 0n ? -n : n;
|
|
402
|
+
const currentD = d < 0n ? -d : d;
|
|
403
|
+
if (currentN >= currentD) throw new InterpreterRangeError("arctanh argument must be strictly between -1 and 1");
|
|
404
|
+
const sumScaled = rawArctanhSeries((currentN << bitPrecision) / currentD, bitPrecision);
|
|
405
|
+
return {
|
|
406
|
+
n: isNegative ? -sumScaled : sumScaled,
|
|
407
|
+
d: 1n << bitPrecision
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
// src/lib/utils/ceil.ts
|
|
282
411
|
/**
|
|
283
412
|
* Returns the smallest integer greater than or equal to the value.
|
|
284
413
|
*/
|
|
@@ -290,9 +419,8 @@ function ceil(v) {
|
|
|
290
419
|
const hasRemainder = n % d !== 0n;
|
|
291
420
|
return isPositive && hasRemainder ? result + 1n : result;
|
|
292
421
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
function divide(left, right) {
|
|
422
|
+
// src/lib/utils/divide.ts
|
|
423
|
+
function divide(left, right, maxDecimals) {
|
|
296
424
|
const lN = left.n;
|
|
297
425
|
const rN = right.n;
|
|
298
426
|
if (rN === 0n) throw new DivisionByZeroError();
|
|
@@ -300,6 +428,7 @@ function divide(left, right) {
|
|
|
300
428
|
if (lN === "OVERFLOW" || rN === "OVERFLOW") return OverflowValue;
|
|
301
429
|
const { d: lD, c: lC, e: lE } = left;
|
|
302
430
|
const { d: rD, c: rC, e: rE } = right;
|
|
431
|
+
if (lC !== void 0 && rC !== void 0 && lC !== rC) return divide(toSimpleFraction(left, maxDecimals), toSimpleFraction(right, maxDecimals), maxDecimals);
|
|
303
432
|
const g1 = gcd(lN, rN);
|
|
304
433
|
const g2 = gcd(rD, lD);
|
|
305
434
|
const n = lN / g1 * (rD / g2);
|
|
@@ -314,17 +443,14 @@ function divide(left, right) {
|
|
|
314
443
|
}, {
|
|
315
444
|
n: rE?.n ?? 1n,
|
|
316
445
|
d: rE?.d ?? 1n
|
|
317
|
-
}, true))
|
|
446
|
+
}, maxDecimals, true))
|
|
318
447
|
};
|
|
319
448
|
const c = lC ?? rC;
|
|
320
449
|
let e;
|
|
321
450
|
if (lC !== void 0) e = lE ? {
|
|
322
451
|
n: lE.n,
|
|
323
452
|
d: lE.d ?? 1n
|
|
324
|
-
} :
|
|
325
|
-
n: 1n,
|
|
326
|
-
d: 1n
|
|
327
|
-
};
|
|
453
|
+
} : ONE;
|
|
328
454
|
else if (rC !== void 0) e = {
|
|
329
455
|
n: -(rE?.n ?? 1n),
|
|
330
456
|
d: rE?.d ?? 1n
|
|
@@ -336,8 +462,7 @@ function divide(left, right) {
|
|
|
336
462
|
e
|
|
337
463
|
};
|
|
338
464
|
}
|
|
339
|
-
|
|
340
|
-
//#region src/lib/utils/sqrt.ts
|
|
465
|
+
// src/lib/utils/sqrt.ts
|
|
341
466
|
/**
|
|
342
467
|
* Calculates the integer square root of a BigInt.
|
|
343
468
|
*/
|
|
@@ -360,78 +485,158 @@ function isPerfectSquare(val) {
|
|
|
360
485
|
const root = isqrt(val);
|
|
361
486
|
return [root * root === val, root];
|
|
362
487
|
}
|
|
363
|
-
function sqrt(v,
|
|
488
|
+
function sqrt(v, maxDecimals) {
|
|
364
489
|
if (v.n === "OVERFLOW") return v;
|
|
365
490
|
if (v.n < 0) throw new NotImplementedError("Square root of negative numbers is not implemented yet.");
|
|
366
491
|
if (v.n === 0n) return ZERO;
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
});
|
|
381
|
-
}
|
|
492
|
+
const [nIsSquare, n] = isPerfectSquare(v.n);
|
|
493
|
+
const [dIsSquare, d] = isPerfectSquare(v.d);
|
|
494
|
+
if (nIsSquare && dIsSquare) {
|
|
495
|
+
const e = simplify({
|
|
496
|
+
n: v.e?.n ?? 1n,
|
|
497
|
+
d: (v.e?.d ?? 1n) * 2n
|
|
498
|
+
});
|
|
499
|
+
return simplify({
|
|
500
|
+
n,
|
|
501
|
+
d,
|
|
502
|
+
c: v.c,
|
|
503
|
+
e
|
|
504
|
+
});
|
|
382
505
|
}
|
|
383
|
-
const
|
|
384
|
-
const scaleFactor =
|
|
506
|
+
const precisionBits = getBitPrecision(maxDecimals);
|
|
507
|
+
const scaleFactor = 1n << precisionBits * 2n;
|
|
508
|
+
const s = toSimpleFraction(v);
|
|
509
|
+
return {
|
|
510
|
+
n: isqrt(s.n * scaleFactor / s.d),
|
|
511
|
+
d: 1n << precisionBits
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
// src/lib/utils/ln.ts
|
|
515
|
+
function ln(v, maxDecimals) {
|
|
516
|
+
const { n, d, c, e } = v;
|
|
517
|
+
if (n <= 0n || d <= 0n) throw new InterpreterRangeError("Logarithm of non-positive number");
|
|
518
|
+
if (c === "e" && n === d) return e ? {
|
|
519
|
+
n: e.n,
|
|
520
|
+
d: e.d ?? 1n
|
|
521
|
+
} : ONE;
|
|
522
|
+
let { n: sN, d: sD } = toSimpleFraction(v);
|
|
523
|
+
const isReciprocal = sN < sD;
|
|
524
|
+
if (isReciprocal) [sN, sD] = [sD, sN];
|
|
525
|
+
let m = 0;
|
|
526
|
+
while (sN >= 2n * sD) {
|
|
527
|
+
const val = sqrt({
|
|
528
|
+
n: sN,
|
|
529
|
+
d: sD
|
|
530
|
+
}, maxDecimals);
|
|
531
|
+
sN = val.n;
|
|
532
|
+
sD = val.d;
|
|
533
|
+
m++;
|
|
534
|
+
}
|
|
535
|
+
const at = arctanh({
|
|
536
|
+
n: sN - sD,
|
|
537
|
+
d: sN + sD
|
|
538
|
+
}, maxDecimals);
|
|
539
|
+
let rN = at.n * 2n;
|
|
540
|
+
if (m > 0) rN <<= BigInt(m);
|
|
541
|
+
if (isReciprocal) rN = -rN;
|
|
385
542
|
return simplify({
|
|
386
|
-
n:
|
|
387
|
-
d:
|
|
543
|
+
n: rN,
|
|
544
|
+
d: at.d
|
|
388
545
|
});
|
|
389
546
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const
|
|
396
|
-
|
|
547
|
+
// src/lib/utils/exp.ts
|
|
548
|
+
function rawExp(v, maxDecimals) {
|
|
549
|
+
const { n, d } = v;
|
|
550
|
+
if (n === 0n) return ONE;
|
|
551
|
+
const scale = 1n << getBitPrecision(maxDecimals);
|
|
552
|
+
const xScaled = n * scale / d;
|
|
553
|
+
const ln2Val = ln({
|
|
554
|
+
n: 2n,
|
|
555
|
+
d: 1n
|
|
556
|
+
}, maxDecimals);
|
|
557
|
+
const ln2Scaled = ln2Val.n * scale / ln2Val.d;
|
|
558
|
+
let k;
|
|
559
|
+
if (xScaled >= 0n) k = (xScaled + ln2Scaled / 2n) / ln2Scaled;
|
|
560
|
+
else k = (xScaled - ln2Scaled / 2n) / ln2Scaled;
|
|
561
|
+
const rScaled = xScaled - k * ln2Scaled;
|
|
562
|
+
const isNegativeR = rScaled < 0n;
|
|
563
|
+
const absRScaled = isNegativeR ? -rScaled : rScaled;
|
|
564
|
+
let sum = scale;
|
|
565
|
+
let currentTerm = scale;
|
|
566
|
+
let factorial = 1n;
|
|
567
|
+
let i = 1n;
|
|
568
|
+
while (true) {
|
|
569
|
+
currentTerm = currentTerm * absRScaled / scale;
|
|
570
|
+
if (currentTerm === 0n) break;
|
|
571
|
+
factorial *= i;
|
|
572
|
+
const termVal = currentTerm / factorial;
|
|
573
|
+
if (termVal === 0n) break;
|
|
574
|
+
if (isNegativeR && i % 2n === 1n) sum -= termVal;
|
|
575
|
+
else sum += termVal;
|
|
576
|
+
i += 1n;
|
|
577
|
+
}
|
|
578
|
+
let finalNum = sum;
|
|
579
|
+
let finalDen = scale;
|
|
580
|
+
if (k > 0n) finalNum <<= k;
|
|
581
|
+
else if (k < 0n) finalDen <<= -k;
|
|
582
|
+
return {
|
|
583
|
+
n: finalNum,
|
|
584
|
+
d: finalDen
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
function exp(v, maxDecimals) {
|
|
588
|
+
const { n, d, c } = v;
|
|
589
|
+
if (n === 0n) return ONE;
|
|
590
|
+
if (!c) return simplify({
|
|
591
|
+
n: 1n,
|
|
592
|
+
d: 1n,
|
|
593
|
+
c: "e",
|
|
594
|
+
e: {
|
|
595
|
+
n,
|
|
596
|
+
d
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
return rawExp(toSimpleFraction(v, maxDecimals), maxDecimals);
|
|
600
|
+
}
|
|
601
|
+
function exponentiate(left, right, maxDecimals) {
|
|
397
602
|
const rN = right.n;
|
|
398
603
|
if (rN === 0n) return ONE;
|
|
399
604
|
const lN = left.n;
|
|
400
605
|
if (lN === "OVERFLOW") return left;
|
|
401
|
-
const lD = left
|
|
402
|
-
const c = left.c;
|
|
606
|
+
const { c, d: lD } = left;
|
|
403
607
|
if (lN === lD && c === void 0) return ONE;
|
|
404
608
|
if (rN === "OVERFLOW") return right;
|
|
405
|
-
|
|
406
|
-
|
|
609
|
+
const rS = simplify(right);
|
|
610
|
+
if (lN === -lD && c === void 0 && rS.d % 2n) {
|
|
611
|
+
if (rS.n % 2n) return {
|
|
407
612
|
n: -1n,
|
|
408
613
|
d: 1n
|
|
409
614
|
};
|
|
410
615
|
return ONE;
|
|
411
616
|
}
|
|
412
|
-
const normalizedExp = toSimpleFraction(
|
|
617
|
+
const normalizedExp = toSimpleFraction(rS, maxDecimals);
|
|
618
|
+
const dExp = normalizedExp.d;
|
|
413
619
|
let exponent = normalizedExp.n;
|
|
414
620
|
if (lN === 0n) {
|
|
415
621
|
if (exponent < 0) throw new DivisionByZeroError();
|
|
416
622
|
return ZERO;
|
|
417
623
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
});
|
|
624
|
+
if ((dExp >= 100 || lD > 1e3) && (lN !== 1n || rS.c)) {
|
|
625
|
+
const isNegativeBase = lN < 0;
|
|
626
|
+
if (isNegativeBase && dExp % 2n === 0n) throw new NotImplementedError(`Even (${dExp}th) root of negative numbers is not implemented yet.`);
|
|
627
|
+
const l = toSimpleFraction(isNegativeBase ? negate(left) : left, maxDecimals);
|
|
628
|
+
const r = toSimpleFraction(rS, maxDecimals);
|
|
629
|
+
const lnBase = ln(l, maxDecimals);
|
|
630
|
+
const scale = 1n << getBitPrecision(maxDecimals);
|
|
631
|
+
const p = lnBase.n * scale / lnBase.d * (r.n * scale / r.d) / scale;
|
|
632
|
+
const pAbs = p < 0 ? -p : p;
|
|
633
|
+
if (scale > pAbs * 10000000n || pAbs > scale * 10000000n) return OverflowValue;
|
|
634
|
+
const result = rawExp({
|
|
635
|
+
n: p,
|
|
636
|
+
d: scale
|
|
637
|
+
}, maxDecimals);
|
|
638
|
+
if (isNegativeBase && normalizedExp.n % 2n !== 0n) return negate(result);
|
|
639
|
+
return result;
|
|
435
640
|
}
|
|
436
641
|
const eN = (left.e?.n ?? 1n) * exponent;
|
|
437
642
|
let baseN = lN;
|
|
@@ -442,7 +647,7 @@ function exponentiate(left, right, precision) {
|
|
|
442
647
|
}
|
|
443
648
|
const baseNDigits = BigInt.bitLength(baseN);
|
|
444
649
|
const baseDDigits = BigInt.bitLength(baseD);
|
|
445
|
-
if (baseNDigits * exponent >
|
|
650
|
+
if (baseNDigits * exponent > 10000000n || baseDDigits * exponent > 10000000n || (baseNDigits > 5e3 || baseDDigits > 5e3) && exponent > 10) return OverflowValue;
|
|
446
651
|
const e = c ? simplify({
|
|
447
652
|
n: eN,
|
|
448
653
|
d: left.e?.d ?? 1n
|
|
@@ -460,11 +665,10 @@ function exponentiate(left, right, precision) {
|
|
|
460
665
|
e
|
|
461
666
|
};
|
|
462
667
|
if (dExp === 1n) return v;
|
|
463
|
-
if (dExp === 2n) return sqrt(v,
|
|
464
|
-
return nthRoot(v, dExp,
|
|
668
|
+
if (dExp === 2n) return sqrt(v, maxDecimals);
|
|
669
|
+
return nthRoot(v, dExp, maxDecimals);
|
|
465
670
|
}
|
|
466
|
-
|
|
467
|
-
//#region src/lib/utils/factorial.ts
|
|
671
|
+
// src/lib/utils/factorial.ts
|
|
468
672
|
function binaryProduct(arr, low, high) {
|
|
469
673
|
if (low > high) return 1n;
|
|
470
674
|
if (low === high) return arr[low];
|
|
@@ -504,8 +708,7 @@ function factorial(n) {
|
|
|
504
708
|
}
|
|
505
709
|
return binaryProduct(primePowers, 0, primePowers.length - 1);
|
|
506
710
|
}
|
|
507
|
-
|
|
508
|
-
//#region src/lib/utils/floor.ts
|
|
711
|
+
// src/lib/utils/floor.ts
|
|
509
712
|
/**
|
|
510
713
|
* Returns the largest integer less than or equal to the value.
|
|
511
714
|
*/
|
|
@@ -517,8 +720,7 @@ function floor(v) {
|
|
|
517
720
|
const hasRemainder = n % d !== 0n;
|
|
518
721
|
return isNegative && hasRemainder ? result - 1n : result;
|
|
519
722
|
}
|
|
520
|
-
|
|
521
|
-
//#region src/lib/utils/mod.ts
|
|
723
|
+
// src/lib/utils/mod.ts
|
|
522
724
|
/**
|
|
523
725
|
* Calculates remainder of division.
|
|
524
726
|
*/
|
|
@@ -532,8 +734,144 @@ function mod(a, b) {
|
|
|
532
734
|
d
|
|
533
735
|
};
|
|
534
736
|
}
|
|
535
|
-
|
|
536
|
-
|
|
737
|
+
// src/lib/utils/trig.ts
|
|
738
|
+
function roundDiv(numerator, denominator) {
|
|
739
|
+
const twice = numerator * 2n;
|
|
740
|
+
const absTwice = twice < 0n ? -twice : twice;
|
|
741
|
+
const absDenom = denominator < 0n ? -denominator : denominator;
|
|
742
|
+
const quotient = (absTwice + absDenom) / (2n * absDenom);
|
|
743
|
+
return twice < 0n !== denominator < 0n ? -quotient : quotient;
|
|
744
|
+
}
|
|
745
|
+
function rawSinSeries(rScaled, bitPrecision) {
|
|
746
|
+
const rSqScaled = rScaled * rScaled >> bitPrecision;
|
|
747
|
+
let termValue = rScaled;
|
|
748
|
+
let totalSum = rScaled;
|
|
749
|
+
let k = 1n;
|
|
750
|
+
while (true) {
|
|
751
|
+
termValue = (termValue * rSqScaled >> bitPrecision) / (2n * k * (2n * k + 1n));
|
|
752
|
+
if (termValue === 0n) break;
|
|
753
|
+
if (k % 2n === 1n) totalSum -= termValue;
|
|
754
|
+
else totalSum += termValue;
|
|
755
|
+
k++;
|
|
756
|
+
}
|
|
757
|
+
return totalSum;
|
|
758
|
+
}
|
|
759
|
+
function rawCosSeries(rScaled, bitPrecision) {
|
|
760
|
+
const rSqScaled = rScaled * rScaled >> bitPrecision;
|
|
761
|
+
const scale = 1n << bitPrecision;
|
|
762
|
+
let termValue = scale;
|
|
763
|
+
let totalSum = scale;
|
|
764
|
+
let k = 1n;
|
|
765
|
+
while (true) {
|
|
766
|
+
termValue = (termValue * rSqScaled >> bitPrecision) / ((2n * k - 1n) * (2n * k));
|
|
767
|
+
if (termValue === 0n) break;
|
|
768
|
+
if (k % 2n === 1n) totalSum -= termValue;
|
|
769
|
+
else totalSum += termValue;
|
|
770
|
+
k++;
|
|
771
|
+
}
|
|
772
|
+
return totalSum;
|
|
773
|
+
}
|
|
774
|
+
function getHalfPi(maxDecimals) {
|
|
775
|
+
const { d, n, c } = computePi(maxDecimals);
|
|
776
|
+
return {
|
|
777
|
+
c,
|
|
778
|
+
n,
|
|
779
|
+
d: d * 2n
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
function sin(v, maxDecimals) {
|
|
783
|
+
const { n, d, c, e } = v;
|
|
784
|
+
if (n === 0n) return ZERO;
|
|
785
|
+
const bitPrecision = getBitPrecision(maxDecimals);
|
|
786
|
+
const scale = 1n << bitPrecision;
|
|
787
|
+
const isPiLinear = c === "pi" && (e === void 0 || e.n === 1n);
|
|
788
|
+
let rScaled;
|
|
789
|
+
let k;
|
|
790
|
+
if (isPiLinear) {
|
|
791
|
+
k = roundDiv(2n * n, d);
|
|
792
|
+
const rN = 2n * n - k * d;
|
|
793
|
+
if (rN === 0n) {
|
|
794
|
+
const quadrant = (k % 4n + 4n) % 4n;
|
|
795
|
+
if (quadrant === 0n || quadrant === 2n) return ZERO;
|
|
796
|
+
if (quadrant === 1n) return ONE;
|
|
797
|
+
return {
|
|
798
|
+
n: -1n,
|
|
799
|
+
d: 1n
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
const piVal = computePi(maxDecimals);
|
|
803
|
+
rScaled = rN * piVal.n * scale / (2n * d * piVal.d);
|
|
804
|
+
} else {
|
|
805
|
+
const simple = toSimpleFraction(v);
|
|
806
|
+
const halfPi = getHalfPi(maxDecimals);
|
|
807
|
+
k = roundDiv(simple.n * halfPi.d, simple.d * halfPi.n);
|
|
808
|
+
const rN = simple.n * halfPi.d - k * halfPi.n * simple.d;
|
|
809
|
+
const rD = simple.d * halfPi.d;
|
|
810
|
+
rScaled = rN * scale / rD;
|
|
811
|
+
}
|
|
812
|
+
const quadrant = (k % 4n + 4n) % 4n;
|
|
813
|
+
let resultScaled;
|
|
814
|
+
let negate = false;
|
|
815
|
+
if (quadrant === 0n) resultScaled = rawSinSeries(rScaled, bitPrecision);
|
|
816
|
+
else if (quadrant === 1n) resultScaled = rawCosSeries(rScaled, bitPrecision);
|
|
817
|
+
else if (quadrant === 2n) {
|
|
818
|
+
resultScaled = rawSinSeries(rScaled, bitPrecision);
|
|
819
|
+
negate = true;
|
|
820
|
+
} else {
|
|
821
|
+
resultScaled = rawCosSeries(rScaled, bitPrecision);
|
|
822
|
+
negate = true;
|
|
823
|
+
}
|
|
824
|
+
return {
|
|
825
|
+
n: negate ? -resultScaled : resultScaled,
|
|
826
|
+
d: scale
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
function cos(v, maxDecimals) {
|
|
830
|
+
const { n, d, c, e } = v;
|
|
831
|
+
if (n === 0n) return ONE;
|
|
832
|
+
const bitPrecision = getBitPrecision(maxDecimals);
|
|
833
|
+
const scale = 1n << bitPrecision;
|
|
834
|
+
const isPiLinear = c === "pi" && (e === void 0 || e.n === 1n);
|
|
835
|
+
let rScaled;
|
|
836
|
+
let k;
|
|
837
|
+
if (isPiLinear) {
|
|
838
|
+
k = roundDiv(2n * n, d);
|
|
839
|
+
const rN = 2n * n - k * d;
|
|
840
|
+
if (rN === 0n) {
|
|
841
|
+
const quadrant = (k % 4n + 4n) % 4n;
|
|
842
|
+
if (quadrant === 0n || quadrant === 2n) return quadrant === 0n ? ONE : {
|
|
843
|
+
n: -1n,
|
|
844
|
+
d: 1n
|
|
845
|
+
};
|
|
846
|
+
return ZERO;
|
|
847
|
+
}
|
|
848
|
+
const piVal = computePi(maxDecimals);
|
|
849
|
+
rScaled = rN * piVal.n * scale / (2n * d * piVal.d);
|
|
850
|
+
} else {
|
|
851
|
+
const simple = toSimpleFraction(v);
|
|
852
|
+
const halfPi = getHalfPi(maxDecimals);
|
|
853
|
+
k = roundDiv(simple.n * halfPi.d, simple.d * halfPi.n);
|
|
854
|
+
const rN = simple.n * halfPi.d - k * halfPi.n * simple.d;
|
|
855
|
+
const rD = simple.d * halfPi.d;
|
|
856
|
+
rScaled = rN * scale / rD;
|
|
857
|
+
}
|
|
858
|
+
const quadrant = (k % 4n + 4n) % 4n;
|
|
859
|
+
let resultScaled;
|
|
860
|
+
let negate = false;
|
|
861
|
+
if (quadrant === 0n) resultScaled = rawCosSeries(rScaled, bitPrecision);
|
|
862
|
+
else if (quadrant === 1n) {
|
|
863
|
+
resultScaled = rawSinSeries(rScaled, bitPrecision);
|
|
864
|
+
negate = true;
|
|
865
|
+
} else if (quadrant === 2n) {
|
|
866
|
+
resultScaled = rawCosSeries(rScaled, bitPrecision);
|
|
867
|
+
negate = true;
|
|
868
|
+
} else resultScaled = rawSinSeries(rScaled, bitPrecision);
|
|
869
|
+
return {
|
|
870
|
+
n: negate ? -resultScaled : resultScaled,
|
|
871
|
+
d: scale
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
// src/lib/interpreter.ts
|
|
537
875
|
const precedence = {
|
|
538
876
|
LPAREN: 0,
|
|
539
877
|
ABS_OPEN: 0,
|
|
@@ -542,103 +880,94 @@ const precedence = {
|
|
|
542
880
|
MULTIPLY: 2,
|
|
543
881
|
DIVIDE: 2,
|
|
544
882
|
MOD: 2,
|
|
545
|
-
UNARY_PLUS:
|
|
546
|
-
UNARY_MINUS:
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
FLOOR_FN: 8,
|
|
552
|
-
SQRT_FN: 8,
|
|
553
|
-
FACTORIAL: 10
|
|
883
|
+
UNARY_PLUS: 3,
|
|
884
|
+
UNARY_MINUS: 3,
|
|
885
|
+
FN_CALL: 4,
|
|
886
|
+
EXP: 5,
|
|
887
|
+
IMPLICIT_MUL: 5,
|
|
888
|
+
FACTORIAL: 6
|
|
554
889
|
};
|
|
555
890
|
function isFunctionOp(op) {
|
|
556
|
-
return
|
|
891
|
+
return typeof op === "object";
|
|
557
892
|
}
|
|
558
893
|
function isUnaryOperation(op) {
|
|
559
894
|
return op === "UNARY_PLUS" || op === "UNARY_MINUS" || isFunctionOp(op);
|
|
560
895
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
/**
|
|
566
|
-
* Maximum allowed factorial
|
|
567
|
-
*/
|
|
568
|
-
const MAX_FACTORIAL = 2e4;
|
|
569
|
-
/**
|
|
570
|
-
* Maximum allowed exponent for scientific number notation
|
|
571
|
-
*/
|
|
572
|
-
const MAX_SCIENTIFIC_NUMBER_EXPONENT = 3e5;
|
|
896
|
+
function getOpPrecedence(op) {
|
|
897
|
+
if (isFunctionOp(op)) return precedence.FN_CALL;
|
|
898
|
+
return precedence[op];
|
|
899
|
+
}
|
|
573
900
|
function evaluate(tokens, options = {}) {
|
|
574
901
|
if (tokens.length === 0) throw new EmptyExpressionError();
|
|
902
|
+
const { maxDecimals } = options;
|
|
575
903
|
const values = [];
|
|
576
904
|
const ops = [];
|
|
577
905
|
const applyOp = (pos) => {
|
|
578
906
|
const op = ops.pop();
|
|
579
|
-
if (!op || op === "LPAREN" || op === "ABS_OPEN") return;
|
|
907
|
+
if (!op || op === "LPAREN" || op === "ABS_OPEN" || op === "UNARY_PLUS") return;
|
|
580
908
|
const right = values.pop();
|
|
581
|
-
if (isUnaryOperation(op))
|
|
582
|
-
|
|
583
|
-
values.push(right);
|
|
909
|
+
if (isUnaryOperation(op)) {
|
|
910
|
+
if (op === "UNARY_MINUS") {
|
|
911
|
+
values.push(negate(right));
|
|
584
912
|
return;
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
values.push({
|
|
592
|
-
...right,
|
|
593
|
-
n: -rN
|
|
594
|
-
});
|
|
913
|
+
}
|
|
914
|
+
const { id } = op;
|
|
915
|
+
if (id === "sqrt") {
|
|
916
|
+
values.push(sqrt(right, maxDecimals));
|
|
595
917
|
return;
|
|
596
918
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
values.push(OverflowValue);
|
|
601
|
-
return;
|
|
602
|
-
}
|
|
603
|
-
const n = rN < 0 ? -rN : rN;
|
|
604
|
-
values.push({
|
|
605
|
-
...right,
|
|
606
|
-
n
|
|
607
|
-
});
|
|
919
|
+
const n = right.n;
|
|
920
|
+
if (n === "OVERFLOW") {
|
|
921
|
+
values.push(OverflowValue);
|
|
608
922
|
return;
|
|
609
923
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
values.push(
|
|
924
|
+
switch (id) {
|
|
925
|
+
case "abs":
|
|
926
|
+
values.push({
|
|
927
|
+
...right,
|
|
928
|
+
n: n < 0 ? -n : n
|
|
929
|
+
});
|
|
613
930
|
return;
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
return;
|
|
620
|
-
case "FLOOR_FN":
|
|
621
|
-
if (right.n === "OVERFLOW") {
|
|
622
|
-
values.push(OverflowValue);
|
|
931
|
+
case "ceil":
|
|
932
|
+
values.push({
|
|
933
|
+
n: ceil(toSimpleFraction(right, maxDecimals)),
|
|
934
|
+
d: 1n
|
|
935
|
+
});
|
|
623
936
|
return;
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
937
|
+
case "floor":
|
|
938
|
+
values.push({
|
|
939
|
+
n: floor(toSimpleFraction(right, maxDecimals)),
|
|
940
|
+
d: 1n
|
|
941
|
+
});
|
|
942
|
+
return;
|
|
943
|
+
case "arctan":
|
|
944
|
+
values.push(arctan(toSimpleFraction(right, maxDecimals), maxDecimals));
|
|
945
|
+
return;
|
|
946
|
+
case "arctanh":
|
|
947
|
+
values.push(arctanh(toSimpleFraction(right, maxDecimals), maxDecimals));
|
|
948
|
+
return;
|
|
949
|
+
case "ln":
|
|
950
|
+
values.push(ln(right, maxDecimals));
|
|
951
|
+
return;
|
|
952
|
+
case "exp":
|
|
953
|
+
values.push(exp(right, maxDecimals));
|
|
954
|
+
return;
|
|
955
|
+
case "sin":
|
|
956
|
+
values.push(sin(right, maxDecimals));
|
|
957
|
+
return;
|
|
958
|
+
case "cos":
|
|
959
|
+
values.push(cos(right, maxDecimals));
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
633
962
|
}
|
|
634
963
|
if (op === "FACTORIAL") {
|
|
635
964
|
if (right.n === "OVERFLOW") {
|
|
636
965
|
values.push(OverflowValue);
|
|
637
966
|
return;
|
|
638
967
|
}
|
|
639
|
-
const reduced = simplify(right);
|
|
968
|
+
const reduced = toSimpleFraction(simplify(right));
|
|
640
969
|
if (reduced.d !== 1n || reduced.n < 0) throw new NotImplementedError("Factorial is only implemented for non-negative integers.", pos);
|
|
641
|
-
if (reduced.n >=
|
|
970
|
+
if (reduced.n >= 2e4) {
|
|
642
971
|
values.push(OverflowValue);
|
|
643
972
|
return;
|
|
644
973
|
}
|
|
@@ -652,44 +981,44 @@ function evaluate(tokens, options = {}) {
|
|
|
652
981
|
switch (op) {
|
|
653
982
|
case "ADD":
|
|
654
983
|
case "SUBTRACT":
|
|
655
|
-
values.push(add(left, right, op === "SUBTRACT"));
|
|
984
|
+
values.push(add(left, right, maxDecimals, op === "SUBTRACT"));
|
|
656
985
|
return;
|
|
657
986
|
case "MULTIPLY":
|
|
658
987
|
case "IMPLICIT_MUL":
|
|
659
|
-
values.push(multiply(left, right));
|
|
988
|
+
values.push(multiply(left, right, maxDecimals));
|
|
660
989
|
return;
|
|
661
990
|
case "DIVIDE":
|
|
662
|
-
values.push(divide(left, right));
|
|
991
|
+
values.push(divide(left, right, maxDecimals));
|
|
663
992
|
return;
|
|
664
993
|
case "MOD":
|
|
665
994
|
if (left.n === "OVERFLOW" || right.n === "OVERFLOW") {
|
|
666
995
|
values.push(OverflowValue);
|
|
667
996
|
return;
|
|
668
997
|
}
|
|
669
|
-
values.push(mod(left, right));
|
|
998
|
+
values.push(mod(toSimpleFraction(left), toSimpleFraction(right)));
|
|
670
999
|
return;
|
|
671
1000
|
case "EXP":
|
|
672
|
-
values.push(exponentiate(left, right,
|
|
1001
|
+
values.push(exponentiate(left, right, maxDecimals));
|
|
673
1002
|
return;
|
|
674
1003
|
}
|
|
675
1004
|
};
|
|
676
1005
|
const pushOpWithPrecedence = (currentOp, pos) => {
|
|
677
1006
|
const isUnary = isUnaryOperation(currentOp);
|
|
678
1007
|
const isRightAssociative = currentOp === "EXP" || currentOp === "IMPLICIT_MUL";
|
|
679
|
-
while (ops.length > 0 && !isUnary && (isRightAssociative ?
|
|
1008
|
+
while (ops.length > 0 && !isUnary && (isRightAssociative ? getOpPrecedence(ops.at(-1)) > getOpPrecedence(currentOp) : getOpPrecedence(ops.at(-1)) >= getOpPrecedence(currentOp))) applyOp(pos);
|
|
680
1009
|
ops.push(currentOp);
|
|
681
1010
|
};
|
|
682
1011
|
for (const token of tokens) switch (token.type) {
|
|
683
1012
|
case "NUMBER": {
|
|
684
1013
|
const frac = token.fraction || "";
|
|
685
1014
|
const fl = frac.length;
|
|
686
|
-
if (fl >
|
|
1015
|
+
if (fl > 5e4) throw new MaximumPrecisionError(fl, MAX_PRECISION);
|
|
687
1016
|
let n = BigInt(token.whole + frac);
|
|
688
1017
|
let d = fl === 0 ? 1n : 10n ** BigInt(fl);
|
|
689
1018
|
if (token.exponent) {
|
|
690
|
-
if (token.exponent.length >
|
|
1019
|
+
if (token.exponent.length > 5e4) throw new MaximumPrecisionError(token.exponent.length, MAX_PRECISION);
|
|
691
1020
|
const expValue = BigInt(token.exponent);
|
|
692
|
-
if (expValue >
|
|
1021
|
+
if (expValue > 3e5) {
|
|
693
1022
|
values.push(OverflowValue);
|
|
694
1023
|
break;
|
|
695
1024
|
}
|
|
@@ -707,27 +1036,21 @@ function evaluate(tokens, options = {}) {
|
|
|
707
1036
|
break;
|
|
708
1037
|
}
|
|
709
1038
|
case "CONST":
|
|
710
|
-
|
|
1039
|
+
values.push({
|
|
711
1040
|
n: 1n,
|
|
712
1041
|
d: 1n,
|
|
713
1042
|
c: token.id
|
|
714
1043
|
});
|
|
715
|
-
else values.push(getConst(token.id));
|
|
716
1044
|
break;
|
|
717
1045
|
case "LPAREN":
|
|
718
1046
|
case "ABS_OPEN":
|
|
719
1047
|
ops.push(token.type);
|
|
720
1048
|
break;
|
|
721
1049
|
case "RPAREN": {
|
|
722
|
-
let foundMatch = false;
|
|
723
1050
|
while (ops.length > 0) {
|
|
724
|
-
if (ops.at(-1) === "LPAREN")
|
|
725
|
-
foundMatch = true;
|
|
726
|
-
break;
|
|
727
|
-
}
|
|
1051
|
+
if (ops.at(-1) === "LPAREN") break;
|
|
728
1052
|
applyOp(token.pos);
|
|
729
1053
|
}
|
|
730
|
-
if (!foundMatch) throw new MismatchedParenthesisError(token.pos);
|
|
731
1054
|
ops.pop();
|
|
732
1055
|
const nextOp = ops.at(-1);
|
|
733
1056
|
if (!nextOp) break;
|
|
@@ -774,22 +1097,7 @@ function evaluate(tokens, options = {}) {
|
|
|
774
1097
|
case "MOD":
|
|
775
1098
|
pushOpWithPrecedence(token.type, token.pos);
|
|
776
1099
|
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;
|
|
1100
|
+
case "FUNC": pushOpWithPrecedence(token, token.pos);
|
|
793
1101
|
}
|
|
794
1102
|
while (ops.length > 0) {
|
|
795
1103
|
const top = ops.at(-1);
|
|
@@ -804,8 +1112,7 @@ function evaluate(tokens, options = {}) {
|
|
|
804
1112
|
if (finalValue.n === "OVERFLOW") throw new OverflowError();
|
|
805
1113
|
return simplify(finalValue);
|
|
806
1114
|
}
|
|
807
|
-
|
|
808
|
-
//#region src/lib/lexer.ts
|
|
1115
|
+
// src/lib/lexer.ts
|
|
809
1116
|
var LexerError = class extends PicocalcError {
|
|
810
1117
|
constructor(message, pos) {
|
|
811
1118
|
super("LexerError", message, pos);
|
|
@@ -912,8 +1219,7 @@ function tokenize(expression, options = {}) {
|
|
|
912
1219
|
}
|
|
913
1220
|
return tokens;
|
|
914
1221
|
}
|
|
915
|
-
|
|
916
|
-
//#region src/lib/symbol.ts
|
|
1222
|
+
// src/lib/symbol.ts
|
|
917
1223
|
const symbolMap = {
|
|
918
1224
|
PLUS: "+",
|
|
919
1225
|
UNARY_PLUS: "+",
|
|
@@ -937,8 +1243,22 @@ function getSym(t) {
|
|
|
937
1243
|
if (t.type === "IDENTIFIER" || t.type === "FUNC" || t.type === "CONST") return t.id;
|
|
938
1244
|
return symbolMap[t.type];
|
|
939
1245
|
}
|
|
940
|
-
|
|
941
|
-
|
|
1246
|
+
// src/lib/parser.ts
|
|
1247
|
+
const KNOWN_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
1248
|
+
"abs",
|
|
1249
|
+
"arctan",
|
|
1250
|
+
"arctanh",
|
|
1251
|
+
"ceil",
|
|
1252
|
+
"cos",
|
|
1253
|
+
"exp",
|
|
1254
|
+
"floor",
|
|
1255
|
+
"ln",
|
|
1256
|
+
"sin",
|
|
1257
|
+
"sqrt"
|
|
1258
|
+
]);
|
|
1259
|
+
function isKnownFunctionName(name) {
|
|
1260
|
+
return KNOWN_FUNCTIONS.has(name);
|
|
1261
|
+
}
|
|
942
1262
|
/**
|
|
943
1263
|
* Helper: Is the last token an "operand" (something that can be followed by a closing pipe or implicit mul)?
|
|
944
1264
|
*/
|
|
@@ -974,7 +1294,7 @@ function resolveIdentifier(token) {
|
|
|
974
1294
|
id: token.id,
|
|
975
1295
|
pos
|
|
976
1296
|
};
|
|
977
|
-
if (token.id
|
|
1297
|
+
if (isKnownFunctionName(token.id)) return {
|
|
978
1298
|
type: "FUNC",
|
|
979
1299
|
id: token.id,
|
|
980
1300
|
pos
|
|
@@ -989,9 +1309,9 @@ function parse(tokens) {
|
|
|
989
1309
|
let token = tokens[i];
|
|
990
1310
|
const prev = tokens[i - 1];
|
|
991
1311
|
if (token.type === "IDENTIFIER") token = resolveIdentifier(token);
|
|
992
|
-
const pos = token
|
|
1312
|
+
const { pos, type } = token;
|
|
993
1313
|
const isPrevOperand = isOperand(result.at(-1));
|
|
994
|
-
if (
|
|
1314
|
+
if (type === "PIPE") {
|
|
995
1315
|
if (absStack > 0 && isPrevOperand) {
|
|
996
1316
|
result.push({
|
|
997
1317
|
type: "ABS_CLOSE",
|
|
@@ -1011,19 +1331,19 @@ function parse(tokens) {
|
|
|
1011
1331
|
absStack++;
|
|
1012
1332
|
continue;
|
|
1013
1333
|
}
|
|
1014
|
-
if ((
|
|
1334
|
+
if ((type === "LPAREN" || type === "FUNC" || type === "NUMBER" || type === "CONST") && isPrevOperand) result.push({
|
|
1015
1335
|
type: "IMPLICIT_MUL",
|
|
1016
1336
|
pos
|
|
1017
1337
|
});
|
|
1018
1338
|
const prevParsed = result.at(-1);
|
|
1019
1339
|
if (isBinaryOperator(token) && isUnaryContext(prevParsed)) throw new UnexpectedOperatorError(`Unexpected operator '${getSym(token)}'`, token.pos);
|
|
1020
|
-
if ((
|
|
1340
|
+
if ((type === "FACTORIAL" || type === "RPAREN") && isUnaryContext(prevParsed)) {
|
|
1021
1341
|
if (!prevParsed) throw new UnexpectedOperatorError(`Unexpected '${getSym(token)}' at the beginning of expression`, token.pos);
|
|
1022
1342
|
throw new UnexpectedOperatorError(`Unexpected '${getSym(token)}' after '${getSym(prevParsed)}'`, token.pos);
|
|
1023
1343
|
}
|
|
1024
|
-
if (prev && (prev.type === "NUMBER" || prev.type === "IDENTIFIER" && resolveIdentifier(prev).type === "CONST") &&
|
|
1025
|
-
if ((
|
|
1026
|
-
type:
|
|
1344
|
+
if (prev && (prev.type === "NUMBER" || prev.type === "IDENTIFIER" && resolveIdentifier(prev).type === "CONST") && type === "NUMBER") throw new ParserError("Missing operator between numbers", token.pos);
|
|
1345
|
+
if ((type === "PLUS" || type === "MINUS") && isUnaryContext(prevParsed)) result.push({
|
|
1346
|
+
type: type === "PLUS" ? "UNARY_PLUS" : "UNARY_MINUS",
|
|
1027
1347
|
pos
|
|
1028
1348
|
});
|
|
1029
1349
|
else result.push(token);
|
|
@@ -1041,8 +1361,20 @@ function parse(tokens) {
|
|
|
1041
1361
|
].includes(last.type))) throw new IncompleteExpressionError(`trailing operator '${last.type === "FUNC" ? last.id : getSym(last)}'`, last.pos);
|
|
1042
1362
|
return result;
|
|
1043
1363
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1364
|
+
// src/lib/decimals.ts
|
|
1365
|
+
function toDecimal({ n, d }, { maxDecimals = 30, roundingMode = ROUNDING_MODE }) {
|
|
1366
|
+
if (d === 0n) throw new Error("Division by zero");
|
|
1367
|
+
if (n === 0n) return "0";
|
|
1368
|
+
const isNegative = n < 0n;
|
|
1369
|
+
let resultStr = (((isNegative ? -n : n) * 10n ** BigInt(maxDecimals) + (roundingMode === "round" ? d / 2n : 0n)) / d).toString();
|
|
1370
|
+
if (resultStr.length <= maxDecimals) resultStr = resultStr.padStart(maxDecimals + 1, "0");
|
|
1371
|
+
const splitIdx = resultStr.length - maxDecimals;
|
|
1372
|
+
const integerPart = resultStr.slice(0, splitIdx);
|
|
1373
|
+
const fractionalPart = resultStr.slice(splitIdx).replace(/0+$/, "");
|
|
1374
|
+
const finalResult = fractionalPart ? `${integerPart}.${fractionalPart}` : integerPart;
|
|
1375
|
+
return (isNegative && finalResult !== "0" ? "-" : "") + finalResult;
|
|
1376
|
+
}
|
|
1377
|
+
// src/utils/format-result.ts
|
|
1046
1378
|
function getConstantStr(coeff, c, e) {
|
|
1047
1379
|
if (coeff === 0n) return "0";
|
|
1048
1380
|
if (!c) return `${coeff}`;
|
|
@@ -1077,26 +1409,14 @@ function formatPrecise(v) {
|
|
|
1077
1409
|
* Converts Result to a Decimal or Fraction String.
|
|
1078
1410
|
*/
|
|
1079
1411
|
function formatResult(v, options = {}) {
|
|
1080
|
-
const { n, d } = v;
|
|
1081
1412
|
if (options.format === "precise") return formatPrecise(v);
|
|
1082
|
-
|
|
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;
|
|
1413
|
+
return toDecimal(toSimpleFraction(v, options.maxDecimals), options);
|
|
1092
1414
|
}
|
|
1093
|
-
|
|
1094
|
-
//#region src/index.ts
|
|
1415
|
+
// src/index.ts
|
|
1095
1416
|
/**
|
|
1096
1417
|
* The main entry point for the library.
|
|
1097
1418
|
*/
|
|
1098
1419
|
function calculate(expression, options) {
|
|
1099
1420
|
return formatResult(evaluate(parse(tokenize(expression, options)), options), options);
|
|
1100
1421
|
}
|
|
1101
|
-
//#endregion
|
|
1102
1422
|
export { PicocalcError, calculate };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "picocalc",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
"bigint.bitlength": "0.2.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@gameroman/config": "0.
|
|
34
|
-
"@types/bun": "1.
|
|
35
|
-
"oxfmt": "0.
|
|
36
|
-
"oxlint": "1.
|
|
37
|
-
"oxlint-tsgolint": "0.
|
|
38
|
-
"tsdown": "0.
|
|
39
|
-
"typescript": "
|
|
33
|
+
"@gameroman/config": "0.3.0",
|
|
34
|
+
"@types/bun": "1.4.4",
|
|
35
|
+
"oxfmt": "0.77.0",
|
|
36
|
+
"oxlint": "1.92.0",
|
|
37
|
+
"oxlint-tsgolint": "7.0.2006",
|
|
38
|
+
"tsdown": "0.23.5",
|
|
39
|
+
"typescript": "7.0.2"
|
|
40
40
|
}
|
|
41
41
|
}
|