(op: Expression)
| 27 | } |
| 28 | |
| 29 | /** Combine rational expressions into a single fraction */ |
| 30 | export function together(op: Expression): Expression { |
| 31 | const ce = op.engine; |
| 32 | const h = op.operator; |
| 33 | |
| 34 | // Thread over inequality |
| 35 | if (isFunction(op)) { |
| 36 | if (isRelationalOperator(h)) return ce.function(h, op.ops.map(together)); |
| 37 | |
| 38 | if (h === 'Divide') return op.ops[0].div(op.ops[1]); |
| 39 | |
| 40 | if (h === 'Negate') return together(op.ops[0]).neg(); |
| 41 | |
| 42 | if (h === 'Add') { |
| 43 | // Fold the terms over a common denominator: |
| 44 | // n₁/d₁ + n₂/d₂ = (n₁·d₂ + n₂·d₁)/(d₁·d₂), reusing d when d₁ = d₂. |
| 45 | let num: Expression | undefined = undefined; |
| 46 | let den: Expression | undefined = undefined; |
| 47 | let sawDenominator = false; |
| 48 | for (const term of op.ops) { |
| 49 | const t = together(term); |
| 50 | let tn = t; |
| 51 | let td: Expression | undefined = undefined; |
| 52 | if (isFunction(t, 'Divide')) { |
| 53 | [tn, td] = t.ops; |
| 54 | sawDenominator = true; |
| 55 | } else if (!isNumber(t)) { |
| 56 | // A denominator is not always a `Divide` node: canonical form writes |
| 57 | // `1/x^2` as `Power(x, -2)` and `y/x^2` as a `Multiply` with a |
| 58 | // negative exponent. Only matching `Divide` left those terms with an |
| 59 | // implicit denominator of 1, so they were folded into the numerator |
| 60 | // and the result kept negative powers (`(x·x^-2 + 1)/x`). |
no test coverage detected