Planned division has the exact reference quotient and remainder.
8.3. Division by Newton iteration
Division with remainder reduces to multiplication. Write rev f for the
polynomial whose coefficients are those of f in reverse order. If
a = q b + r with a of degree m, b of degree n and r of degree less
than n, then reversing gives rev a ≡ rev q · rev b modulo xᵐ⁻ⁿ⁺¹. The
constant term of rev b is the leading coefficient of b. When it is
invertible, rev b has an inverse as a power series, and
rev q = rev a · (rev b)⁻¹ mod xᵐ⁻ⁿ⁺¹.
The remainder is then a - q b. Newton's method computes the inverse of a
power series g: if h agrees with g⁻¹ to k terms, then h (2 - g h)
agrees with it to 2k terms. Each step costs a few multiplications at the
new precision, and the precisions double, so the whole inverse costs a
constant times one multiplication at the final precision. Dividing a
polynomial with 2n coefficients by one with n therefore costs a constant
times one product of polynomials with n coefficients. Hex.DensePoly.reverseSeries reverses a polynomial into a
series, and Hex.DensePoly.reciprocalWith runs Newton's method. It
takes the inverse of the constant term as an argument, and the theorem
Hex.DensePoly.reciprocalWith_eq says that it returns the same series
as TSeries.invOfUnit from HexTruncatedSeries, which computes the inverse
one coefficient at a time.
When many polynomials are divided by the same divisor, the inverse needs to be
computed only once. A Hex.DensePoly.DivPlan stores a divisor and the
inverse of its reversal to a fixed number of terms, its capacity, which bounds
the length of the quotients it can produce.
Hex.DensePoly.DivPlan.ofMonic builds one for a monic divisor over a
commutative ring, and Hex.DensePoly.DivPlan.ofNonzero for a nonzero
divisor over a field. Hex.DensePoly.DivPlan.divMod and
Hex.DensePoly.DivPlan.mod take a proof that the quotient fits.
Modulo x² - x - 1, the power xᵏ reduces to Fₖ x + Fₖ₋₁, where Fₖ is
the kth Fibonacci number:
def golden : DensePoly Int := #p[-1, -1, 1]
def goldenPlan : DivPlan Int :=
DivPlan.ofMonic (karatsubaPlan 32) golden
(⊢ golden.Monic ⊢ golden.leadingCoeff = 1; All goals completed! 🐙) (⊢ golden ≠ 0 All goals completed! 🐙) 128
def reduce (p : DensePoly Int) :
Option (DensePoly Int) :=
if h : quotientLength p goldenPlan.divisor ≤
goldenPlan.capacity then
some (goldenPlan.mod p h)
else none
#eval reduce (monomial 100 1)
For a single division over a field, Hex.DensePoly.divModWith builds
the plan and uses it once. It returns exactly the quotient and remainder of
HexPoly's long division, including its conventions for a zero divisor and
for a divisor longer than the dividend:
Hex.DensePoly.divModWith_eq.{u} {F : Type u} [DecidableEq F] [Lean.Grind.Field F] (mul : Hex.DensePoly.MulPlan F) (p q : Hex.DensePoly F) : Hex.DensePoly.divModWith mul p q = p.divMod qHex.DensePoly.divModWith_eq.{u} {F : Type u} [DecidableEq F] [Lean.Grind.Field F] (mul : Hex.DensePoly.MulPlan F) (p q : Hex.DensePoly F) : Hex.DensePoly.divModWith mul p q = p.divMod q
When the divisor or the quotient has at most eight coefficients, long division
is faster, and compiled code for divModWith uses it. In fact
divModWith is defined to be HexPoly's divMod, and a @[csimp] lemma,
which proves the two functions equal, tells the compiler to run the
Newton version instead. So a proof that evaluates divModWith in the kernel,
for example by decide, sees long division. The gcd functions and
multipoint evaluation below are set up in the same way.