diff --git a/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js b/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js index ca8f7763..85f84b4b 100644 --- a/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js +++ b/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js @@ -240,7 +240,6 @@ function cancelTerms(numerator, denominator) { if (print.ascii(numerator) === print.ascii(denominator)) { return new CancelOutStatus(null, null, true); } - // case 2: they're both exponent nodes with the same base // e.g. (2x+5)^8 and (2x+5)^2 if (Node.Type.isOperator(numerator, '^') && @@ -257,16 +256,25 @@ function cancelTerms(numerator, denominator) { numerator.args[1] = newExponent; return new CancelOutStatus(numerator, null, true); } - // case 3: they're both polynomial terms, check if they have the same symbol // e.g. 4x^2 / 5x^2 => 4 / 5 // e.g. 4x^3 / 5x^2 => 4x^(3-2) / 5 + // case 3.1: they're both polynomial terms with different symbols but with coefficients + // e.g 20x / 40y => x / 2y + // e.g 60x / 40y => 3x / 2y + // e.g 4x / 2y => 2x / y if (Node.PolynomialTerm.isPolynomialTerm(numerator) && Node.PolynomialTerm.isPolynomialTerm(denominator)) { const numeratorTerm = new Node.PolynomialTerm(numerator); const denominatorTerm = new Node.PolynomialTerm(denominator); if (numeratorTerm.getSymbolName() !== denominatorTerm.getSymbolName()) { - return new CancelOutStatus(numerator, denominator); + if (Node.Type.isOperator(numerator, '*') && Node.Type.isOperator(denominator, '*')) { + // case 3.1 + return cancelCoeffs(numerator, denominator); + } + else { + return new CancelOutStatus(numerator, denominator); + } } const numeratorExponent = numeratorTerm.getExponentNode(true); let denominatorExponent = denominatorTerm.getExponentNode(true); @@ -294,7 +302,6 @@ function cancelTerms(numerator, denominator) { // or is multiplication node // e.g. 2 / 4x -> 1 / 2x // e.g. ignore cases like: 2 / a and 2 / x^2 - if (Node.Type.isConstant(numerator) && Node.Type.isOperator(denominator, '*') && Node.PolynomialTerm.isPolynomialTerm(denominator)) { @@ -330,7 +337,6 @@ function cancelTerms(numerator, denominator) { // case 5: both numerator and denominator are numbers within a more complicated fraction // e.g. (35 * nthRoot (7)) / (5 * nthRoot(5)) -> (7 * nthRoot(7)) / nthRoot(5) - if (Node.Type.isConstant(numerator) && Node.Type.isConstant(denominator)) { const frac = Node.Creator.operator('/', [numerator, denominator]); const reduceStatus = divideByGCD(frac); @@ -371,4 +377,42 @@ function isMultiplicationOfTerms(node) { !Node.PolynomialTerm.isPolynomialTerm(node)); } +function cancelCoeffs(numerator, denominator){ + const denominatorTerm = new Node.PolynomialTerm(denominator); + const numeratorTerm = new Node.PolynomialTerm(numerator); + + const denominatorCoeff = denominatorTerm.getCoeffNode(); + const denominatorVariable = denominatorTerm.getSymbolNode(); + const denominatorExponent = denominatorTerm.getExponentNode(); + + const numeratorCoeff = numeratorTerm.getCoeffNode(); + const numeratorVariable = numeratorTerm.getSymbolNode(); + const numeratorExponent = numeratorTerm.getExponentNode(); + + // simplify a constant fraction (e.g 2 / 4) + const frac = Node.Creator.operator('/', [numeratorCoeff, denominatorCoeff]); + + const reduceStatus = divideByGCD(frac); + + if (!reduceStatus.hasChanged()) { + return new CancelOutStatus(numerator, denominator, false); + } + + // Sometimes the fraction reduces to a constant e.g. 6 / 2 -> 3, + // in which case the denominator coefficient should be null + let newDenominatorCoeff = null; + let newNumerator = null; + if (Node.Type.isConstant(reduceStatus.newNode)) { + newNumerator = Node.Creator.polynomialTerm(numeratorVariable, numeratorExponent, reduceStatus.newNode); + newDenominatorCoeff = null; + } + else { + newNumerator = Node.Creator.polynomialTerm(numeratorVariable, numeratorExponent, reduceStatus.newNode.args[0]); + newDenominatorCoeff = reduceStatus.newNode.args[1]; + } + const newDenominator = Node.Creator.polynomialTerm(denominatorVariable, denominatorExponent, newDenominatorCoeff); + + return new CancelOutStatus(newNumerator, newDenominator, true); +} + module.exports = cancelLikeTerms; diff --git a/test/simplifyExpression/fractionsSearch/cancelLikeTerms.test.js b/test/simplifyExpression/fractionsSearch/cancelLikeTerms.test.js index a877684e..6fe79df6 100644 --- a/test/simplifyExpression/fractionsSearch/cancelLikeTerms.test.js +++ b/test/simplifyExpression/fractionsSearch/cancelLikeTerms.test.js @@ -24,9 +24,14 @@ describe('cancel like terms', function () { ['2/ (4x)', '1 / (2x)'], ['2/ (4x^2)', '1 / (2x^2)'], ['2 a / a', '2'], - ['(35 * nthRoot (7)) / (5 * nthRoot(5))','(7 * nthRoot(7)) / nthRoot(5)'], + ['(35 * nthRoot (7)) / (5 * nthRoot(5))', '(7 * nthRoot(7)) / nthRoot(5)'], ['3/(9r^2)', '1 / (3r^2)'], - ['6/(2x)', '3 / (x)'] + ['6/(2x)', '3 / (x)'], + ['(40 * x) / (20 * y)', '(2x) / (y)'], + ['(20 * x) / (40 * y)', '(x) / (2y)'], + ['20x / (40y)', 'x / (2y)'], + ['60x / (40y)', '3x / (2y)'], + ['4x / (2y)', '2x / (y)'] ]; tests.forEach(t => testCancelLikeTerms(t[0], t[1])); diff --git a/test/simplifyExpression/simplify.test.js b/test/simplifyExpression/simplify.test.js index 6fec2487..d6a30840 100644 --- a/test/simplifyExpression/simplify.test.js +++ b/test/simplifyExpression/simplify.test.js @@ -51,6 +51,11 @@ describe('can simplify with division', function () { ['2x/x', '2'], ['2x/4/3', '1/6 x'], ['((2+x)(3+x))/(2+x)', '3 + x'], + ['(20 * x) / (5 * (40 * y))', 'x / (10y)'], + ['400 * z / ((20 * x) / (5 * (40 * y)))', '(4000y * z) / x'], + ['20x / (40y)', 'x / (2y)'], + ['60x / (40y)', '3x / (2y)'], + ['4x / (2y)', '2x / y'] ]; tests.forEach(t => testSimplify(t[0], t[1], t[2])); // TODO: factor the numerator to cancel out with denominator