@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.
Files changed (2) hide show
  1. package/dist/cli/cli.js +11 -1421
  2. 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.31";
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$1(num) {
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$1(num)) {
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$1(n); },
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$1(i)) {
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$1(num);
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 prettyPi(value);
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 prettyPi(cell);
31477
+ return cell;
32854
31478
  }).join(',\n '), "\n]");
32855
31479
  }
32856
31480
  else {
32857
31481
  return "[".concat(value.map(function (cell) {
32858
- return prettyPi(cell);
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, prettyIfNumber(cell).length); }, 0) + 1;
32902
- var rows = matrix.map(function (row) { return "[".concat(row.map(function (cell) { return prettyIfNumber(cell).padStart(padding); }).join(' '), " ]"); });
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]; }));