Conversation
|
Mathlib CI status (docs):
|
|
Reference manual CI status:
|
TwoFX
left a comment
There was a problem hiding this comment.
Codex believes that power and inverse do not really need native implementations, see https://gist.github.com/TwoFX/fbbe5362c992e509a4044cd6f8c50172 and https://gist.github.com/TwoFX/a81c8df6b457eed0a44da36b5e0c8ff5
Replace the `lean_uint64_pow_mod` and `lean_uint64_inv_mod` externs with word-sized Lean loops installed via `@[csimp]`, so the compiled behaviour is proved equal to the natural-number definitions rather than trusted. `powModImpl` consumes a `Nat` exponent 64 bits at a time, costing one division per word rather than one per bit; the previous extern halved an mpz exponent on every bit and never returned to machine-word arithmetic. `invModImpl` runs the extended Euclidean loop entirely in `UInt64`. `mulMod` keeps its extern, since no Lean primitive exposes a widening 64-by-64 multiply. Add `toNat_mulMod` and `toNat_powMod`, which the equivalence proofs need. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Thank you, good catch. I've double checked this, and agree. So |
Add `toNat_mulMod_zero`, `toNat_powMod_zero` and `powMod_lt`, and prove that `invMod?` computes a multiplicative inverse: `isSome_invMod?` characterizes when one exists in terms of `Nat.gcd`, `mulMod_of_invMod?_eq_some` gives the defining equation, and `lt_of_invMod?_eq_some` gives the range. The `invMod?` proofs go through `invMod?_go_spec`, the loop invariant of the extended Euclidean algorithm: the coefficient sequence satisfies `oldS * a ≡ oldR` and `s * a ≡ r` modulo the modulus while `Nat.gcd oldR r` is preserved. Reorder the module so the specification precedes the runtime implementations. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add `invMod?_eq_some_iff`, identifying the result of `invMod?` by its defining equation: `invMod? a modulus = some x` exactly when `x < modulus` and `mulMod a x modulus = 1 % modulus`. The bound is part of the characterization, since the equation alone also admits representatives shifted by `modulus`. Also run fixed vectors unconditionally in the portable multiplication test. Every assertion was previously guarded by the presence of a 128-bit type, so on a compiler that genuinely lacks one the test did nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rework the private lemmas supporting the modular `UInt64` operations, cutting the module from 617 to 480 lines. The public statements, the compiled definitions, and the generated C are unchanged. The coefficient step of the extended Euclidean loop no longer needs a modular cancellation lemma: since `nextS` is congruent to `oldS + (modulus - product)`, multiplying through leaves a multiple of the modulus, which vanishes. `nextS_mul_mod` states that step directly, replacing two helpers. `invMod?_eq_some_spec` factors out the unfolding that the three `invMod?` characterization lemmas each repeated, and `mul_pow_step` does the same for the squaring step shared by the word and chunk loops. `gcd_eq_one_of_mul_mod` no longer requires a positive modulus. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reduce the module from 480 to 454 lines. The public statements, the compiled definitions, and the generated C are unchanged. `invMod?_go_spec` and `invMod?_spec` now destructure the returned pair with a `let`, so the conclusions name their components instead of repeating the call, and `invMod?_go_spec` takes its invariants as arguments rather than introducing them. `gcd_eq_one_of_mul_mod` goes through `Nat.gcd_dvd_gcd_mul_right_left`, which absorbs the modulus-one case. Correct the runtime implementation comment: `powMod` already multiplies through the `mulMod` extern, so it is the one-bit-at-a-time exponent handling that is slow there, not intermediate products. That description applies to the Euclidean loop in `invMod?`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| if exponent = 0 then | ||
| 1 % modulus | ||
| else | ||
| let result := powMod (mulMod base base modulus) (exponent / 2) modulus | ||
| if exponent % 2 = 1 then mulMod result base modulus else result | ||
| termination_by exponent | ||
| decreasing_by omega |
There was a problem hiding this comment.
If you're going to csimp it away anyway, why not just make this .ofNat ((base.toNat ^ exponent) % modulus.toNat))?
| if modulus = 0 then | ||
| none | ||
| else | ||
| let rec go (oldR r oldS s : Nat) : Nat × Nat := |
There was a problem hiding this comment.
This is fine for now, but eventually we'll probably want an extended Euclidean algorithm as a public API on Nat (mathlib already has this) and go through that.
There was a problem hiding this comment.
I'll do this as a follow up, but not in this PR.
Use direct division and remainder lemmas for termination, simplify inverse existence, and combine the inverse implementation’s base cases.
This PR adds
UInt64.mulMod,UInt64.powMod, andUInt64.invMod?for fixed-width modular arithmetic without overflowing intermediate products.powModaccepts an arbitraryNatexponent, andinvMod?returnsnonefor a zero modulus or noncoprime inputs.The kernel-visible definitions use natural-number arithmetic.
mulModis overridden by a C implementation using 128-bit intermediates where available and a portable double-and-add fallback otherwise; no Lean primitive exposes a widening 64-by-64 multiply.powModandinvMod?are instead overridden by word-sized Lean loops installed with@[csimp], so their compiled behaviour is proved equal to the definitions rather than trusted.powModconsumes the exponent 64 bits at a time, costing oneNatdivision per word rather than one per bit.toNat_mulMod,toNat_powModand their zero-modulus counterparts characterize the results ofmulModandpowMod, andmulMod_ltandpowMod_ltbound them. ForinvMod?,isSome_invMod?says an inverse exists exactly whenNat.gcdof the inputs is one, andinvMod?_eq_some_iffidentifies the returned value by its defining equation together with the bound that makes it unique.The operations are tested against independent exact-
Natreferences over a boundary matrix that includes exponents on both sides of the scalar and big-Natrepresentation boundary, exponents whose middle 64-bit chunks are zero, and Fermat's little theorem for known primes. A separate C test forces the portable multiplication branch and compares it with 128-bit arithmetic on one million deterministic pseudorandom inputs.These operations provide reusable runtime primitives for
UInt64-backed residue and finite-field libraries.🤖 Generated with Claude Code