(expr: Expression)
| 694 | return factor(expr); |
| 695 | } |
| 696 | |
| 697 | /** |
| 698 | * Recursively factor each operand of a product. Used to fully factor the |
| 699 | * result of a single difference-of-squares split, whose halves may themselves |
| 700 | * be factorable (e.g. (x³−1)(x³+1)). Non-product inputs are returned |
| 701 | * unchanged. Recursion is bounded: each call factors strictly lower-degree |
| 702 | * factors. |
| 703 | */ |
| 704 | function refactorProductFactors( |
| 705 | product: Expression, |
| 706 | variable: string | undefined |
| 707 | ): Expression { |
| 708 | const ce = product.engine; |
| 709 | |
| 710 | // Power: factor the base, then distribute a positive-integer exponent over |
| 711 | // the factored base so each prime factor carries the multiplicity — |
| 712 | // (x²+x)² → x²·(x+1)², while an irreducible base such as (x²+1)² is left |
| 713 | // intact. collectFactors/partial fractions need irreducible factors, not |
| 714 | // (f·g)^n. |
| 715 | if (isFunction(product, 'Power')) { |
| 716 | const exp = asSmallInteger(product.op2); |
| 717 | if (exp === null || exp <= 0) return product; |
| 718 | const factoredBase = factorPolynomial(product.op1, variable); |
| 719 | if (factoredBase.isSame(product.op1)) return product; // base irreducible |
| 720 | const baseFactors = isFunction(factoredBase, 'Multiply') |
| 721 | ? factoredBase.ops |
| 722 | : [factoredBase]; |
| 723 | return ce.function( |
| 724 | 'Multiply', |
| 725 | baseFactors.map( |
| 726 | (f) => ce.function('Power', [f.json, product.op2.json]).json |
| 727 | ) |
| 728 | ); |
| 729 | } |
| 730 | |
| 731 | if (!isFunction(product, 'Multiply')) return product; |
| 732 | const factors = product.ops.map((f) => factorPolynomial(f, variable)); |
| 733 | return ce.function( |
| 734 | 'Multiply', |
| 735 | factors.map((f) => f.json) |
| 736 | ); |
| 737 | } |
| 738 | |
| 739 | /** |
| 740 | * Pull a common monomial factor xᵏ out of a polynomial: x³+x² → x²·(x+1), |
| 741 | * 3x⁴+2x³ → x³·(3x+2). The cofactor is recursively factored. Returns null |
| 742 | * when the constant term is nonzero (no monomial content) or the input is not |
| 743 | * a polynomial in `variable`. |
| 744 | */ |
| 745 | function extractMonomialContent( |
| 746 | expr: Expression, |
| 747 | variable: string |
| 748 | ): Expression | null { |
| 749 | const coeffs = getPolynomialCoefficients(expr, variable); |
| 750 | if (!coeffs || coeffs.length === 0) return null; |
| 751 | |
| 752 | // Lowest index with a nonzero coefficient = the shared power of x. |
| 753 | let k = 0; |
no test coverage detected