Universe

Typeset synthetic division (Ruffini’s rule) in Typst — the classic three-row division box and the stacked factorization staircase.

Unlike a sign or variation table, this computes: you pass the coefficients and the root, and it does the arithmetic and draws it — the products row, the running sums, the boxed remainder. No more hand-building tables and quietly dropping the middle row. No dependencies (native table).

Single division, three-row tableau Full factorization staircase

Leading coefficient not one Spanish labels

Usage

#import "@preview/stair-division:0.1.0": ruffini, ruffini-factor

// Divide x³ − 2x² + 1 by (x − 2):
#ruffini((1, -2, 0, 1), 2)
// → three-row tableau; Quotient: C(x) = x²   Remainder: R = 1

// Factor x³ − 6x² + 11x − 6 using its roots 1, 2, 3:
#ruffini-factor((1, -6, 11, -6), (1, 2, 3))
// → stacked staircase; Factorization: P(x) = (x − 1)(x − 2)(x − 3)

Coefficients go highest degree first and must include zeros for missing terms: x³ − 2x² + 1(1, -2, 0, 1).

Divisor convention: root is the a in (x − a). To divide by (x + 3), pass root: -3.

Fractions

Arithmetic is exact (rational), not floating-point, and fractions render as fractions. You can write them two ways:

  • As a plain number — works for ordinary fractions, whose value the package recovers exactly (even repeating decimals like 1/3):

    #ruffini((2, -1, -1), 1/2)      // root 1/2   → shows ½
    #ruffini((1, 0, -3), 1/3)       // root 1/3   → shows ⅓, exact
    #ruffini((0.25, 0.5, -1), 0.5)  // decimals too → ¼, ½
    
  • As a string (in quotes) — always exact, no matter how unusual the fraction. Both for root and inside coefficients:

    #ruffini(("1/2", "1/4", "-1/4"), "1/2")   // fractional coefficients + root
    

⚠️ Use quotes for unusual fractions. A bare number goes through Typst’s floating-point first, so a fraction with a large denominator cannot be recovered — the package then stops with a clear error telling you to quote it (it never guesses a wrong fraction). Rule of thumb: ordinary fractions (1/2, 2/3, 5/6, 1/12…) work as bare numbers; anything exotic (7/99991, 355/113…) must be a string ("7/99991"). When in doubt, quote it — the string form is always exact.

#ruffini((1, 0, -3), 1/99991)      // ✗ error: pass it as a string, e.g. "1/3"
#ruffini((1, 0, -3), "1/99991")    // ✓ exact

Variable. The rendered labels use x by default; pass variable: "t" (or any letter) to write C(t), P(z), (t − 2), …

ruffini(coefficients, root, ...)

One division P(x) ÷ (x − root), rendered as the three-row tableau (coefficients · products · results) with the remainder boxed.

Parameter Default Meaning
coefficients (required) Array, highest degree first, zeros included. Numbers, or string fractions for unusual ones (see Fractions).
root (required) The a in (x − a). A number (-3, 1/2), or a string for unusual fractions ("7/99991").
lang "en" Language of the rendered words: "en" or "es".
variable "x" The polynomial’s variable in the rendered labels.
color blue Accent color of the L-rule and the remainder box.
show-result true Append the Quotient / Remainder line.
highlight-remainder true Draw the box around the remainder cell.
trail false Overlay teaching arrows (see below).

Explaining the algorithm (trail)

trail: true overlays the arrows that show how synthetic division works — bring the first coefficient down, multiply by the root (the ×a diagonals), add the column (the + signs) — so it doubles as a lecture figure. Drawn natively (no CeTZ). Best with integer coefficients.

Teaching trail: bring down, multiply by the root, add the column

#ruffini((1, -2, 0, 1), 2, trail: true)

ruffini-factor(coefficients, roots, ...)

Applies several roots in turn — each quotient becomes the next dividend — and draws the stacked staircase. If every division is exact, it appends the factorization; otherwise it says so.

Parameter Default Meaning
coefficients (required) Array, highest degree first, zeros included.
roots (required) The successive values a to divide by, in order. Ints or string fractions.
lang "en" "en" or "es".
variable "x" The polynomial’s variable in the rendered labels.
color blue Accent color of the rules.
show-result true Append the Factorization line.
highlight-remainder true Box each division’s remainder cell.

The factorization keeps the leading coefficient correct and, when an irreducible factor of degree ≥ 2 remains, shows it in parentheses — e.g. P(x) = (x − 1)(x + 1)(3x + 2) or P(x) = (x − 2)(x² + x + 1).

What it handles

Case Behavior
Exact division Remainder 0, boxed; quotient shown.
Nonzero remainder Boxed remainder; R = … in the result line.
Missing terms Handled via the explicit zero coefficients you pass.
Fractional root / coefficients Exact rational arithmetic; rendered as fractions. Bare numbers for ordinary fractions, strings for unusual ones.
Leading coefficient ≠ 1 Preserved through the staircase and in the factorization.
Irreducible quotient ruffini-factor stops and shows (…) for the remaining factor.
Supplied value is not a root ruffini-factor reports “not an exact division”.

Localization

Rendered words default to English. Pass lang: "es" for Spanish (Cociente / Resto / Factorización). Adding a language is copying one block in the _i18n dictionary in lib.typ and translating four words — contributions welcome.

Compatibility

  • Typst >= 0.14.0
  • No dependencies.

Known limitations

See ROADMAP.md. In short: it does not find the roots for you (you supply them — that is a root-finding problem, not a layout one), and it divides only by linear binomials (x − a), which is what Ruffini’s rule is for.

License

MIT.