( expr: Expression, variable?: string )
| 557 | * IMPORTANT: Does not call .simplify() to avoid infinite recursion. |
| 558 | */ |
| 559 | function extractContent(expr: Expression, variable: string): Expression | null { |
| 560 | const ce = expr.engine; |
| 561 | const coeffs = getPolynomialCoefficients(expr, variable); |
| 562 | if (!coeffs) return null; |
| 563 | |
| 564 | // Extract integer values from all coefficients |
| 565 | const intCoeffs: number[] = []; |
| 566 | for (const c of coeffs) { |
| 567 | const n = asSmallInteger(c); |
| 568 | if (n === null) return null; |
| 569 | intCoeffs.push(n); |
| 570 | } |
| 571 | |
| 572 | // Compute GCD of all non-zero coefficients |
| 573 | const gcd = (a: number, b: number): number => { |
| 574 | a = Math.abs(a); |
| 575 | b = Math.abs(b); |
| 576 | while (b) { |
| 577 | [a, b] = [b, a % b]; |
| 578 | } |
| 579 | return a; |
| 580 | }; |
| 581 | |
| 582 | let content = 0; |
| 583 | for (const c of intCoeffs) { |
| 584 | if (c !== 0) content = gcd(content, c); |
| 585 | } |
| 586 | |
| 587 | if (content <= 1) return null; |
| 588 | |
| 589 | // If the leading coefficient is negative, extract a negative content so |
| 590 | // the primitive part leads with a positive coefficient: |
| 591 | // -2x - 4 → -2(x + 2), not 2(-x - 2) |
| 592 | // (the top slot can be 0 if leading terms canceled during expansion, so |
| 593 | // scan back to the last non-zero coefficient) |
| 594 | let i = intCoeffs.length - 1; |
| 595 | while (i >= 0 && intCoeffs[i] === 0) i -= 1; |
| 596 | if (i >= 0 && intCoeffs[i] < 0) content = -content; |
| 597 | |
| 598 | // Divide all coefficients by the content to get the primitive part |
| 599 | const primitiveCoeffs = coeffs.map((c) => { |
| 600 | const n = asSmallInteger(c)!; |
| 601 | return ce.number(n / content); |
| 602 | }); |
| 603 | |
| 604 | // Reconstruct the primitive polynomial |
| 605 | const primitive = fromCoefficients(primitiveCoeffs, variable); |
| 606 | |
| 607 | // Recursively factor the primitive part |
| 608 | const factoredPrimitive = factorPolynomial(primitive, variable); |
| 609 | |
| 610 | // Build the product `content · primitive` with ce.function rather than |
| 611 | // `.mul()`. The arithmetic `.mul()` distributes a numeric factor over a |
| 612 | // bare sum — e.g. `3 · (2x + 3)` collapses back to `6x + 9`, undoing the |
| 613 | // content extraction for a primitive that doesn't factor further (such as |
| 614 | // a linear polynomial). Canonical `Multiply` does not distribute, so the |
| 615 | // factored form is preserved. `|content|` is always ≥ 2 here (we returned |
| 616 | // null for content ≤ 1 before applying the sign), so the coefficient-±1 |
no test coverage detected