@mojir/lits 2.1.31 → 2.1.32
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/cli/cli.js +11 -1421
- package/package.json +1 -1
package/dist/cli/cli.js
CHANGED
|
@@ -92,7 +92,7 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
92
92
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
93
93
|
};
|
|
94
94
|
|
|
95
|
-
var version = "2.1.
|
|
95
|
+
var version = "2.1.32";
|
|
96
96
|
|
|
97
97
|
function getCodeMarker(sourceCodeInfo) {
|
|
98
98
|
if (!sourceCodeInfo.position || !sourceCodeInfo.code)
|
|
@@ -9239,7 +9239,7 @@ var collatzSequence = {
|
|
|
9239
9239
|
'noNth': true,
|
|
9240
9240
|
};
|
|
9241
9241
|
|
|
9242
|
-
function isPrime
|
|
9242
|
+
function isPrime(num) {
|
|
9243
9243
|
if (num <= 1) {
|
|
9244
9244
|
return false;
|
|
9245
9245
|
}
|
|
@@ -9261,18 +9261,18 @@ var primeSequence = {
|
|
|
9261
9261
|
var primes = [];
|
|
9262
9262
|
var num = 2;
|
|
9263
9263
|
while (primes.length < length) {
|
|
9264
|
-
if (isPrime
|
|
9264
|
+
if (isPrime(num)) {
|
|
9265
9265
|
primes.push(num);
|
|
9266
9266
|
}
|
|
9267
9267
|
num += 1;
|
|
9268
9268
|
}
|
|
9269
9269
|
return primes;
|
|
9270
9270
|
},
|
|
9271
|
-
'nth:prime?': function (n) { return isPrime
|
|
9271
|
+
'nth:prime?': function (n) { return isPrime(n); },
|
|
9272
9272
|
'nth:prime-take-while': function (takeWhile) {
|
|
9273
9273
|
var primes = [];
|
|
9274
9274
|
for (var i = 2;; i += 1) {
|
|
9275
|
-
if (!isPrime
|
|
9275
|
+
if (!isPrime(i)) {
|
|
9276
9276
|
continue;
|
|
9277
9277
|
}
|
|
9278
9278
|
if (!takeWhile(i, primes.length)) {
|
|
@@ -9288,7 +9288,7 @@ function isComposite(num) {
|
|
|
9288
9288
|
if (num <= 1) {
|
|
9289
9289
|
return false;
|
|
9290
9290
|
}
|
|
9291
|
-
return !isPrime
|
|
9291
|
+
return !isPrime(num);
|
|
9292
9292
|
}
|
|
9293
9293
|
var compositeSequence = {
|
|
9294
9294
|
'nth:composite-seq': function (length) {
|
|
@@ -31444,1382 +31444,6 @@ function isAbstractLitsError(error) {
|
|
|
31444
31444
|
return error instanceof LitsError;
|
|
31445
31445
|
}
|
|
31446
31446
|
|
|
31447
|
-
const defaultConfig = {
|
|
31448
|
-
spaceSeparation: false,
|
|
31449
|
-
precision: 8,
|
|
31450
|
-
epsilon: 1e-10,
|
|
31451
|
-
};
|
|
31452
|
-
const CONFIG = {
|
|
31453
|
-
spaceSeparation: false,
|
|
31454
|
-
precision: 8,
|
|
31455
|
-
epsilon: 1e-10,
|
|
31456
|
-
};
|
|
31457
|
-
function setConfig(newConfig) {
|
|
31458
|
-
CONFIG.spaceSeparation = newConfig.spaceSeparation ?? defaultConfig.spaceSeparation;
|
|
31459
|
-
CONFIG.precision = newConfig.precision ?? defaultConfig.precision;
|
|
31460
|
-
CONFIG.epsilon = newConfig.epsilon ?? defaultConfig.epsilon;
|
|
31461
|
-
}
|
|
31462
|
-
/**
|
|
31463
|
-
* Mathematical constants used for symbolic representation
|
|
31464
|
-
*/
|
|
31465
|
-
const CONSTANTS = [
|
|
31466
|
-
{ getSymbol: () => 'π', value: Math.PI },
|
|
31467
|
-
{ getSymbol: () => 'e', value: Math.E },
|
|
31468
|
-
{ getSymbol: () => 'φ', value: (1 + Math.sqrt(5)) / 2 },
|
|
31469
|
-
{ getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? '√(2)' : '√2', value: Math.sqrt(2) },
|
|
31470
|
-
{ getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? '√(3)' : '√3', value: Math.sqrt(3) },
|
|
31471
|
-
{ getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? '√(5)' : '√5', value: Math.sqrt(5) },
|
|
31472
|
-
{ getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? '√(7)' : '√7', value: Math.sqrt(7) },
|
|
31473
|
-
{ getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? '√(11)' : '√11', value: Math.sqrt(11) },
|
|
31474
|
-
{ getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? '√(13)' : '√13', value: Math.sqrt(13) },
|
|
31475
|
-
{ getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? '√(17)' : '√17', value: Math.sqrt(17) },
|
|
31476
|
-
{ getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? '√(19)' : '√19', value: Math.sqrt(19) },
|
|
31477
|
-
{ getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? '√(π)' : '√π', value: Math.sqrt(Math.PI) },
|
|
31478
|
-
{ getSymbol: () => 'ln(2)', value: Math.LN2 },
|
|
31479
|
-
{ getSymbol: () => 'ln(10)', value: Math.LN10 },
|
|
31480
|
-
{ getSymbol: () => 'log₂(e)', value: Math.LOG2E },
|
|
31481
|
-
{ getSymbol: () => 'log₁₀(e)', value: Math.LOG10E },
|
|
31482
|
-
];
|
|
31483
|
-
/**
|
|
31484
|
-
* Trigonometric values with their exact symbolic representations
|
|
31485
|
-
*/
|
|
31486
|
-
const TRIG_VALUES = [
|
|
31487
|
-
// sin values
|
|
31488
|
-
{ value: Math.sin(Math.PI / 6), getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? 'sin(π / 6)' : 'sin(π/6)', getExactForm: () => CONFIG.spaceSeparation ? '1 / 2' : '1/2' },
|
|
31489
|
-
{ value: Math.sin(Math.PI / 4), getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? 'sin(π / 4)' : 'sin(π/4)', getExactForm: () => CONFIG.spaceSeparation ? '√2 / 2' : '√2/2' },
|
|
31490
|
-
{ value: Math.sin(Math.PI / 3), getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? 'sin(π / 3)' : 'sin(π/3)', getExactForm: () => CONFIG.spaceSeparation ? '√3 / 2' : '√3/2' },
|
|
31491
|
-
{ value: Math.sin(Math.PI / 2), getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? 'sin(π / 2)' : 'sin(π/2)', getExactForm: () => '1' },
|
|
31492
|
-
// cos values
|
|
31493
|
-
{ value: Math.cos(0), getSymbol: () => 'cos(0)', getExactForm: () => '1' },
|
|
31494
|
-
{ value: Math.cos(Math.PI / 6), getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? 'cos(π / 6)' : 'cos(π/6)', getExactForm: () => CONFIG.spaceSeparation ? '√(3) / 2' : '√3/2' },
|
|
31495
|
-
{ value: Math.cos(Math.PI / 4), getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? 'cos(π / 4)' : 'cos(π/4)', getExactForm: () => CONFIG.spaceSeparation ? '√(2) / 2' : '√2/2' },
|
|
31496
|
-
{ value: Math.cos(Math.PI / 3), getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? 'cos(π / 3)' : 'cos(π/3)', getExactForm: () => CONFIG.spaceSeparation ? '1 / 2' : '1/2' },
|
|
31497
|
-
{ value: Math.cos(Math.PI / 2), getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? 'cos(π / 2)' : 'cos(π/2)', getExactForm: () => '0' },
|
|
31498
|
-
// tan values
|
|
31499
|
-
{ value: Math.tan(Math.PI / 6), getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? 'tan(π / 6)' : 'tan(π/6)', getExactForm: () => CONFIG.spaceSeparation ? '1 / √(3)' : '1/√3' },
|
|
31500
|
-
{ value: Math.tan(Math.PI / 4), getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? 'tan(π / 4)' : 'tan(π/4)', getExactForm: () => '1' },
|
|
31501
|
-
{ value: Math.tan(Math.PI / 3), getSymbol: noSpaceSeparation => CONFIG.spaceSeparation && !noSpaceSeparation ? 'tan(π / 3)' : 'tan(π/3)', getExactForm: () => CONFIG.spaceSeparation ? '√(3)' : '√3' },
|
|
31502
|
-
];
|
|
31503
|
-
|
|
31504
|
-
/**
|
|
31505
|
-
* Factorizes a number under a radical to extract perfect square factors
|
|
31506
|
-
* @param n The number to factorize
|
|
31507
|
-
* @returns Object containing the coefficient and remaining radicand
|
|
31508
|
-
*/
|
|
31509
|
-
function factorizeRadicand(n) {
|
|
31510
|
-
// Find largest perfect square factor
|
|
31511
|
-
let coefficient = 1;
|
|
31512
|
-
let i = 2;
|
|
31513
|
-
while (i * i <= n) {
|
|
31514
|
-
// Check if i² is a factor of n
|
|
31515
|
-
if (n % (i * i) === 0) {
|
|
31516
|
-
coefficient *= i;
|
|
31517
|
-
n /= (i * i);
|
|
31518
|
-
// Continue with the same i to check for multiple powers
|
|
31519
|
-
continue;
|
|
31520
|
-
}
|
|
31521
|
-
i++;
|
|
31522
|
-
}
|
|
31523
|
-
return { coefficient, radicand: n };
|
|
31524
|
-
}
|
|
31525
|
-
/**
|
|
31526
|
-
* Finds the greatest common divisor of two numbers
|
|
31527
|
-
* @param a First number
|
|
31528
|
-
* @param b Second number
|
|
31529
|
-
* @returns The GCD of a and b
|
|
31530
|
-
*/
|
|
31531
|
-
function findGCD(a, b) {
|
|
31532
|
-
a = Math.abs(a);
|
|
31533
|
-
b = Math.abs(b);
|
|
31534
|
-
while (b !== 0) {
|
|
31535
|
-
const temp = b;
|
|
31536
|
-
b = a % b;
|
|
31537
|
-
a = temp;
|
|
31538
|
-
}
|
|
31539
|
-
return a;
|
|
31540
|
-
}
|
|
31541
|
-
/**
|
|
31542
|
-
* Checks if a number is prime
|
|
31543
|
-
* @param n The number to check
|
|
31544
|
-
* @returns True if n is prime, false otherwise
|
|
31545
|
-
*/
|
|
31546
|
-
function isPrime(n) {
|
|
31547
|
-
if (n <= 1)
|
|
31548
|
-
return false;
|
|
31549
|
-
if (n <= 3)
|
|
31550
|
-
return true;
|
|
31551
|
-
if (n % 2 === 0 || n % 3 === 0)
|
|
31552
|
-
return false;
|
|
31553
|
-
let i = 5;
|
|
31554
|
-
while (i * i <= n) {
|
|
31555
|
-
if (n % i === 0 || n % (i + 2) === 0)
|
|
31556
|
-
return false;
|
|
31557
|
-
i += 6;
|
|
31558
|
-
}
|
|
31559
|
-
return true;
|
|
31560
|
-
}
|
|
31561
|
-
/**
|
|
31562
|
-
* Determines if a number's square root should be preserved in direct form
|
|
31563
|
-
* @param n The number to check
|
|
31564
|
-
* @returns True if the square root should be kept in direct form
|
|
31565
|
-
*/
|
|
31566
|
-
function shouldPreserveDirectRadical(n) {
|
|
31567
|
-
// Always preserve direct form for prime numbers
|
|
31568
|
-
if (isPrime(n))
|
|
31569
|
-
return true;
|
|
31570
|
-
// Always preserve direct form for small integers
|
|
31571
|
-
if (n <= 30)
|
|
31572
|
-
return true;
|
|
31573
|
-
// Check if n has any perfect square factors
|
|
31574
|
-
const factorized = factorizeRadicand(n);
|
|
31575
|
-
// If no perfect square factors were found, preserve direct form
|
|
31576
|
-
return factorized.coefficient === 1;
|
|
31577
|
-
}
|
|
31578
|
-
|
|
31579
|
-
function isNumberNode(node) {
|
|
31580
|
-
return node.type === 'Number';
|
|
31581
|
-
}
|
|
31582
|
-
function isUnaryOpNode(node) {
|
|
31583
|
-
return node.type === 'UnaryOp';
|
|
31584
|
-
}
|
|
31585
|
-
function isBinaryOpNode(node) {
|
|
31586
|
-
return node.type === 'BinaryOp';
|
|
31587
|
-
}
|
|
31588
|
-
function isRootNode(node) {
|
|
31589
|
-
return node.type === 'Root';
|
|
31590
|
-
}
|
|
31591
|
-
function isConstantNode(node) {
|
|
31592
|
-
return node.type === 'Constant';
|
|
31593
|
-
}
|
|
31594
|
-
function isPowerNode(node) {
|
|
31595
|
-
return node.type === 'Power';
|
|
31596
|
-
}
|
|
31597
|
-
|
|
31598
|
-
/**
|
|
31599
|
-
* Helper function to determine expression type priority for ordering
|
|
31600
|
-
* @returns A priority number (lower number = higher priority)
|
|
31601
|
-
*/
|
|
31602
|
-
function getExpressionPriority(node) {
|
|
31603
|
-
// Priority order: Number < Root/Power < Constant < Other
|
|
31604
|
-
if (isNumberNode(node))
|
|
31605
|
-
return 1;
|
|
31606
|
-
if (isRootNode(node))
|
|
31607
|
-
return 2;
|
|
31608
|
-
// Handle other node types here
|
|
31609
|
-
return 6; // Default for other types
|
|
31610
|
-
}
|
|
31611
|
-
/**
|
|
31612
|
-
* Determines if two expressions should be swapped in multiplication
|
|
31613
|
-
* @returns true if expressions should be swapped
|
|
31614
|
-
*/
|
|
31615
|
-
function shouldSwapInMultiplication(left, right) {
|
|
31616
|
-
const leftPriority = getExpressionPriority(left);
|
|
31617
|
-
const rightPriority = getExpressionPriority(right);
|
|
31618
|
-
// Lower priority number should come first
|
|
31619
|
-
return leftPriority > rightPriority;
|
|
31620
|
-
}
|
|
31621
|
-
|
|
31622
|
-
/**
|
|
31623
|
-
* Base abstract class for all expression nodes
|
|
31624
|
-
*/
|
|
31625
|
-
class ExprNode {
|
|
31626
|
-
}
|
|
31627
|
-
|
|
31628
|
-
/**
|
|
31629
|
-
* Checks if a node represents exactly zero within epsilon tolerance
|
|
31630
|
-
*/
|
|
31631
|
-
function isZero(node) {
|
|
31632
|
-
return isNumberNode(node) && Math.abs(node.evaluate()) < 1e-10;
|
|
31633
|
-
}
|
|
31634
|
-
/**
|
|
31635
|
-
* Checks if a node represents exactly one within epsilon tolerance
|
|
31636
|
-
*/
|
|
31637
|
-
function isOne(node) {
|
|
31638
|
-
return isNumberNode(node) && Math.abs(node.evaluate() - 1) < 1e-10;
|
|
31639
|
-
}
|
|
31640
|
-
/**
|
|
31641
|
-
* Node for numeric constants (integers, simple fractions, etc.)
|
|
31642
|
-
*/
|
|
31643
|
-
class NumberNode extends ExprNode {
|
|
31644
|
-
constructor(value) {
|
|
31645
|
-
super();
|
|
31646
|
-
Object.defineProperty(this, "value", {
|
|
31647
|
-
enumerable: true,
|
|
31648
|
-
configurable: true,
|
|
31649
|
-
writable: true,
|
|
31650
|
-
value: value
|
|
31651
|
-
});
|
|
31652
|
-
Object.defineProperty(this, "type", {
|
|
31653
|
-
enumerable: true,
|
|
31654
|
-
configurable: true,
|
|
31655
|
-
writable: true,
|
|
31656
|
-
value: 'Number'
|
|
31657
|
-
});
|
|
31658
|
-
}
|
|
31659
|
-
evaluate() {
|
|
31660
|
-
return this.value;
|
|
31661
|
-
}
|
|
31662
|
-
toString() {
|
|
31663
|
-
// Round values extremely close to zero to exactly zero
|
|
31664
|
-
if (Math.abs(this.value) < CONFIG.epsilon) {
|
|
31665
|
-
return "0";
|
|
31666
|
-
}
|
|
31667
|
-
if (this.value === Number.POSITIVE_INFINITY) {
|
|
31668
|
-
return "∞";
|
|
31669
|
-
}
|
|
31670
|
-
if (this.value === Number.NEGATIVE_INFINITY) {
|
|
31671
|
-
return "-∞";
|
|
31672
|
-
}
|
|
31673
|
-
if (Number.isInteger(this.value)) {
|
|
31674
|
-
return this.value.toString();
|
|
31675
|
-
}
|
|
31676
|
-
// Handle fractions if it's a "nice" fraction
|
|
31677
|
-
const MAX_DENOMINATOR = 1000;
|
|
31678
|
-
for (let denominator = 2; denominator <= MAX_DENOMINATOR; denominator++) {
|
|
31679
|
-
const numerator = Math.round(this.value * denominator);
|
|
31680
|
-
if (Math.abs(this.value - numerator / denominator) < CONFIG.epsilon) {
|
|
31681
|
-
const gcd = findGCD(Math.abs(numerator), denominator);
|
|
31682
|
-
return CONFIG.spaceSeparation ? `${numerator / gcd} / ${denominator / gcd}` : `${numerator / gcd}/${denominator / gcd}`;
|
|
31683
|
-
}
|
|
31684
|
-
}
|
|
31685
|
-
// Just return the decimal representation
|
|
31686
|
-
const valueStr = this.value.toString();
|
|
31687
|
-
if (valueStr.includes('.')) {
|
|
31688
|
-
const [, decimalPart] = valueStr.split('.');
|
|
31689
|
-
if (decimalPart.length > 8) {
|
|
31690
|
-
return this.value.toFixed(CONFIG.precision);
|
|
31691
|
-
}
|
|
31692
|
-
}
|
|
31693
|
-
return this.value.toString();
|
|
31694
|
-
}
|
|
31695
|
-
simplify() {
|
|
31696
|
-
return this; // Numbers are already in simplest form
|
|
31697
|
-
}
|
|
31698
|
-
equals(other) {
|
|
31699
|
-
if (!(isNumberNode(other)))
|
|
31700
|
-
return false;
|
|
31701
|
-
return Math.abs(this.value - other.evaluate()) < CONFIG.epsilon;
|
|
31702
|
-
}
|
|
31703
|
-
}
|
|
31704
|
-
|
|
31705
|
-
/**
|
|
31706
|
-
* Node for binary operations (+, -, *, /)
|
|
31707
|
-
*/
|
|
31708
|
-
class BinaryOpNode extends ExprNode {
|
|
31709
|
-
constructor(op, left, right) {
|
|
31710
|
-
super();
|
|
31711
|
-
Object.defineProperty(this, "op", {
|
|
31712
|
-
enumerable: true,
|
|
31713
|
-
configurable: true,
|
|
31714
|
-
writable: true,
|
|
31715
|
-
value: op
|
|
31716
|
-
});
|
|
31717
|
-
Object.defineProperty(this, "left", {
|
|
31718
|
-
enumerable: true,
|
|
31719
|
-
configurable: true,
|
|
31720
|
-
writable: true,
|
|
31721
|
-
value: left
|
|
31722
|
-
});
|
|
31723
|
-
Object.defineProperty(this, "right", {
|
|
31724
|
-
enumerable: true,
|
|
31725
|
-
configurable: true,
|
|
31726
|
-
writable: true,
|
|
31727
|
-
value: right
|
|
31728
|
-
});
|
|
31729
|
-
Object.defineProperty(this, "type", {
|
|
31730
|
-
enumerable: true,
|
|
31731
|
-
configurable: true,
|
|
31732
|
-
writable: true,
|
|
31733
|
-
value: 'BinaryOp'
|
|
31734
|
-
});
|
|
31735
|
-
// Apply automatic ordering for multiplication operations
|
|
31736
|
-
if (this.op === '*' && shouldSwapInMultiplication(left, right)) {
|
|
31737
|
-
// Swap the operands if they're in the wrong order
|
|
31738
|
-
this.left = right;
|
|
31739
|
-
this.right = left;
|
|
31740
|
-
}
|
|
31741
|
-
}
|
|
31742
|
-
evaluate() {
|
|
31743
|
-
const leftVal = this.left.evaluate();
|
|
31744
|
-
const rightVal = this.right.evaluate();
|
|
31745
|
-
switch (this.op) {
|
|
31746
|
-
case '+': return leftVal + rightVal;
|
|
31747
|
-
case '-': return leftVal - rightVal;
|
|
31748
|
-
case '*': return leftVal * rightVal;
|
|
31749
|
-
case '/': return leftVal / rightVal;
|
|
31750
|
-
}
|
|
31751
|
-
}
|
|
31752
|
-
toString() {
|
|
31753
|
-
let leftStr = this.left.toString();
|
|
31754
|
-
let rightStr = this.right.toString();
|
|
31755
|
-
// Add parentheses if needed
|
|
31756
|
-
if ((this.op === '+' || this.op === '-')
|
|
31757
|
-
&& (isBinaryOpNode(this.left) && (this.left.getOp() === '+' || this.left.getOp() === '-'))) {
|
|
31758
|
-
leftStr = `(${leftStr})`;
|
|
31759
|
-
}
|
|
31760
|
-
else if ((this.op === '+' || this.op === '-')
|
|
31761
|
-
&& (isBinaryOpNode(this.right) && (this.right.getOp() === '+' || this.right.getOp() === '-'))) {
|
|
31762
|
-
rightStr = `(${rightStr})`;
|
|
31763
|
-
}
|
|
31764
|
-
if (this.op === '*') {
|
|
31765
|
-
// Double-check order at display time
|
|
31766
|
-
if (shouldSwapInMultiplication(this.left, this.right)) {
|
|
31767
|
-
return CONFIG.spaceSeparation
|
|
31768
|
-
? `${this.right.toString()} · ${this.left.toString()}`
|
|
31769
|
-
: `${this.right.toString()}·${this.left.toString()}`;
|
|
31770
|
-
}
|
|
31771
|
-
return CONFIG.spaceSeparation
|
|
31772
|
-
? `${this.left.toString()} · ${this.right.toString()}`
|
|
31773
|
-
: `${this.left.toString()}·${this.right.toString()}`;
|
|
31774
|
-
}
|
|
31775
|
-
else {
|
|
31776
|
-
return CONFIG.spaceSeparation
|
|
31777
|
-
? `${leftStr} ${this.op} ${rightStr}`
|
|
31778
|
-
: `${leftStr}${this.op}${rightStr}`;
|
|
31779
|
-
}
|
|
31780
|
-
}
|
|
31781
|
-
getOp() {
|
|
31782
|
-
return this.op;
|
|
31783
|
-
}
|
|
31784
|
-
getLeft() {
|
|
31785
|
-
return this.left;
|
|
31786
|
-
}
|
|
31787
|
-
getRight() {
|
|
31788
|
-
return this.right;
|
|
31789
|
-
}
|
|
31790
|
-
simplify() {
|
|
31791
|
-
// First simplify both children
|
|
31792
|
-
const left = this.left.simplify();
|
|
31793
|
-
const right = this.right.simplify();
|
|
31794
|
-
// If both are numbers, perform the operation
|
|
31795
|
-
if (isNumberNode(left) && isNumberNode(right)) {
|
|
31796
|
-
return new NumberNode(this.evaluate());
|
|
31797
|
-
}
|
|
31798
|
-
// Simplification rules for addition
|
|
31799
|
-
if (this.op === '+') {
|
|
31800
|
-
// a + 0 = a
|
|
31801
|
-
if (isNumberNode(right) && right.evaluate() === 0) {
|
|
31802
|
-
return left;
|
|
31803
|
-
}
|
|
31804
|
-
// 0 + a = a
|
|
31805
|
-
if (isNumberNode(left) && left.evaluate() === 0) {
|
|
31806
|
-
return right;
|
|
31807
|
-
}
|
|
31808
|
-
}
|
|
31809
|
-
// Simplification rules for subtraction
|
|
31810
|
-
if (this.op === '-') {
|
|
31811
|
-
// a - 0 = a
|
|
31812
|
-
if (isNumberNode(right) && right.evaluate() === 0) {
|
|
31813
|
-
return left;
|
|
31814
|
-
}
|
|
31815
|
-
// 0 - a = -a
|
|
31816
|
-
if (isNumberNode(left) && left.evaluate() === 0) {
|
|
31817
|
-
return new UnaryOpNode('-', right);
|
|
31818
|
-
}
|
|
31819
|
-
// a - a = 0
|
|
31820
|
-
if (left.equals(right)) {
|
|
31821
|
-
return new NumberNode(0);
|
|
31822
|
-
}
|
|
31823
|
-
}
|
|
31824
|
-
// Simplification rules for multiplication
|
|
31825
|
-
if (this.op === '*') {
|
|
31826
|
-
// a * 0 = 0
|
|
31827
|
-
if (isZero(left) || isZero(right)) {
|
|
31828
|
-
return new NumberNode(0);
|
|
31829
|
-
}
|
|
31830
|
-
// a * 1 = a
|
|
31831
|
-
if (isOne(right)) {
|
|
31832
|
-
return left;
|
|
31833
|
-
}
|
|
31834
|
-
// 1 * a = a
|
|
31835
|
-
if (isOne(left)) {
|
|
31836
|
-
return right;
|
|
31837
|
-
}
|
|
31838
|
-
// Special case for n·√m/2 (12·√3/2 case)
|
|
31839
|
-
if (isNumberNode(left)
|
|
31840
|
-
&& isBinaryOpNode(right)
|
|
31841
|
-
&& right.getOp() === '/'
|
|
31842
|
-
&& isNumberNode(right.getRight())
|
|
31843
|
-
&& right.getRight().evaluate() === 2
|
|
31844
|
-
&& isRootNode(right.getLeft())) {
|
|
31845
|
-
const n = left.evaluate();
|
|
31846
|
-
const rootNode = right.getLeft();
|
|
31847
|
-
return new BinaryOpNode('*', new NumberNode(n / 2), rootNode);
|
|
31848
|
-
}
|
|
31849
|
-
// Handle multiplications with fractions
|
|
31850
|
-
if (isBinaryOpNode(left) && left.getOp() === '/') {
|
|
31851
|
-
// (a/b) * c = (a*c)/b
|
|
31852
|
-
const fraction = left;
|
|
31853
|
-
const numerator = fraction.getLeft();
|
|
31854
|
-
const denominator = fraction.getRight();
|
|
31855
|
-
return new BinaryOpNode('/', new BinaryOpNode('*', numerator, right), denominator).simplify();
|
|
31856
|
-
}
|
|
31857
|
-
if (isBinaryOpNode(right) && right.getOp() === '/') {
|
|
31858
|
-
// c * (a/b) = (c*a)/b
|
|
31859
|
-
const fraction = right;
|
|
31860
|
-
const numerator = fraction.getLeft();
|
|
31861
|
-
const denominator = fraction.getRight();
|
|
31862
|
-
return new BinaryOpNode('/', new BinaryOpNode('*', left, numerator), denominator).simplify();
|
|
31863
|
-
}
|
|
31864
|
-
// Multiple numeric factors: (2·3)·π = 6·π
|
|
31865
|
-
if (isNumberNode(left)
|
|
31866
|
-
&& isBinaryOpNode(right)
|
|
31867
|
-
&& right.getOp() === '*'
|
|
31868
|
-
&& isNumberNode(right.getLeft())) {
|
|
31869
|
-
const n1 = left.evaluate();
|
|
31870
|
-
const n2 = (right).getLeft().evaluate();
|
|
31871
|
-
const restTerm = right.getRight();
|
|
31872
|
-
return new BinaryOpNode('*', new NumberNode(n1 * n2), restTerm).simplify();
|
|
31873
|
-
}
|
|
31874
|
-
// Consolidate products of square roots: √a·√b = √(a·b)
|
|
31875
|
-
if (isRootNode(left) && isRootNode(right)) {
|
|
31876
|
-
const leftOperand = getRootOperand(left);
|
|
31877
|
-
const rightOperand = getRootOperand(right);
|
|
31878
|
-
// Create √(a·b) instead of √a·√b
|
|
31879
|
-
if (isNumberNode(leftOperand) && isNumberNode(rightOperand)) {
|
|
31880
|
-
const newRadicand = leftOperand.evaluate() * rightOperand.evaluate();
|
|
31881
|
-
return new RootNode(new NumberNode(newRadicand)).simplify();
|
|
31882
|
-
}
|
|
31883
|
-
}
|
|
31884
|
-
}
|
|
31885
|
-
// Simplification rules for division
|
|
31886
|
-
if (this.op === '/') {
|
|
31887
|
-
// 0 / a = 0
|
|
31888
|
-
if (isZero(left)) {
|
|
31889
|
-
return new NumberNode(0);
|
|
31890
|
-
}
|
|
31891
|
-
// a / 1 = a
|
|
31892
|
-
if (isOne(right)) {
|
|
31893
|
-
return left;
|
|
31894
|
-
}
|
|
31895
|
-
// a / a = 1
|
|
31896
|
-
if (left.equals(right)) {
|
|
31897
|
-
return new NumberNode(1);
|
|
31898
|
-
}
|
|
31899
|
-
// Simplify numeric fractions
|
|
31900
|
-
if (isNumberNode(left) && isNumberNode(right)) {
|
|
31901
|
-
const num = left.evaluate();
|
|
31902
|
-
const denom = right.evaluate();
|
|
31903
|
-
if (Number.isInteger(num) && Number.isInteger(denom)) {
|
|
31904
|
-
const gcd = findGCD(Math.abs(num), Math.abs(denom));
|
|
31905
|
-
if (gcd > 1) {
|
|
31906
|
-
return new BinaryOpNode('/', new NumberNode(num / gcd), new NumberNode(denom / gcd));
|
|
31907
|
-
}
|
|
31908
|
-
}
|
|
31909
|
-
}
|
|
31910
|
-
// (a*b) / b = a
|
|
31911
|
-
if (isBinaryOpNode(left) && left.getOp() === '*') {
|
|
31912
|
-
const product = left;
|
|
31913
|
-
// Case for n·something / n = something (6·√3/6 = √3)
|
|
31914
|
-
if (isNumberNode(product.getLeft())
|
|
31915
|
-
&& isNumberNode(right)
|
|
31916
|
-
&& Math.abs(product.getLeft().evaluate() - right.evaluate()) < 1e-10) {
|
|
31917
|
-
return product.getRight();
|
|
31918
|
-
}
|
|
31919
|
-
if (isNumberNode(product.getRight())
|
|
31920
|
-
&& isNumberNode(right)
|
|
31921
|
-
&& Math.abs(product.getRight().evaluate() - right.evaluate()) < 1e-10) {
|
|
31922
|
-
return product.getLeft();
|
|
31923
|
-
}
|
|
31924
|
-
// General case
|
|
31925
|
-
if (product.getLeft().equals(right)) {
|
|
31926
|
-
return product.getRight();
|
|
31927
|
-
}
|
|
31928
|
-
if (product.getRight().equals(right)) {
|
|
31929
|
-
return product.getLeft();
|
|
31930
|
-
}
|
|
31931
|
-
}
|
|
31932
|
-
}
|
|
31933
|
-
// If no simplification rules apply, return a new node with simplified children
|
|
31934
|
-
return new BinaryOpNode(this.op, left, right);
|
|
31935
|
-
}
|
|
31936
|
-
equals(other) {
|
|
31937
|
-
if (!(isBinaryOpNode(other)))
|
|
31938
|
-
return false;
|
|
31939
|
-
const otherOp = other;
|
|
31940
|
-
return this.op === otherOp.getOp()
|
|
31941
|
-
&& this.left.equals(otherOp.getLeft())
|
|
31942
|
-
&& this.right.equals(otherOp.getRight());
|
|
31943
|
-
}
|
|
31944
|
-
}
|
|
31945
|
-
/**
|
|
31946
|
-
* Extracts the radicand from a square root expression
|
|
31947
|
-
*/
|
|
31948
|
-
function getRootOperand(node) {
|
|
31949
|
-
if (isRootNode(node)) {
|
|
31950
|
-
return node.getOperand();
|
|
31951
|
-
}
|
|
31952
|
-
if (isUnaryOpNode(node) && node.getOp() === 'sqrt') {
|
|
31953
|
-
return node.getOperand();
|
|
31954
|
-
}
|
|
31955
|
-
throw new Error('Not a root node');
|
|
31956
|
-
}
|
|
31957
|
-
/**
|
|
31958
|
-
* Checks if a node represents a square root expression
|
|
31959
|
-
*/
|
|
31960
|
-
function isRootLikeNode(node) {
|
|
31961
|
-
return isRootNode(node)
|
|
31962
|
-
|| (isUnaryOpNode(node) && node.getOp() === 'sqrt');
|
|
31963
|
-
}
|
|
31964
|
-
/**
|
|
31965
|
-
* Specialized node for square roots for nicer formatting
|
|
31966
|
-
*/
|
|
31967
|
-
class RootNode extends ExprNode {
|
|
31968
|
-
constructor(operand) {
|
|
31969
|
-
super();
|
|
31970
|
-
Object.defineProperty(this, "operand", {
|
|
31971
|
-
enumerable: true,
|
|
31972
|
-
configurable: true,
|
|
31973
|
-
writable: true,
|
|
31974
|
-
value: operand
|
|
31975
|
-
});
|
|
31976
|
-
Object.defineProperty(this, "type", {
|
|
31977
|
-
enumerable: true,
|
|
31978
|
-
configurable: true,
|
|
31979
|
-
writable: true,
|
|
31980
|
-
value: 'Root'
|
|
31981
|
-
});
|
|
31982
|
-
}
|
|
31983
|
-
evaluate() {
|
|
31984
|
-
return Math.sqrt(this.operand.evaluate());
|
|
31985
|
-
}
|
|
31986
|
-
toString() {
|
|
31987
|
-
if (CONFIG.spaceSeparation) {
|
|
31988
|
-
const operandStr = this.operand.toString();
|
|
31989
|
-
if (!isNumberNode(this.operand) || operandStr.includes('/')) {
|
|
31990
|
-
return `√(${operandStr})`;
|
|
31991
|
-
}
|
|
31992
|
-
else {
|
|
31993
|
-
return `√${operandStr}`;
|
|
31994
|
-
}
|
|
31995
|
-
}
|
|
31996
|
-
else {
|
|
31997
|
-
return `√${this.operand.toString()}`;
|
|
31998
|
-
}
|
|
31999
|
-
}
|
|
32000
|
-
simplify() {
|
|
32001
|
-
const operand = this.operand.simplify();
|
|
32002
|
-
// Simplify sqrt of perfect squares
|
|
32003
|
-
if (isNumberNode(operand)) {
|
|
32004
|
-
const val = operand.evaluate();
|
|
32005
|
-
const sqrtVal = Math.sqrt(val);
|
|
32006
|
-
// If it's a perfect square, return the number
|
|
32007
|
-
if (Number.isInteger(sqrtVal)) {
|
|
32008
|
-
return new NumberNode(sqrtVal);
|
|
32009
|
-
}
|
|
32010
|
-
// Otherwise, check if we can simplify the radicand
|
|
32011
|
-
const factorized = factorizeRadicand(val);
|
|
32012
|
-
if (factorized.coefficient > 1) {
|
|
32013
|
-
// If there's a perfect square factor, extract it
|
|
32014
|
-
return new BinaryOpNode('*', new NumberNode(factorized.coefficient), new RootNode(new NumberNode(factorized.radicand)));
|
|
32015
|
-
}
|
|
32016
|
-
}
|
|
32017
|
-
// Handle product of radicands under a root
|
|
32018
|
-
if (isBinaryOpNode(operand) && operand.getOp() === '*') {
|
|
32019
|
-
const left = operand.getLeft();
|
|
32020
|
-
const right = operand.getRight();
|
|
32021
|
-
// We can't simplify if they aren't both numbers
|
|
32022
|
-
if (isNumberNode(left) && isNumberNode(right)) {
|
|
32023
|
-
const leftVal = left.evaluate();
|
|
32024
|
-
const rightVal = right.evaluate();
|
|
32025
|
-
// Try to factorize the product
|
|
32026
|
-
const factorized = factorizeRadicand(leftVal * rightVal);
|
|
32027
|
-
if (factorized.coefficient > 1) {
|
|
32028
|
-
// If there's a perfect square factor, extract it
|
|
32029
|
-
return new BinaryOpNode('*', new NumberNode(factorized.coefficient), new RootNode(new NumberNode(factorized.radicand))).simplify();
|
|
32030
|
-
}
|
|
32031
|
-
}
|
|
32032
|
-
}
|
|
32033
|
-
return new RootNode(operand);
|
|
32034
|
-
}
|
|
32035
|
-
getOperand() {
|
|
32036
|
-
return this.operand;
|
|
32037
|
-
}
|
|
32038
|
-
equals(other) {
|
|
32039
|
-
if (!(isRootLikeNode(other)))
|
|
32040
|
-
return false;
|
|
32041
|
-
return this.operand.equals(other.getOperand());
|
|
32042
|
-
}
|
|
32043
|
-
}
|
|
32044
|
-
/**
|
|
32045
|
-
* Specialized node for power expressions
|
|
32046
|
-
*/
|
|
32047
|
-
class PowerNode extends ExprNode {
|
|
32048
|
-
constructor(base, exponent) {
|
|
32049
|
-
super();
|
|
32050
|
-
Object.defineProperty(this, "base", {
|
|
32051
|
-
enumerable: true,
|
|
32052
|
-
configurable: true,
|
|
32053
|
-
writable: true,
|
|
32054
|
-
value: base
|
|
32055
|
-
});
|
|
32056
|
-
Object.defineProperty(this, "exponent", {
|
|
32057
|
-
enumerable: true,
|
|
32058
|
-
configurable: true,
|
|
32059
|
-
writable: true,
|
|
32060
|
-
value: exponent
|
|
32061
|
-
});
|
|
32062
|
-
Object.defineProperty(this, "type", {
|
|
32063
|
-
enumerable: true,
|
|
32064
|
-
configurable: true,
|
|
32065
|
-
writable: true,
|
|
32066
|
-
value: 'Power'
|
|
32067
|
-
});
|
|
32068
|
-
}
|
|
32069
|
-
evaluate() {
|
|
32070
|
-
return this.base.evaluate() ** this.exponent.evaluate();
|
|
32071
|
-
}
|
|
32072
|
-
toString() {
|
|
32073
|
-
const baseStr = this.base.toString();
|
|
32074
|
-
const expVal = this.exponent.evaluate();
|
|
32075
|
-
// Use superscript for powers 2 and 3
|
|
32076
|
-
if (expVal === 2)
|
|
32077
|
-
return `${baseStr}²`;
|
|
32078
|
-
if (expVal === 3)
|
|
32079
|
-
return `${baseStr}³`;
|
|
32080
|
-
return CONFIG.spaceSeparation ? `${baseStr} ^ ${this.exponent.toString()}` : `${baseStr}^${this.exponent.toString()}`;
|
|
32081
|
-
}
|
|
32082
|
-
simplify() {
|
|
32083
|
-
const base = this.base.simplify();
|
|
32084
|
-
const exponent = this.exponent.simplify();
|
|
32085
|
-
// Anything to the power of 0 is 1
|
|
32086
|
-
if (isNumberNode(exponent) && exponent.evaluate() === 0) {
|
|
32087
|
-
return new NumberNode(1);
|
|
32088
|
-
}
|
|
32089
|
-
// Anything to the power of 1 is itself
|
|
32090
|
-
if (isNumberNode(exponent) && exponent.evaluate() === 1) {
|
|
32091
|
-
return base;
|
|
32092
|
-
}
|
|
32093
|
-
// 0 to any positive power is 0
|
|
32094
|
-
if (isNumberNode(base) && base.evaluate() === 0
|
|
32095
|
-
&& isNumberNode(exponent) && exponent.evaluate() > 0) {
|
|
32096
|
-
return new NumberNode(0);
|
|
32097
|
-
}
|
|
32098
|
-
// 1 to any power is 1
|
|
32099
|
-
if (isNumberNode(base) && base.evaluate() === 1) {
|
|
32100
|
-
return new NumberNode(1);
|
|
32101
|
-
}
|
|
32102
|
-
// If both are numbers, compute the power
|
|
32103
|
-
if (isNumberNode(base) && isNumberNode(exponent)) {
|
|
32104
|
-
return new NumberNode(base.evaluate() ** exponent.evaluate());
|
|
32105
|
-
}
|
|
32106
|
-
return new PowerNode(base, exponent);
|
|
32107
|
-
}
|
|
32108
|
-
equals(other) {
|
|
32109
|
-
if (!(isPowerNode(other)))
|
|
32110
|
-
return false;
|
|
32111
|
-
const otherPower = other;
|
|
32112
|
-
return this.base.equals(otherPower.base)
|
|
32113
|
-
&& this.exponent.equals(otherPower.exponent);
|
|
32114
|
-
}
|
|
32115
|
-
getBase() {
|
|
32116
|
-
return this.base;
|
|
32117
|
-
}
|
|
32118
|
-
getExponent() {
|
|
32119
|
-
return this.exponent;
|
|
32120
|
-
}
|
|
32121
|
-
}
|
|
32122
|
-
/**
|
|
32123
|
-
* Node for unary operations (-, sqrt, cbrt, etc.)
|
|
32124
|
-
*/
|
|
32125
|
-
class UnaryOpNode extends ExprNode {
|
|
32126
|
-
constructor(op, operand) {
|
|
32127
|
-
super();
|
|
32128
|
-
Object.defineProperty(this, "op", {
|
|
32129
|
-
enumerable: true,
|
|
32130
|
-
configurable: true,
|
|
32131
|
-
writable: true,
|
|
32132
|
-
value: op
|
|
32133
|
-
});
|
|
32134
|
-
Object.defineProperty(this, "operand", {
|
|
32135
|
-
enumerable: true,
|
|
32136
|
-
configurable: true,
|
|
32137
|
-
writable: true,
|
|
32138
|
-
value: operand
|
|
32139
|
-
});
|
|
32140
|
-
Object.defineProperty(this, "type", {
|
|
32141
|
-
enumerable: true,
|
|
32142
|
-
configurable: true,
|
|
32143
|
-
writable: true,
|
|
32144
|
-
value: 'UnaryOp'
|
|
32145
|
-
});
|
|
32146
|
-
}
|
|
32147
|
-
evaluate() {
|
|
32148
|
-
const val = this.operand.evaluate();
|
|
32149
|
-
switch (this.op) {
|
|
32150
|
-
case '-': return -val;
|
|
32151
|
-
case 'sqrt': return Math.sqrt(val);
|
|
32152
|
-
case 'cbrt': return Math.cbrt(val);
|
|
32153
|
-
}
|
|
32154
|
-
}
|
|
32155
|
-
toString() {
|
|
32156
|
-
const operandStr = this.operand.toString();
|
|
32157
|
-
if (operandStr === '0') {
|
|
32158
|
-
return '0';
|
|
32159
|
-
}
|
|
32160
|
-
switch (this.op) {
|
|
32161
|
-
case '-': return CONFIG.spaceSeparation && (!isNumberNode(this.operand) || operandStr.includes('/')) ? `-(${operandStr})` : `-${operandStr}`;
|
|
32162
|
-
case 'sqrt': return CONFIG.spaceSeparation ? `√(${operandStr})` : `√${operandStr}`;
|
|
32163
|
-
case 'cbrt': return CONFIG.spaceSeparation ? `∛(${operandStr})` : `∛${operandStr}`;
|
|
32164
|
-
}
|
|
32165
|
-
}
|
|
32166
|
-
simplify() {
|
|
32167
|
-
const operand = this.operand.simplify();
|
|
32168
|
-
// Double negation: --a = a
|
|
32169
|
-
if (this.op === '-' && isUnaryOpNode(operand)
|
|
32170
|
-
&& operand.getOp() === '-') {
|
|
32171
|
-
return operand.getOperand();
|
|
32172
|
-
}
|
|
32173
|
-
// Simplify sqrt of perfect squares
|
|
32174
|
-
if (this.op === 'sqrt' && isNumberNode(operand)) {
|
|
32175
|
-
const val = operand.evaluate();
|
|
32176
|
-
const sqrtVal = Math.sqrt(val);
|
|
32177
|
-
if (Number.isInteger(sqrtVal)) {
|
|
32178
|
-
return new NumberNode(sqrtVal);
|
|
32179
|
-
}
|
|
32180
|
-
}
|
|
32181
|
-
// Simplify cbrt of perfect cubes
|
|
32182
|
-
if (this.op === 'cbrt' && isNumberNode(operand)) {
|
|
32183
|
-
const val = operand.evaluate();
|
|
32184
|
-
const cbrtVal = Math.cbrt(val);
|
|
32185
|
-
if (Number.isInteger(cbrtVal)) {
|
|
32186
|
-
return new NumberNode(cbrtVal);
|
|
32187
|
-
}
|
|
32188
|
-
}
|
|
32189
|
-
return new UnaryOpNode(this.op, operand);
|
|
32190
|
-
}
|
|
32191
|
-
getOp() {
|
|
32192
|
-
return this.op;
|
|
32193
|
-
}
|
|
32194
|
-
getOperand() {
|
|
32195
|
-
return this.operand;
|
|
32196
|
-
}
|
|
32197
|
-
equals(other) {
|
|
32198
|
-
if (!(isUnaryOpNode(other)))
|
|
32199
|
-
return false;
|
|
32200
|
-
const otherOp = other;
|
|
32201
|
-
return this.op === otherOp.getOp()
|
|
32202
|
-
&& this.operand.equals(otherOp.getOperand());
|
|
32203
|
-
}
|
|
32204
|
-
}
|
|
32205
|
-
|
|
32206
|
-
/**
|
|
32207
|
-
* Node for mathematical constants (π, e, etc.)
|
|
32208
|
-
*/
|
|
32209
|
-
class ConstantNode extends ExprNode {
|
|
32210
|
-
constructor(symbol, value) {
|
|
32211
|
-
super();
|
|
32212
|
-
Object.defineProperty(this, "symbol", {
|
|
32213
|
-
enumerable: true,
|
|
32214
|
-
configurable: true,
|
|
32215
|
-
writable: true,
|
|
32216
|
-
value: symbol
|
|
32217
|
-
});
|
|
32218
|
-
Object.defineProperty(this, "value", {
|
|
32219
|
-
enumerable: true,
|
|
32220
|
-
configurable: true,
|
|
32221
|
-
writable: true,
|
|
32222
|
-
value: value
|
|
32223
|
-
});
|
|
32224
|
-
Object.defineProperty(this, "type", {
|
|
32225
|
-
enumerable: true,
|
|
32226
|
-
configurable: true,
|
|
32227
|
-
writable: true,
|
|
32228
|
-
value: 'Constant'
|
|
32229
|
-
});
|
|
32230
|
-
}
|
|
32231
|
-
evaluate() {
|
|
32232
|
-
return this.value;
|
|
32233
|
-
}
|
|
32234
|
-
toString() {
|
|
32235
|
-
return this.symbol;
|
|
32236
|
-
}
|
|
32237
|
-
simplify() {
|
|
32238
|
-
return this; // Constants are already in simplest form
|
|
32239
|
-
}
|
|
32240
|
-
equals(other) {
|
|
32241
|
-
if (!(isConstantNode(other)))
|
|
32242
|
-
return false;
|
|
32243
|
-
return this.symbol === other.symbol;
|
|
32244
|
-
}
|
|
32245
|
-
getSymbol() {
|
|
32246
|
-
return this.symbol;
|
|
32247
|
-
}
|
|
32248
|
-
}
|
|
32249
|
-
|
|
32250
|
-
/**
|
|
32251
|
-
* Converts a number to its continued fraction representation
|
|
32252
|
-
* @param x The number to convert
|
|
32253
|
-
* @param maxTerms Maximum number of terms to compute
|
|
32254
|
-
* @returns Array of continued fraction terms [a0, a1, a2, ...]
|
|
32255
|
-
*/
|
|
32256
|
-
function toContinuedFraction(x, maxTerms = 20) {
|
|
32257
|
-
const terms = [];
|
|
32258
|
-
for (let i = 0; i < maxTerms; i++) {
|
|
32259
|
-
const a = Math.floor(x);
|
|
32260
|
-
terms.push(a);
|
|
32261
|
-
// If we've reached a very small remainder, we're done
|
|
32262
|
-
if (Math.abs(x - a) < CONFIG.epsilon) {
|
|
32263
|
-
break;
|
|
32264
|
-
}
|
|
32265
|
-
// Prevent division by zero and avoid floating point issues
|
|
32266
|
-
const remainder = x - a;
|
|
32267
|
-
if (Math.abs(remainder) < CONFIG.epsilon) {
|
|
32268
|
-
break;
|
|
32269
|
-
}
|
|
32270
|
-
x = 1 / remainder;
|
|
32271
|
-
}
|
|
32272
|
-
return terms;
|
|
32273
|
-
}
|
|
32274
|
-
/**
|
|
32275
|
-
* Converts continued fraction terms to a fraction [numerator, denominator]
|
|
32276
|
-
* @param terms Array of continued fraction terms
|
|
32277
|
-
* @returns [numerator, denominator]
|
|
32278
|
-
*/
|
|
32279
|
-
function fromContinuedFraction(terms) {
|
|
32280
|
-
if (terms.length === 0)
|
|
32281
|
-
return [0, 1];
|
|
32282
|
-
if (terms.length === 1)
|
|
32283
|
-
return [terms[0], 1];
|
|
32284
|
-
// Start with the last term
|
|
32285
|
-
let numerator = terms[terms.length - 1];
|
|
32286
|
-
let denominator = 1;
|
|
32287
|
-
// Work backwards through the terms
|
|
32288
|
-
for (let i = terms.length - 2; i >= 0; i--) {
|
|
32289
|
-
// For each term, compute the new fraction
|
|
32290
|
-
// a_i + 1/previous
|
|
32291
|
-
const newNumerator = terms[i] * numerator + denominator;
|
|
32292
|
-
const newDenominator = numerator;
|
|
32293
|
-
// Update for next iteration
|
|
32294
|
-
numerator = newNumerator;
|
|
32295
|
-
denominator = newDenominator;
|
|
32296
|
-
}
|
|
32297
|
-
return [numerator, denominator];
|
|
32298
|
-
}
|
|
32299
|
-
/**
|
|
32300
|
-
* Generates all convergents (approximations) from continued fraction terms
|
|
32301
|
-
* @param terms Array of continued fraction terms
|
|
32302
|
-
* @returns Array of [numerator, denominator] pairs
|
|
32303
|
-
*/
|
|
32304
|
-
function getConvergents(terms) {
|
|
32305
|
-
const convergents = [];
|
|
32306
|
-
for (let i = 1; i <= terms.length; i++) {
|
|
32307
|
-
convergents.push(fromContinuedFraction(terms.slice(0, i)));
|
|
32308
|
-
}
|
|
32309
|
-
return convergents;
|
|
32310
|
-
}
|
|
32311
|
-
/**
|
|
32312
|
-
* Tries to identify if a number is a simple quadratic irrational
|
|
32313
|
-
* @param x The number to check
|
|
32314
|
-
* @returns The identified form or null if not recognized
|
|
32315
|
-
*/
|
|
32316
|
-
function identifyQuadraticIrrational(x) {
|
|
32317
|
-
// Direct checks for common values
|
|
32318
|
-
// Check for square roots of integers
|
|
32319
|
-
for (let n = 2; n <= 20; n++) {
|
|
32320
|
-
if (Math.abs(x - Math.sqrt(n)) < CONFIG.epsilon) {
|
|
32321
|
-
return `√${n}`;
|
|
32322
|
-
}
|
|
32323
|
-
}
|
|
32324
|
-
// Check for golden ratio and its conjugate
|
|
32325
|
-
if (Math.abs(x - (1 + Math.sqrt(5)) / 2) < CONFIG.epsilon) {
|
|
32326
|
-
return "φ"; // Already defined as a constant
|
|
32327
|
-
}
|
|
32328
|
-
if (Math.abs(x - (Math.sqrt(5) - 1) / 2) < CONFIG.epsilon) {
|
|
32329
|
-
return "(√5-1)/2";
|
|
32330
|
-
}
|
|
32331
|
-
// Check for expressions of form (√n ± m)/k where n, m, k are small integers
|
|
32332
|
-
for (let n = 2; n <= 30; n++) {
|
|
32333
|
-
const sqrtN = Math.sqrt(n);
|
|
32334
|
-
for (let m = 1; m <= 10; m++) {
|
|
32335
|
-
for (let k = 2; k <= 10; k++) {
|
|
32336
|
-
// Check (√n + m)/k
|
|
32337
|
-
if (Math.abs(x - (sqrtN + m) / k) < CONFIG.epsilon) {
|
|
32338
|
-
if (m === 1 && k === 2) {
|
|
32339
|
-
return `(√${n}+1)/2`;
|
|
32340
|
-
}
|
|
32341
|
-
else {
|
|
32342
|
-
return `(√${n}+${m})/${k}`;
|
|
32343
|
-
}
|
|
32344
|
-
}
|
|
32345
|
-
// Check (√n - m)/k
|
|
32346
|
-
if (Math.abs(x - (sqrtN - m) / k) < CONFIG.epsilon) {
|
|
32347
|
-
if (m === 1 && k === 2) {
|
|
32348
|
-
return `(√${n}-1)/2`;
|
|
32349
|
-
}
|
|
32350
|
-
else {
|
|
32351
|
-
return `(√${n}-${m})/${k}`;
|
|
32352
|
-
}
|
|
32353
|
-
}
|
|
32354
|
-
}
|
|
32355
|
-
}
|
|
32356
|
-
}
|
|
32357
|
-
// Get continued fraction for pattern analysis
|
|
32358
|
-
const terms = toContinuedFraction(x, 30);
|
|
32359
|
-
// Analyze the continued fraction for periodic patterns
|
|
32360
|
-
const period = detectPeriod(terms);
|
|
32361
|
-
if (period) {
|
|
32362
|
-
// For purely periodic CFs (√D): [a0; a1, a2, ..., aN, a1, a2, ...]
|
|
32363
|
-
// where a0 = floor(√D) and the sequence after a0 is periodic
|
|
32364
|
-
// For √2: [1; 2, 2, 2, ...]
|
|
32365
|
-
if (period.length === 1 && period[0] === 2 && terms[0] === 1) {
|
|
32366
|
-
return "√2";
|
|
32367
|
-
}
|
|
32368
|
-
// For √3: [1; 1, 2, 1, 2, ...]
|
|
32369
|
-
if (period.length === 2 && period[0] === 1 && period[1] === 2 && terms[0] === 1) {
|
|
32370
|
-
return "√3";
|
|
32371
|
-
}
|
|
32372
|
-
// For √5: [2; 4, 4, 4, ...]
|
|
32373
|
-
if (period.length === 1 && period[0] === 4 && terms[0] === 2) {
|
|
32374
|
-
return "√5";
|
|
32375
|
-
}
|
|
32376
|
-
// For √6: [2; 2, 4, 2, 4, ...]
|
|
32377
|
-
if (period.length === 2 && period[0] === 2 && period[1] === 4 && terms[0] === 2) {
|
|
32378
|
-
return "√6";
|
|
32379
|
-
}
|
|
32380
|
-
// For √7: [2; 1, 1, 1, 4, 1, 1, 1, 4, ...]
|
|
32381
|
-
if (period.length === 4 &&
|
|
32382
|
-
period[0] === 1 && period[1] === 1 &&
|
|
32383
|
-
period[2] === 1 && period[3] === 4 &&
|
|
32384
|
-
terms[0] === 2) {
|
|
32385
|
-
return "√7";
|
|
32386
|
-
}
|
|
32387
|
-
// For √8: [2; 1, 4, 1, 4, ...]
|
|
32388
|
-
if (period.length === 2 && period[0] === 1 && period[1] === 4 && terms[0] === 2) {
|
|
32389
|
-
return "√8";
|
|
32390
|
-
}
|
|
32391
|
-
// Try to reconstruct the quadratic form from the pattern
|
|
32392
|
-
// (Complex algorithm that would determine D from periodic CF)
|
|
32393
|
-
const D = reconstructD(terms[0], period);
|
|
32394
|
-
if (D > 0 && Math.abs(x - Math.sqrt(D)) < CONFIG.epsilon) {
|
|
32395
|
-
return `√${D}`;
|
|
32396
|
-
}
|
|
32397
|
-
}
|
|
32398
|
-
return null; // Could not identify
|
|
32399
|
-
}
|
|
32400
|
-
// Helper function to detect periodic patterns in continued fractions
|
|
32401
|
-
function detectPeriod(terms) {
|
|
32402
|
-
// Need at least a few terms to detect patterns
|
|
32403
|
-
if (terms.length < 6)
|
|
32404
|
-
return null;
|
|
32405
|
-
// Check for single repeating digit (like √2 = [1; 2, 2, 2, ...])
|
|
32406
|
-
const allSameAfterFirst = terms.slice(1).every(t => t === terms[1]);
|
|
32407
|
-
if (allSameAfterFirst) {
|
|
32408
|
-
return [terms[1]];
|
|
32409
|
-
}
|
|
32410
|
-
// For period of length 2
|
|
32411
|
-
const periodLength2 = terms.slice(1, 3).every((val, idx) => {
|
|
32412
|
-
for (let i = 1; i < Math.floor((terms.length - 1) / 2); i++) {
|
|
32413
|
-
if (terms[1 + idx + 2 * i] !== val)
|
|
32414
|
-
return false;
|
|
32415
|
-
}
|
|
32416
|
-
return true;
|
|
32417
|
-
});
|
|
32418
|
-
if (periodLength2) {
|
|
32419
|
-
return terms.slice(1, 3);
|
|
32420
|
-
}
|
|
32421
|
-
// For period of length 4
|
|
32422
|
-
if (terms.length >= 9) {
|
|
32423
|
-
const periodLength4 = terms.slice(1, 5).every((val, idx) => {
|
|
32424
|
-
return terms[1 + idx + 4] === val && (terms.length >= 13 ? terms[1 + idx + 8] === val : true);
|
|
32425
|
-
});
|
|
32426
|
-
if (periodLength4) {
|
|
32427
|
-
return terms.slice(1, 5);
|
|
32428
|
-
}
|
|
32429
|
-
}
|
|
32430
|
-
// Could add more pattern detection logic here
|
|
32431
|
-
return null;
|
|
32432
|
-
}
|
|
32433
|
-
// Helper function to reconstruct D from continued fraction pattern
|
|
32434
|
-
function reconstructD(a0, period) {
|
|
32435
|
-
// Simple cases
|
|
32436
|
-
if (period.length === 1 && period[0] === 2)
|
|
32437
|
-
return 2;
|
|
32438
|
-
if (period.length === 2 && period[0] === 1 && period[1] === 2)
|
|
32439
|
-
return 3;
|
|
32440
|
-
if (period.length === 1 && period[0] === 4)
|
|
32441
|
-
return 5;
|
|
32442
|
-
if (period.length === 2 && period[0] === 2 && period[1] === 4)
|
|
32443
|
-
return 6;
|
|
32444
|
-
if (period.length === 4 &&
|
|
32445
|
-
period[0] === 1 && period[1] === 1 &&
|
|
32446
|
-
period[2] === 1 && period[3] === 4)
|
|
32447
|
-
return 7;
|
|
32448
|
-
if (period.length === 2 && period[0] === 1 && period[1] === 4)
|
|
32449
|
-
return 8;
|
|
32450
|
-
// For purely periodic CFs, D = a0² + P where P depends on the period
|
|
32451
|
-
// This is a simplified heuristic approach
|
|
32452
|
-
if (period.length === 1) {
|
|
32453
|
-
// If period has form [n], then D ≈ a0² + 1/n
|
|
32454
|
-
return Math.round(a0 * a0 + 1 / period[0]);
|
|
32455
|
-
}
|
|
32456
|
-
// Generalized approach for reconstructing D would be more complex
|
|
32457
|
-
// and would involve solving the Pell equation
|
|
32458
|
-
return -1; // Couldn't determine D
|
|
32459
|
-
}
|
|
32460
|
-
|
|
32461
|
-
/**
|
|
32462
|
-
* Parser to convert numeric values to expression trees
|
|
32463
|
-
*/
|
|
32464
|
-
class ExpressionParser {
|
|
32465
|
-
/**
|
|
32466
|
-
* Main parsing function that converts a number to an expression tree
|
|
32467
|
-
*/
|
|
32468
|
-
parseNumber(num, depth = 0) {
|
|
32469
|
-
const MAX_DEPTH = 3;
|
|
32470
|
-
// Handle zero
|
|
32471
|
-
if (Math.abs(num) < CONFIG.epsilon) {
|
|
32472
|
-
return new NumberNode(0);
|
|
32473
|
-
}
|
|
32474
|
-
// Handle negative numbers
|
|
32475
|
-
if (num < 0) {
|
|
32476
|
-
return new UnaryOpNode('-', this.parseNumber(-num, depth));
|
|
32477
|
-
}
|
|
32478
|
-
// Handle integers
|
|
32479
|
-
if (Math.abs(num - Math.round(num)) < CONFIG.epsilon) {
|
|
32480
|
-
return new NumberNode(Math.round(num));
|
|
32481
|
-
}
|
|
32482
|
-
const cfTerms = toContinuedFraction(num, 20);
|
|
32483
|
-
const convergents = getConvergents(cfTerms);
|
|
32484
|
-
for (const [numerator, denominator] of convergents) {
|
|
32485
|
-
// Only consider fractions with reasonably-sized components
|
|
32486
|
-
if (denominator <= 1000 && Math.abs(numerator) <= 10000) {
|
|
32487
|
-
// Verify the approximation is within our epsilon
|
|
32488
|
-
if (Math.abs(num - numerator / denominator) < CONFIG.epsilon) {
|
|
32489
|
-
const gcd = this.findGCD(Math.abs(numerator), denominator);
|
|
32490
|
-
return new BinaryOpNode('/', new NumberNode(numerator / gcd), new NumberNode(denominator / gcd));
|
|
32491
|
-
}
|
|
32492
|
-
}
|
|
32493
|
-
}
|
|
32494
|
-
// Try direct matches to constants
|
|
32495
|
-
for (const constant of CONSTANTS) {
|
|
32496
|
-
if (Math.abs(num - constant.value) < CONFIG.epsilon) {
|
|
32497
|
-
return new ConstantNode(constant.getSymbol(), constant.value);
|
|
32498
|
-
}
|
|
32499
|
-
}
|
|
32500
|
-
// Try direct matches to trig values
|
|
32501
|
-
for (const trigValue of TRIG_VALUES) {
|
|
32502
|
-
if (Math.abs(num - trigValue.value) < CONFIG.epsilon) {
|
|
32503
|
-
// If it has an exact form, parse that instead
|
|
32504
|
-
if (trigValue.getExactForm) {
|
|
32505
|
-
return this.parseExactForm(trigValue.getExactForm());
|
|
32506
|
-
}
|
|
32507
|
-
return new ConstantNode(trigValue.getSymbol(), trigValue.value);
|
|
32508
|
-
}
|
|
32509
|
-
}
|
|
32510
|
-
const quadraticForm = identifyQuadraticIrrational(num);
|
|
32511
|
-
if (quadraticForm) {
|
|
32512
|
-
// If we identified something like "√7" or "(√13-3)/2"
|
|
32513
|
-
return this.parseExactForm(quadraticForm);
|
|
32514
|
-
}
|
|
32515
|
-
// Direct check for square roots before other decompositions
|
|
32516
|
-
// This ensures square roots of integers are handled directly
|
|
32517
|
-
for (let i = 2; i <= 100; i++) {
|
|
32518
|
-
if (Math.abs(num - Math.sqrt(i)) < CONFIG.epsilon) {
|
|
32519
|
-
// Use our enhanced check to determine if we should keep direct form
|
|
32520
|
-
if (shouldPreserveDirectRadical(i)) {
|
|
32521
|
-
return new RootNode(new NumberNode(i));
|
|
32522
|
-
}
|
|
32523
|
-
// Only factorize if appropriate
|
|
32524
|
-
const factorized = factorizeRadicand(i);
|
|
32525
|
-
if (factorized.coefficient > 1) {
|
|
32526
|
-
return new BinaryOpNode('*', new NumberNode(factorized.coefficient), new RootNode(new NumberNode(factorized.radicand)));
|
|
32527
|
-
}
|
|
32528
|
-
// Default to direct representation
|
|
32529
|
-
return new RootNode(new NumberNode(i));
|
|
32530
|
-
}
|
|
32531
|
-
}
|
|
32532
|
-
// Check for powers
|
|
32533
|
-
for (const constant of [...CONSTANTS, ...TRIG_VALUES]) {
|
|
32534
|
-
for (let power = 2; power <= 3; power++) {
|
|
32535
|
-
if (Math.abs(num - constant.value ** power) < CONFIG.epsilon) {
|
|
32536
|
-
const baseNode = constant.getExactForm
|
|
32537
|
-
? this.parseExactForm(constant.getExactForm())
|
|
32538
|
-
: new ConstantNode(constant.getSymbol(), constant.value);
|
|
32539
|
-
return new PowerNode(baseNode, new NumberNode(power));
|
|
32540
|
-
}
|
|
32541
|
-
}
|
|
32542
|
-
}
|
|
32543
|
-
// Recursive decomposition with extra care for square roots
|
|
32544
|
-
if (depth < MAX_DEPTH) {
|
|
32545
|
-
// Check for π/n pattern
|
|
32546
|
-
for (const constant of CONSTANTS) {
|
|
32547
|
-
for (let denominator = 2; denominator <= 12; denominator++) {
|
|
32548
|
-
if (Math.abs(num - constant.value / denominator) < CONFIG.epsilon) {
|
|
32549
|
-
return new BinaryOpNode('/', constant.getExactForm
|
|
32550
|
-
? this.parseExactForm(constant.getExactForm())
|
|
32551
|
-
: new ConstantNode(constant.getSymbol(), constant.value), new NumberNode(denominator));
|
|
32552
|
-
}
|
|
32553
|
-
}
|
|
32554
|
-
}
|
|
32555
|
-
// Check for products with more careful handling of square roots
|
|
32556
|
-
for (const constant of [...CONSTANTS, ...TRIG_VALUES]) {
|
|
32557
|
-
if (Math.abs(constant.value) > CONFIG.epsilon) {
|
|
32558
|
-
const multiplier = num / constant.value;
|
|
32559
|
-
// Skip square root decomposition attempts for prime square roots
|
|
32560
|
-
if (constant.getSymbol() && constant.getSymbol().startsWith('√')
|
|
32561
|
-
&& isPrime(Number.parseInt(constant.getSymbol(true).substring(1)))) {
|
|
32562
|
-
continue;
|
|
32563
|
-
}
|
|
32564
|
-
// Skip common fractions that might be misidentified as complex expressions
|
|
32565
|
-
if ((Math.abs(multiplier - 0.75) < CONFIG.epsilon)
|
|
32566
|
-
|| (Math.abs(multiplier - 0.5) < CONFIG.epsilon)
|
|
32567
|
-
|| (Math.abs(multiplier - 0.25) < CONFIG.epsilon)
|
|
32568
|
-
|| (Math.abs(multiplier - 0.3333333333333333) < CONFIG.epsilon)
|
|
32569
|
-
|| (Math.abs(multiplier - 0.6666666666666666) < CONFIG.epsilon)) {
|
|
32570
|
-
continue;
|
|
32571
|
-
}
|
|
32572
|
-
if (this.isNiceValue(multiplier)) {
|
|
32573
|
-
const multiplierNode = this.parseNumber(multiplier, depth + 1);
|
|
32574
|
-
const valueNode = constant.getExactForm
|
|
32575
|
-
? this.parseExactForm(constant.getExactForm())
|
|
32576
|
-
: new ConstantNode(constant.getSymbol(), constant.value);
|
|
32577
|
-
// If multiplier is 1, just return the constant
|
|
32578
|
-
if (isNumberNode(multiplierNode)
|
|
32579
|
-
&& Math.abs(multiplierNode.evaluate() - 1) < CONFIG.epsilon) {
|
|
32580
|
-
return valueNode;
|
|
32581
|
-
}
|
|
32582
|
-
// Create the product node with canonical ordering
|
|
32583
|
-
let productNode;
|
|
32584
|
-
// Define a priority order for constants
|
|
32585
|
-
// Lower number = higher priority (should appear first)
|
|
32586
|
-
const getConstantPriority = (node) => {
|
|
32587
|
-
if (!(isConstantNode(node)))
|
|
32588
|
-
return 100;
|
|
32589
|
-
const symbol = node.getSymbol();
|
|
32590
|
-
if (symbol === 'e')
|
|
32591
|
-
return 1;
|
|
32592
|
-
if (symbol === 'π')
|
|
32593
|
-
return 2;
|
|
32594
|
-
if (symbol === 'φ')
|
|
32595
|
-
return 3;
|
|
32596
|
-
if (symbol.startsWith('√'))
|
|
32597
|
-
return 10;
|
|
32598
|
-
return 50; // Other constants
|
|
32599
|
-
};
|
|
32600
|
-
// Apply the priority ordering
|
|
32601
|
-
if (isConstantNode(multiplierNode) && isConstantNode(valueNode)) {
|
|
32602
|
-
// When multiplying two constants, order them by priority
|
|
32603
|
-
const multiplierPriority = getConstantPriority(multiplierNode);
|
|
32604
|
-
const valuePriority = getConstantPriority(valueNode);
|
|
32605
|
-
if (multiplierPriority <= valuePriority) {
|
|
32606
|
-
productNode = new BinaryOpNode('*', multiplierNode, valueNode);
|
|
32607
|
-
}
|
|
32608
|
-
else {
|
|
32609
|
-
productNode = new BinaryOpNode('*', valueNode, multiplierNode);
|
|
32610
|
-
}
|
|
32611
|
-
}
|
|
32612
|
-
else if (isNumberNode(multiplierNode)) {
|
|
32613
|
-
// Number multiplier always goes first
|
|
32614
|
-
productNode = new BinaryOpNode('*', multiplierNode, valueNode);
|
|
32615
|
-
}
|
|
32616
|
-
else if (getConstantPriority(valueNode) < 50) {
|
|
32617
|
-
// Special constants (e, π, φ) go before other expressions
|
|
32618
|
-
productNode = new BinaryOpNode('*', valueNode, multiplierNode);
|
|
32619
|
-
}
|
|
32620
|
-
else {
|
|
32621
|
-
// Default ordering
|
|
32622
|
-
productNode = new BinaryOpNode('*', multiplierNode, valueNode);
|
|
32623
|
-
}
|
|
32624
|
-
// Check if this product is part of a sum
|
|
32625
|
-
// Try to see if the product plus a nice value equals our number
|
|
32626
|
-
for (let addend = 1; addend <= 10; addend++) {
|
|
32627
|
-
const productValue = productNode.evaluate();
|
|
32628
|
-
const sum = productValue + addend;
|
|
32629
|
-
if (Math.abs(num - sum) < CONFIG.epsilon) {
|
|
32630
|
-
return new BinaryOpNode('+', productNode, new NumberNode(addend));
|
|
32631
|
-
}
|
|
32632
|
-
}
|
|
32633
|
-
return productNode;
|
|
32634
|
-
}
|
|
32635
|
-
}
|
|
32636
|
-
}
|
|
32637
|
-
// Check for sums and differences with base constants
|
|
32638
|
-
for (const constant of [...CONSTANTS, ...TRIG_VALUES]) {
|
|
32639
|
-
const remainder = num - constant.value;
|
|
32640
|
-
if (this.isNiceValue(remainder)) {
|
|
32641
|
-
const remainderNode = this.parseNumber(remainder, depth + 1);
|
|
32642
|
-
const valueNode = constant.getExactForm
|
|
32643
|
-
? this.parseExactForm(constant.getExactForm())
|
|
32644
|
-
: new ConstantNode(constant.getSymbol(), constant.value);
|
|
32645
|
-
// If remainder is 0, just return the constant
|
|
32646
|
-
if (isNumberNode(remainderNode)
|
|
32647
|
-
&& Math.abs(remainderNode.evaluate()) < CONFIG.epsilon) {
|
|
32648
|
-
return valueNode;
|
|
32649
|
-
}
|
|
32650
|
-
// If remainder is negative, use subtraction
|
|
32651
|
-
if (remainder < 0) {
|
|
32652
|
-
return new BinaryOpNode('-', valueNode, this.parseNumber(-remainder, depth + 1));
|
|
32653
|
-
}
|
|
32654
|
-
return new BinaryOpNode('+', valueNode, remainderNode);
|
|
32655
|
-
}
|
|
32656
|
-
}
|
|
32657
|
-
// Check for sums and differences with multiples of constants
|
|
32658
|
-
for (const constant of [...CONSTANTS, ...TRIG_VALUES]) {
|
|
32659
|
-
for (let multiplier = 2; multiplier <= 5; multiplier++) {
|
|
32660
|
-
const multipleValue = multiplier * constant.value;
|
|
32661
|
-
const remainder = num - multipleValue;
|
|
32662
|
-
if (this.isNiceValue(remainder)) {
|
|
32663
|
-
// Create the multiple constant node
|
|
32664
|
-
const constantNode = constant.getExactForm
|
|
32665
|
-
? this.parseExactForm(constant.getExactForm())
|
|
32666
|
-
: new ConstantNode(constant.getSymbol(), constant.value);
|
|
32667
|
-
const multipleNode = new BinaryOpNode('*', new NumberNode(multiplier), constantNode);
|
|
32668
|
-
// Create the remainder node
|
|
32669
|
-
const remainderNode = this.parseNumber(remainder, depth + 1);
|
|
32670
|
-
// If remainder is 0, just return the multiple
|
|
32671
|
-
if (isNumberNode(remainderNode)
|
|
32672
|
-
&& Math.abs(remainderNode.evaluate()) < CONFIG.epsilon) {
|
|
32673
|
-
return multipleNode;
|
|
32674
|
-
}
|
|
32675
|
-
// If remainder is negative, use subtraction
|
|
32676
|
-
if (remainder < 0) {
|
|
32677
|
-
return new BinaryOpNode('-', multipleNode, this.parseNumber(-remainder, depth + 1));
|
|
32678
|
-
}
|
|
32679
|
-
return new BinaryOpNode('+', multipleNode, remainderNode);
|
|
32680
|
-
}
|
|
32681
|
-
}
|
|
32682
|
-
}
|
|
32683
|
-
// Check for cube roots of integers
|
|
32684
|
-
for (let i = 2; i <= 100; i++) {
|
|
32685
|
-
if (Math.abs(num - Math.cbrt(i)) < CONFIG.epsilon) {
|
|
32686
|
-
return new UnaryOpNode('cbrt', new NumberNode(i));
|
|
32687
|
-
}
|
|
32688
|
-
}
|
|
32689
|
-
}
|
|
32690
|
-
// Final direct check for square roots before giving up
|
|
32691
|
-
// This ensures we catch any square roots we might have missed
|
|
32692
|
-
const possibleRadicand = Math.round(num * num);
|
|
32693
|
-
if (Math.abs(num - Math.sqrt(possibleRadicand)) < CONFIG.epsilon) {
|
|
32694
|
-
return new RootNode(new NumberNode(possibleRadicand));
|
|
32695
|
-
}
|
|
32696
|
-
// Fallback to decimal representation
|
|
32697
|
-
return new NumberNode(num);
|
|
32698
|
-
}
|
|
32699
|
-
/**
|
|
32700
|
-
* Helper method to parse exact forms like "√2/2"
|
|
32701
|
-
*/
|
|
32702
|
-
parseExactForm(exactForm) {
|
|
32703
|
-
// Handle fractions
|
|
32704
|
-
if (exactForm.includes('/')) {
|
|
32705
|
-
const [numerator, denominator] = exactForm.split('/');
|
|
32706
|
-
return new BinaryOpNode('/', this.parseExactForm(numerator), this.parseExactForm(denominator));
|
|
32707
|
-
}
|
|
32708
|
-
// Handle square roots
|
|
32709
|
-
if (exactForm.startsWith('√')) {
|
|
32710
|
-
const argument = exactForm.substring(1);
|
|
32711
|
-
return new RootNode(this.parseExactForm(argument));
|
|
32712
|
-
}
|
|
32713
|
-
// Handle simple numbers
|
|
32714
|
-
if (/^\d+$/.test(exactForm)) {
|
|
32715
|
-
return new NumberNode(Number.parseInt(exactForm));
|
|
32716
|
-
}
|
|
32717
|
-
// Handle known constants
|
|
32718
|
-
for (const constant of CONSTANTS) {
|
|
32719
|
-
if (exactForm === constant.getSymbol()) {
|
|
32720
|
-
return new ConstantNode(constant.getSymbol(), constant.value);
|
|
32721
|
-
}
|
|
32722
|
-
}
|
|
32723
|
-
// If we don't recognize it, treat it as a symbol
|
|
32724
|
-
return new ConstantNode(exactForm, Number.NaN);
|
|
32725
|
-
}
|
|
32726
|
-
/**
|
|
32727
|
-
* Helper to determine if a value is "nice"
|
|
32728
|
-
*/
|
|
32729
|
-
isNiceValue(num) {
|
|
32730
|
-
// Check if it's close to an integer
|
|
32731
|
-
if (Math.abs(num - Math.round(num)) < CONFIG.epsilon) {
|
|
32732
|
-
return true;
|
|
32733
|
-
}
|
|
32734
|
-
const cfTerms = toContinuedFraction(num, 10);
|
|
32735
|
-
const convergents = getConvergents(cfTerms);
|
|
32736
|
-
// Check if any convergent is a good approximation and reasonably simple
|
|
32737
|
-
for (const [numerator, denominator] of convergents) {
|
|
32738
|
-
// Only consider "nice" fractions (reasonable size numerator/denominator)
|
|
32739
|
-
if (denominator <= 20 && Math.abs(numerator) <= 50) {
|
|
32740
|
-
if (Math.abs(num - numerator / denominator) < CONFIG.epsilon) {
|
|
32741
|
-
return true;
|
|
32742
|
-
}
|
|
32743
|
-
}
|
|
32744
|
-
}
|
|
32745
|
-
// Check if it's a square root of a small integer (highest priority)
|
|
32746
|
-
// This helps prefer direct √n forms
|
|
32747
|
-
for (let i = 2; i <= 30; i++) {
|
|
32748
|
-
if (Math.abs(num - Math.sqrt(i)) < CONFIG.epsilon) {
|
|
32749
|
-
// Give very high priority to prime square roots
|
|
32750
|
-
if (isPrime(i)) {
|
|
32751
|
-
return true;
|
|
32752
|
-
}
|
|
32753
|
-
// Also prioritize other small square roots
|
|
32754
|
-
if (i <= 20) {
|
|
32755
|
-
return true;
|
|
32756
|
-
}
|
|
32757
|
-
}
|
|
32758
|
-
}
|
|
32759
|
-
// Check if it's close to a common constant
|
|
32760
|
-
for (const constant of CONSTANTS) {
|
|
32761
|
-
if (Math.abs(num - constant.value) < CONFIG.epsilon) {
|
|
32762
|
-
return true;
|
|
32763
|
-
}
|
|
32764
|
-
}
|
|
32765
|
-
// Check trig values
|
|
32766
|
-
for (const value of TRIG_VALUES) {
|
|
32767
|
-
if (Math.abs(num - value.value) < CONFIG.epsilon) {
|
|
32768
|
-
return true;
|
|
32769
|
-
}
|
|
32770
|
-
}
|
|
32771
|
-
// Other common values with lower priority
|
|
32772
|
-
const COMMON_VALUES = [
|
|
32773
|
-
Math.sin(Math.PI / 3),
|
|
32774
|
-
Math.cos(Math.PI / 4),
|
|
32775
|
-
];
|
|
32776
|
-
for (const value of COMMON_VALUES) {
|
|
32777
|
-
if (Math.abs(num - value) < CONFIG.epsilon) {
|
|
32778
|
-
return true;
|
|
32779
|
-
}
|
|
32780
|
-
}
|
|
32781
|
-
return false;
|
|
32782
|
-
}
|
|
32783
|
-
/**
|
|
32784
|
-
* Find greatest common divisor helper method
|
|
32785
|
-
*/
|
|
32786
|
-
findGCD(a, b) {
|
|
32787
|
-
a = Math.abs(a);
|
|
32788
|
-
b = Math.abs(b);
|
|
32789
|
-
while (b !== 0) {
|
|
32790
|
-
const temp = b;
|
|
32791
|
-
b = a % b;
|
|
32792
|
-
a = temp;
|
|
32793
|
-
}
|
|
32794
|
-
return a;
|
|
32795
|
-
}
|
|
32796
|
-
}
|
|
32797
|
-
|
|
32798
|
-
/**
|
|
32799
|
-
* Symbolic Number Printer using Expression Tree Representation
|
|
32800
|
-
*
|
|
32801
|
-
* This implementation uses a proper expression tree to represent mathematical
|
|
32802
|
-
* expressions, enabling more sophisticated simplification and consistent formatting.
|
|
32803
|
-
*/
|
|
32804
|
-
/**
|
|
32805
|
-
* Main function to convert a number to its symbolic representation
|
|
32806
|
-
* @param num The number to convert to symbolic form
|
|
32807
|
-
* @returns A string containing the symbolic representation
|
|
32808
|
-
*/
|
|
32809
|
-
function prettyPi(num, config = {}) {
|
|
32810
|
-
if (!isFinite(num)) {
|
|
32811
|
-
if (isNaN(num)) {
|
|
32812
|
-
return "NaN";
|
|
32813
|
-
}
|
|
32814
|
-
return num > 0 ? "∞" : "-∞";
|
|
32815
|
-
}
|
|
32816
|
-
setConfig(config);
|
|
32817
|
-
const parser = new ExpressionParser();
|
|
32818
|
-
const exprTree = parser.parseNumber(num);
|
|
32819
|
-
const simplified = exprTree.simplify();
|
|
32820
|
-
return simplified.toString();
|
|
32821
|
-
}
|
|
32822
|
-
|
|
32823
31447
|
function stringifyValue(value, html) {
|
|
32824
31448
|
var _a;
|
|
32825
31449
|
var gt = '>';
|
|
@@ -32837,7 +31461,7 @@ function stringifyValue(value, html) {
|
|
|
32837
31461
|
if (typeof value === 'object' && value instanceof RegExp)
|
|
32838
31462
|
return "".concat(value);
|
|
32839
31463
|
if (typeof value === 'number') {
|
|
32840
|
-
return
|
|
31464
|
+
return "".concat(value);
|
|
32841
31465
|
}
|
|
32842
31466
|
if (isRegularExpression(value))
|
|
32843
31467
|
return "/".concat(value.s, "/").concat(value.f);
|
|
@@ -32850,12 +31474,12 @@ function stringifyValue(value, html) {
|
|
|
32850
31474
|
return '[]';
|
|
32851
31475
|
if (value.length > 8) {
|
|
32852
31476
|
return "[\n ".concat(value.map(function (cell) {
|
|
32853
|
-
return
|
|
31477
|
+
return cell;
|
|
32854
31478
|
}).join(',\n '), "\n]");
|
|
32855
31479
|
}
|
|
32856
31480
|
else {
|
|
32857
31481
|
return "[".concat(value.map(function (cell) {
|
|
32858
|
-
return
|
|
31482
|
+
return cell;
|
|
32859
31483
|
}).join(', '), "]");
|
|
32860
31484
|
}
|
|
32861
31485
|
}
|
|
@@ -32891,45 +31515,11 @@ function replaceInfinities(value) {
|
|
|
32891
31515
|
}
|
|
32892
31516
|
return value;
|
|
32893
31517
|
}
|
|
32894
|
-
function prettyIfNumber(value) {
|
|
32895
|
-
if (typeof value === 'number') {
|
|
32896
|
-
return prettyPi(value);
|
|
32897
|
-
}
|
|
32898
|
-
return "".concat(value);
|
|
32899
|
-
}
|
|
32900
31518
|
function stringifyMatrix(matrix) {
|
|
32901
|
-
var padding = matrix.flat().reduce(function (max, cell) { return Math.max(max,
|
|
32902
|
-
var rows = matrix.map(function (row) { return "[".concat(row.map(function (cell) { return
|
|
31519
|
+
var padding = matrix.flat().reduce(function (max, cell) { return Math.max(max, "".concat(cell).length); }, 0) + 1;
|
|
31520
|
+
var rows = matrix.map(function (row) { return "[".concat(row.map(function (cell) { return "".concat(cell).padStart(padding); }).join(' '), " ]"); });
|
|
32903
31521
|
return rows.join('\n');
|
|
32904
31522
|
}
|
|
32905
|
-
// function isMatrix(value: unknown): value is (null | number | string | boolean)[][] {
|
|
32906
|
-
// if (!Array.isArray(value)) {
|
|
32907
|
-
// return false
|
|
32908
|
-
// }
|
|
32909
|
-
// if (!value.every(row => Array.isArray(row))) {
|
|
32910
|
-
// return false
|
|
32911
|
-
// }
|
|
32912
|
-
// let cols = -1
|
|
32913
|
-
// for (const row of value) {
|
|
32914
|
-
// if (cols === -1) {
|
|
32915
|
-
// cols = row.length
|
|
32916
|
-
// if (cols === 0) {
|
|
32917
|
-
// return false
|
|
32918
|
-
// }
|
|
32919
|
-
// }
|
|
32920
|
-
// else {
|
|
32921
|
-
// if (row.length !== cols) {
|
|
32922
|
-
// return false
|
|
32923
|
-
// }
|
|
32924
|
-
// }
|
|
32925
|
-
// for (const cell of row) {
|
|
32926
|
-
// if (typeof cell !== 'number' && typeof cell !== 'string' && typeof cell !== 'boolean' && cell !== null) {
|
|
32927
|
-
// return false
|
|
32928
|
-
// }
|
|
32929
|
-
// }
|
|
32930
|
-
// }
|
|
32931
|
-
// return true
|
|
32932
|
-
// }
|
|
32933
31523
|
function findAllOccurrences(input, pattern) {
|
|
32934
31524
|
var matches = __spreadArray([], __read(input.matchAll(pattern)), false);
|
|
32935
31525
|
return new Set(matches.map(function (match) { return match[0]; }));
|