From 06988a5a4777521010a79ac6b9ed114c26d05b6a Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 9 Feb 2026 11:09:58 +0000 Subject: [PATCH 01/44] chore: split into separate theorems --- Fp/Theorems/UnpackedRound.lean | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Fp/Theorems/UnpackedRound.lean b/Fp/Theorems/UnpackedRound.lean index 721f1f5..efd96f3 100644 --- a/Fp/Theorems/UnpackedRound.lean +++ b/Fp/Theorems/UnpackedRound.lean @@ -134,6 +134,20 @@ theorem PackedFloat.eq_of_unpack_eq_unpack_of_isInfinity {x y : PackedFloat e s} x = y := by cases x using PackedFloat.kindCasesNaNInfZeroNum <;> try grind +/-- +Purely arithmetic fact that needs to be proven, +which should just be to show that the fixed point computation equals the +rational multiplication. +Actually, this is too strong, the theorem statemtnt should be able to state +something weaker, that only upto (s+1) bits agree, +and that the sticky bit is computed correctly. +-/ +theorem toRat_unpackNormOrNonzeroSubnorm_mul_eq_mul_toNumberRat + (a b : PackedFloat ein sin) + (ha : a.isNormOrNonzeroSubnorm = true) + (hb : b.isNormOrNonzeroSubnorm = true) : + (a.unpackNormOrNonzeroSubnorm.mul b.unpackNormOrNonzeroSubnorm).toRat = a.toNumberRat * b.toNumberRat := sorry + set_option warn.sorry false in /-- Example theorem we will prove, using our proof strategy of proving against the SMT-Lib semantics. @@ -240,7 +254,5 @@ theorem mul_eq_mul {ein sin : Nat} (hsin : 0 < sin) (he : 0 < ein) rw [roundQ_Number_eq_round] rw [PackedFloat.toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm hb] simp only [ExtRat.number_mul_number_eq, ExtRat.Number.injEq] - -- Purely arithmetic statement. - -- ⊢ (a.unpackNormOrNonzeroSubnorm.mul b.unpackNormOrNonzeroSubnorm).toRat = a.toNumberRat * b.toNumberRat - sorry + apply toRat_unpackNormOrNonzeroSubnorm_mul_eq_mul_toNumberRat <;> assumption end Fp From dce7a0abc17f5805c93519befdfff87af353710e Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 9 Feb 2026 11:30:19 +0000 Subject: [PATCH 02/44] chore: figure out correct staement --- .claude/settings.local.json | 22 ---------------------- Fp/Theorems/UnpackedRound.lean | 28 +++++++++++++++++++++------- 2 files changed, 21 insertions(+), 29 deletions(-) delete mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 3a1f913..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(lake build:*)", - "Bash(git add:*)", - "Bash(git commit -m \"$\\(cat <<''EOF''\nfeat: add generic SMT-LIB binary relations and min/max relations\n\nAdd FpEqRel, FpLtRel, FpGtRel, FpLeqRel, FpGeqRel, and FpIsNaN as generic\nbinary relations parameterized over embedding v : RoundableEmbed X R,\nfollowing the BTRW15 paper definitions.\n\nAdd FpMinRel and FpMaxRel as relations \\(rather than functions\\) to capture\nthe IEEE-754 underspecification for ±0 cases where either value is valid.\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")", - "Bash(lake exe fp-lean fpMaxRel)", - "Bash(./.lake/build/bin/fp-lean:*)", - "Bash(git branch:*)", - "Bash(git ls-tree:*)", - "Bash(echo:*)", - "Bash(git commit:*)", - "Bash(git push:*)", - "Bash(git checkout:*)", - "Bash(git pull:*)", - "Bash(gh run view:*)", - "WebSearch", - "WebFetch(domain:github.com)", - "WebFetch(domain:raw.githubusercontent.com)" - ] - } -} diff --git a/Fp/Theorems/UnpackedRound.lean b/Fp/Theorems/UnpackedRound.lean index efd96f3..204e5b8 100644 --- a/Fp/Theorems/UnpackedRound.lean +++ b/Fp/Theorems/UnpackedRound.lean @@ -75,9 +75,23 @@ theorem round_eq_mkZero_of_mkZero {zeroSign : Bool} {eout sout : Nat} {rm : Roun rm zeroSign (ExtRat.Number 0) = PackedFloat.getZero eout sout zeroSign := by rcases rm <;> sorry +/-- +'uf' approximtes 'r' upto rounding. +-/ +structure ApproximatesUptoRounding (uf : UnpackedFloat ein sin) (er : ExtRat) (eout sout : Nat) : Prop where + /-- we have at least 2 bits more, of guard and sticky. -/ + hSigGe : sin + 2 ≥ sout + /-- we have at least as much exponent range. -/ + hExpGe : ein ≥ eout -- we have at least as much exponent range + /-- rational values have (sout + 1) bits of precision. -/ + hApproxUptoGuard : ∀ (r : Rat), .Number r = er → (uf.toRat - r).abs < (2 : Rat) ^ (-(sout + 1 : Int)) + /-- the sticky bit is zero iff the number truncated upto the guard bit equals -/ + hStickyBitCorrect : ∀ (r : Rat), .Number r = er → ((uf.sig.extractMsb' (sout + 1) (sin - (sout + 1)) ≠ 0) = decide (r = uf.toRat)) + set_option warn.sorry false in -theorem roundQ_Number_eq_round (er : ExtRat) (uf : UnpackedFloat ein sin) - (hruf : ExtRat.Number uf.toRat = er) : +theorem roundQ_Number_eq_round + (er : ExtRat) (uf : UnpackedFloat ein sin) + (hruf : ApproximatesUptoRounding uf er eout sout) (rm : RoundingMode) (sign : Bool) : (SmtLibSemantics.smtLibRoundMethod eout sout SmtLibSemantics.smtLibV SmtLibSemantics.smtLibV).round rm sign er = (UnpackedFloat.round uf rm).pack := by sorry @@ -142,11 +156,12 @@ Actually, this is too strong, the theorem statemtnt should be able to state something weaker, that only upto (s+1) bits agree, and that the sticky bit is computed correctly. -/ -theorem toRat_unpackNormOrNonzeroSubnorm_mul_eq_mul_toNumberRat +theorem ApproximatesUptoRounding_mul_mul (a b : PackedFloat ein sin) (ha : a.isNormOrNonzeroSubnorm = true) (hb : b.isNormOrNonzeroSubnorm = true) : - (a.unpackNormOrNonzeroSubnorm.mul b.unpackNormOrNonzeroSubnorm).toRat = a.toNumberRat * b.toNumberRat := sorry + ApproximatesUptoRounding (a.unpackNormOrNonzeroSubnorm.mul b.unpackNormOrNonzeroSubnorm) + (ExtRat.Number a.toNumberRat * ExtRat.Number b.toNumberRat) ein sin := sorry set_option warn.sorry false in /-- @@ -251,8 +266,7 @@ theorem mul_eq_mul {ein sin : Nat} (hsin : 0 < sin) (he : 0 < ein) simp [this] have : ¬ b.isZero := by grind simp [this] - rw [roundQ_Number_eq_round] rw [PackedFloat.toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm hb] - simp only [ExtRat.number_mul_number_eq, ExtRat.Number.injEq] - apply toRat_unpackNormOrNonzeroSubnorm_mul_eq_mul_toNumberRat <;> assumption + apply roundQ_Number_eq_round + apply ApproximatesUptoRounding_mul_mul <;> assumption end Fp From 0bb7a7c93a7703b80854425304dbe4e72c5145cd Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 9 Feb 2026 12:17:11 +0000 Subject: [PATCH 03/44] chore: clenaup --- Fp/Basic.lean | 41 +++++++++++++++- Fp/SmtLibSemantics.lean | 42 +++++++++++++--- Fp/Theorems/SmtLibSemanticsQ.lean | 4 +- Fp/Theorems/UnpackedRound.lean | 82 +++++++++++++++---------------- 4 files changed, 117 insertions(+), 52 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 0524b17..cdb91d5 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -59,7 +59,6 @@ instance : Repr (PackedFloat exWidth sigWidth) where reprPrec x _prec := f!"\{ sign := {if x.sign then "-" else "+"}, ex := {x.ex}, sig := {x.sig} }" - /-- A fixed point number with specified exponent offset. -/ @@ -1461,6 +1460,18 @@ theorem toExtDyadic_eq_Infinity_of_isInfinite (pf : PackedFloat e s) (hp : pf.is def toExtRat (pf : PackedFloat e s) : ExtRat := pf.toExtDyadic.toExtRat + +instance : LE (PackedFloat exWidth sigWidth) where + le x y := x.toExtRat ≤ y.toExtRat + +@[simp] +theorem le_def (x y : PackedFloat e s) : (x.toExtRat ≤ y.toExtRat) = (x ≤ y) := rfl + +instance {x y : PackedFloat e s} : Decidable (x ≤ y) := by + simp only [← PackedFloat.le_def] + simp only [← ExtRat.le_def] + infer_instance + def toExtRat' (pf : PackedFloat e s) : ExtRat := bif pf.isNaN then .NaN @@ -1474,6 +1485,8 @@ def toExtRat' (pf : PackedFloat e s) : ExtRat := -- `-(bias e - 1)` is slightly different from SMT-LIB standard `1 - bias e` to allow `e = 1`. .Number (pf.sign.toSign * (0 + pf.sig.toNat / 2 ^ s) * 2 ^ (-(bias e - 1 : Nat) : Int)) + + @[simp] theorem toExtRat'_eq_zero_of_isZero (pf : PackedFloat e s) (hp : pf.isZero) : pf.toExtRat' = .Number 0 := by @@ -1577,6 +1590,32 @@ axiom toExtRat'_getZero (sign : Bool) (hs : 0 < s := by grind) : -- simp [this] -- grind +/-- +Case splitting on the different values a packed float +can have: it can be nan, infinity, zero, or a nonzero normal/subnormal.+ +-/ +@[elab_as_elim] +theorem classification {P : PackedFloat e s → Prop} + (x : PackedFloat e s) + (nanCase : ∀ (n : PackedFloat e s), n.isNaN → P n) + (infCase : ∀ sign, P (PackedFloat.getInfinity e s sign)) + (zeroCase : ∀ sign, P (PackedFloat.getZero e s sign)) + (numCase : ∀ (n : PackedFloat e s), n.isNormOrNonzeroSubnorm → P n) : + P x := by + have := x.classification_exhaustive + simp at this + by_cases h1 : x.isNaN + · grind + · by_cases h2 : x.isInfinite + · grind + · by_cases h3 : x.isZero + · grind + · by_cases h4 : x.isNonzeroSubnorm + · grind + · by_cases h5 : x.isNorm + · grind + · grind + end PackedFloat diff --git a/Fp/SmtLibSemantics.lean b/Fp/SmtLibSemantics.lean index 291053d..32aa4e0 100644 --- a/Fp/SmtLibSemantics.lean +++ b/Fp/SmtLibSemantics.lean @@ -3,6 +3,7 @@ import Fp.Rounding import Fp.UnpackedRound import Fp.Utils import Lean +import Fp.Theorems.Packing open Lean @@ -102,7 +103,7 @@ structure RoundableLower (X : Type) (R : Type) where lower : R → X /-- The default embedding of packed floats into the extended rationals. -/ -instance : RoundableEmbed (PackedFloat e s) ExtRat where +instance embedPackedFloatExtRat (e s) : RoundableEmbed (PackedFloat e s) ExtRat where embed (x : PackedFloat e s) : ExtRat := x.toExtRat @@ -114,6 +115,14 @@ structure RoundableAdjunction (X : Type) (R : Type) extends RoundableUpper X R where +/-- +This is the main property of a lawful rounding adjunction, which states that the lower and upper approximants +are correct with respect to the embedding. Specifically, `lower` computes the greatest lower bound and `upper` computes the least upper bound of the embedding of `X` into `R`. +-/ +class LawfulRoundableAdjunction [LE X] [ExtendedNumber R] (adj : RoundableAdjunction X R) where + adjunctionLower : ∀ (r : R) (x : X), adj.embed x ≤ r ↔ x ≤ adj.lower r + adjunctionUpper : ∀ (r : R) (x : X), r ≤ adj.embed x ↔ adj.upper r ≤ x + /-- Check if the given rational `r` is *strictly in* the lower half of the interval `(embed (lower r), embed (upper r))`. -/ structure RoundableLowerHalf (X : Type) (R : Type) where @@ -283,26 +292,43 @@ for better computational properties. We will show later that the `vlower` and `vupper` defined this way agree with the galois adjunction expected. -/ -noncomputable def smtLibV [Inhabited X] [ExtendedNumber R] [RoundableEmbed X R] : +noncomputable def smtLibV [Inhabited X] [ExtendedNumber R] (embed : RoundableEmbed X R) : RoundableAdjunction X R where embed := RoundableEmbed.embed lower := smtLibLower.lower upper := smtLibUpper.upper +instance : LawfulRoundableAdjunction (smtLibV (embedPackedFloatExtRat e s)) where + adjunctionLower := by + intros r p + simp only [← PackedFloat.le_def, ← ExtRat.le_def] + simp [RoundableEmbed.embed] + rw [PackedFloat.toExtRat'] + induction p using PackedFloat.classification + case nanCase n hn => + simp [hn] + sorry + case zeroCase => + simp + sorry + case infCase sign => sorry + case numCase n hnum => sorry + adjunctionUpper := sorry + @[simp] theorem smtLibV_embed_eq [Inhabited X] - [ExtendedNumber R] [RoundableEmbed X R] - : (smtLibV (X := X) (R := R)).embed = RoundableEmbed.embed := rfl + [ExtendedNumber R] (embed : RoundableEmbed X R) + : (smtLibV embed).embed = RoundableEmbed.embed := rfl @[simp] theorem smtLibV_lower_eq [Inhabited X] - [ExtendedNumber R] [RoundableEmbed X R] - : (smtLibV (X := X) (R := R)).lower = smtLibLower.lower := rfl + [ExtendedNumber R] (embed : RoundableEmbed X R) + : (smtLibV embed).lower = smtLibLower.lower := rfl @[simp] theorem smtLibV_upper_eq [Inhabited X] - [ExtendedNumber R] [RoundableEmbed X R] - : (smtLibV (X := X) (R := R)).upper = smtLibUpper.upper := rfl + [ExtendedNumber R] (embed : RoundableEmbed X R) + : (smtLibV embed).upper = smtLibUpper.upper := rfl /-- The SMT-Lib definition of the rounding methods for any choice of rounding adjunction 'v'. diff --git a/Fp/Theorems/SmtLibSemanticsQ.lean b/Fp/Theorems/SmtLibSemanticsQ.lean index be8648b..db8fd6f 100644 --- a/Fp/Theorems/SmtLibSemanticsQ.lean +++ b/Fp/Theorems/SmtLibSemanticsQ.lean @@ -14,7 +14,9 @@ into a packed float of 'eout, sout' according to the SMT-Lib semantics. Our proofs will be against this definition. -/ noncomputable abbrev smtLibRoundMethodQ (eout sout : Nat) : SmtLibSemantics.RoundMethod (PackedFloat eout sout) ExtRat := - SmtLibSemantics.smtLibRoundMethod eout sout (R := ExtRat) (SmtLibSemantics.smtLibV) (SmtLibSemantics.smtLibV) + SmtLibSemantics.smtLibRoundMethod eout sout + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1))) end SmtLibSemanticsQ diff --git a/Fp/Theorems/UnpackedRound.lean b/Fp/Theorems/UnpackedRound.lean index 204e5b8..262984e 100644 --- a/Fp/Theorems/UnpackedRound.lean +++ b/Fp/Theorems/UnpackedRound.lean @@ -6,12 +6,14 @@ import Fp.Theorems.Packing namespace Fp +open SmtLibSemantics @[simp] theorem roundQ_eq (eout sout : Nat) (rm : RoundingMode) (sign : Bool) (r : ExtRat): (Fp.SmtLibSemanticsQ.smtLibRoundMethodQ eout sout).round rm sign r = (SmtLibSemantics.smtLibRoundMethod eout sout - SmtLibSemantics.smtLibV SmtLibSemantics.smtLibV).round rm sign + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1)))).round rm sign r := rfl set_option warn.sorry false in @@ -26,32 +28,42 @@ theorem upper_NaN_eq_PackedFloat_mkNaN : @[simp] theorem roundRNA_mkNaN (eout sout : Nat) (sign : Bool) : - (SmtLibSemantics.smtLibRoundMethod eout sout SmtLibSemantics.smtLibV SmtLibSemantics.smtLibV).roundRNA sign + (SmtLibSemantics.smtLibRoundMethod eout sout + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1)))).roundRNA sign (ExtRat.NaN) = PackedFloat.mkNaN := by simp [SmtLibSemantics.RoundMethod.roundRNA] @[simp] theorem roundRNE_mkNaN (eout sout : Nat) (sign : Bool) : - (SmtLibSemantics.smtLibRoundMethod eout sout SmtLibSemantics.smtLibV SmtLibSemantics.smtLibV).roundRNE sign + (SmtLibSemantics.smtLibRoundMethod eout sout + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1)))).roundRNE sign (ExtRat.NaN) = PackedFloat.mkNaN := by simp [SmtLibSemantics.RoundMethod.roundRNE] @[simp] theorem roundRTP_mkNaN (eout sout : Nat) (sign : Bool) : - (SmtLibSemantics.smtLibRoundMethod eout sout SmtLibSemantics.smtLibV SmtLibSemantics.smtLibV).roundRTP sign + (SmtLibSemantics.smtLibRoundMethod eout sout + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1)))).roundRTP sign (ExtRat.NaN) = PackedFloat.mkNaN := by simp [SmtLibSemantics.RoundMethod.roundRTP] @[simp] theorem roundRTN_mkNaN (eout sout : Nat) (sign : Bool) : - (SmtLibSemantics.smtLibRoundMethod eout sout SmtLibSemantics.smtLibV SmtLibSemantics.smtLibV).roundRTN sign + (SmtLibSemantics.smtLibRoundMethod eout sout + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1)))).roundRTN sign (ExtRat.NaN) = PackedFloat.mkNaN := by simp [SmtLibSemantics.RoundMethod.roundRTN] rcases sign <;> simp @[simp] theorem rountRTZ_mkNaN (eout sout : Nat) (sign : Bool) : - (SmtLibSemantics.smtLibRoundMethod eout sout SmtLibSemantics.smtLibV SmtLibSemantics.smtLibV).roundRTZ sign + (SmtLibSemantics.smtLibRoundMethod eout sout + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1)))).roundRTZ sign (ExtRat.NaN) = PackedFloat.mkNaN := by simp [SmtLibSemantics.RoundMethod.roundRTZ] rcases sign <;> simp @@ -59,7 +71,9 @@ theorem rountRTZ_mkNaN (eout sout : Nat) (sign : Bool) : @[simp] theorem round_eq_mkNaN_of_NaN {sign} {eout sout : Nat} {rm : RoundingMode} : - (SmtLibSemantics.smtLibRoundMethod eout sout SmtLibSemantics.smtLibV SmtLibSemantics.smtLibV).round + (SmtLibSemantics.smtLibRoundMethod eout sout + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1)))).round rm sign ExtRat.NaN = PackedFloat.mkNaN := by rcases rm · simp @@ -71,7 +85,10 @@ theorem round_eq_mkNaN_of_NaN {sign} {eout sout : Nat} {rm : RoundingMode} : set_option warn.sorry false in @[simp] theorem round_eq_mkZero_of_mkZero {zeroSign : Bool} {eout sout : Nat} {rm : RoundingMode} : - (SmtLibSemantics.smtLibRoundMethod eout sout SmtLibSemantics.smtLibV SmtLibSemantics.smtLibV).round + (SmtLibSemantics.smtLibRoundMethod eout sout + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1))) + ).round rm zeroSign (ExtRat.Number 0) = PackedFloat.getZero eout sout zeroSign := by rcases rm <;> sorry @@ -80,11 +97,11 @@ theorem round_eq_mkZero_of_mkZero {zeroSign : Bool} {eout sout : Nat} {rm : Roun -/ structure ApproximatesUptoRounding (uf : UnpackedFloat ein sin) (er : ExtRat) (eout sout : Nat) : Prop where /-- we have at least 2 bits more, of guard and sticky. -/ - hSigGe : sin + 2 ≥ sout + hSigGe : sin + 2 ≥ sout /-- we have at least as much exponent range. -/ hExpGe : ein ≥ eout -- we have at least as much exponent range /-- rational values have (sout + 1) bits of precision. -/ - hApproxUptoGuard : ∀ (r : Rat), .Number r = er → (uf.toRat - r).abs < (2 : Rat) ^ (-(sout + 1 : Int)) + hApproxUptoGuard : ∀ (r : Rat), .Number r = er → (uf.toRat - r).abs < (2 : Rat) ^ (-(sout + 1 : Int)) /-- the sticky bit is zero iff the number truncated upto the guard bit equals -/ hStickyBitCorrect : ∀ (r : Rat), .Number r = er → ((uf.sig.extractMsb' (sout + 1) (sin - (sout + 1)) ≠ 0) = decide (r = uf.toRat)) @@ -92,7 +109,10 @@ set_option warn.sorry false in theorem roundQ_Number_eq_round (er : ExtRat) (uf : UnpackedFloat ein sin) (hruf : ApproximatesUptoRounding uf er eout sout) (rm : RoundingMode) (sign : Bool) : - (SmtLibSemantics.smtLibRoundMethod eout sout SmtLibSemantics.smtLibV SmtLibSemantics.smtLibV).round rm sign + (SmtLibSemantics.smtLibRoundMethod eout sout + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1))) + ).round rm sign er = (UnpackedFloat.round uf rm).pack := by sorry @@ -111,42 +131,20 @@ theorem roundQ_Number_eq_round @[simp] theorem roundQ_eq_round_of_Infinity {zeroSign infSign : Bool} {e s : Nat} {rm : RoundingMode} : - (SmtLibSemantics.smtLibRoundMethod e s SmtLibSemantics.smtLibV SmtLibSemantics.smtLibV).round rm zeroSign + (SmtLibSemantics.smtLibRoundMethod e s + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat e s)) + (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat e (s + 1))) + ).round rm zeroSign (ExtRat.Infinity infSign) = PackedFloat.getInfinity e s infSign := by sorry -/-- -Case splitting on the different values a packed float -can have: it can be nan, infinity, zero, or a nonzero normal/subnormal.+ --/ -@[elab_as_elim] -theorem PackedFloat.kindCasesNaNInfZeroNum {P : PackedFloat e s → Prop} - (x : PackedFloat e s) - (nanCase : ∀ (n : PackedFloat e s), n.isNaN → P n) - (infCase : ∀ sign, P (PackedFloat.getInfinity e s sign)) - (zeroCase : ∀ sign, P (PackedFloat.getZero e s sign)) - (numCase : ∀ (n : PackedFloat e s), n.isNormOrNonzeroSubnorm → P n) : - P x := by - have := x.classification_exhaustive - simp at this - by_cases h1 : x.isNaN - · grind - · by_cases h2 : x.isInfinite - · grind - · by_cases h3 : x.isZero - · grind - · by_cases h4 : x.isNonzeroSubnorm - · grind - · by_cases h5 : x.isNorm - · grind - · grind @[grind <=] theorem PackedFloat.eq_of_unpack_eq_unpack_of_isInfinity {x y : PackedFloat e s} (hs : 0 < s) (he : 0 < e) (hx : x.isInfinite) (hy : y.isInfinite) (h : x.unpack = y.unpack) : x = y := by - cases x using PackedFloat.kindCasesNaNInfZeroNum <;> try grind + cases x using PackedFloat.classification <;> try grind /-- Purely arithmetic fact that needs to be proven, @@ -173,7 +171,7 @@ theorem mul_eq_mul {ein sin : Nat} (hsin : 0 < sin) (he : 0 < ein) rm a b = PackedFloat.mul rm a b := by simp [SmtLibSemantics.SmtLibFunctions.mul] rw [PackedFloat.mul, EUnpackedFloat.mul] - cases a using PackedFloat.kindCasesNaNInfZeroNum + cases a using PackedFloat.classification case nanCase hnan => simp [hnan] rw [round_eq_mkNaN_of_NaN] @@ -182,7 +180,7 @@ theorem mul_eq_mul {ein sin : Nat} (hsin : 0 < sin) (he : 0 < ein) rw [← ExtRat.mul_def] unfold ExtRat.mul simp - cases b using PackedFloat.kindCasesNaNInfZeroNum + cases b using PackedFloat.classification case nanCase hb => simp [hb] -- | why does this not apply automatically? @@ -213,7 +211,7 @@ theorem mul_eq_mul {ein sin : Nat} (hsin : 0 < sin) (he : 0 < ein) sorry case zeroCase sign => simp [he] - cases b using PackedFloat.kindCasesNaNInfZeroNum + cases b using PackedFloat.classification case nanCase hb => simp [hb] rw [round_eq_mkNaN_of_NaN] @@ -240,7 +238,7 @@ theorem mul_eq_mul {ein sin : Nat} (hsin : 0 < sin) (he : 0 < ein) rw [PackedFloat.unpack_eq_mkNumber_of_isNormOrNonzeroSubnorm ha] rw [PackedFloat.toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm ha] -- interesting case, when a is a number. - cases b using PackedFloat.kindCasesNaNInfZeroNum + cases b using PackedFloat.classification case nanCase hb => simp [hb] rw [round_eq_mkNaN_of_NaN] From c0f3c436e735a6ef6cac8810fb958815a8afbfe5 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 9 Feb 2026 12:17:50 +0000 Subject: [PATCH 04/44] chore: refactor rounding to make it easier to write theorems down --- Fp/SmtLibSemantics.lean | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Fp/SmtLibSemantics.lean b/Fp/SmtLibSemantics.lean index 32aa4e0..477dcde 100644 --- a/Fp/SmtLibSemantics.lean +++ b/Fp/SmtLibSemantics.lean @@ -301,8 +301,8 @@ noncomputable def smtLibV [Inhabited X] [ExtendedNumber R] (embed : RoundableEmb instance : LawfulRoundableAdjunction (smtLibV (embedPackedFloatExtRat e s)) where adjunctionLower := by intros r p - simp only [← PackedFloat.le_def, ← ExtRat.le_def] - simp [RoundableEmbed.embed] + simp only [instExtendedRat, ← ExtRat.le_def, ← PackedFloat.le_def, + PackedFloat.toExtRat_eq_toExtRat', Bool.coe_iff_coe] rw [PackedFloat.toExtRat'] induction p using PackedFloat.classification case nanCase n hn => From 0f4e9d286db2253766e6d1bf73b0a304f19a5a91 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 9 Feb 2026 12:23:57 +0000 Subject: [PATCH 05/44] chore: cleanup smtlib in real theory --- fp-real-theory/FpRealTheory/Smtlib.lean | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fp-real-theory/FpRealTheory/Smtlib.lean b/fp-real-theory/FpRealTheory/Smtlib.lean index 647e90c..532801c 100644 --- a/fp-real-theory/FpRealTheory/Smtlib.lean +++ b/fp-real-theory/FpRealTheory/Smtlib.lean @@ -149,7 +149,7 @@ noncomputable instance : ExtendedNumber ExtReal where isNaN r := r = .NaN smtLibEq r1 r2 := r1.eq r2 -instance : RoundableEmbed (PackedFloat e s) ExtReal where +instance embedPackedFloatExtReal (e s : Nat) : RoundableEmbed (PackedFloat e s) ExtReal where embed := fun pf => match pf.toExtRat with | .NaN => .NaN @@ -159,7 +159,9 @@ instance : RoundableEmbed (PackedFloat e s) ExtReal where end ExtReal noncomputable def smtLibRealRounder : RoundMethod (PackedFloat e s) ExtReal := - smtLibRoundMethod e s smtLibV smtLibV + smtLibRoundMethod e s + (smtLibV (ExtReal.embedPackedFloatExtReal e s)) + (smtLibV (ExtReal.embedPackedFloatExtReal e (s + 1))) namespace RealSemantics open Classical From a525bf39cd01b3345882e230d8a60c03fde3299c Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 9 Feb 2026 15:32:13 +0000 Subject: [PATCH 06/44] chore: we have two functions for creating a nan, unify them to have a coherent API --- Fp/Basic.lean | 29 +++++++++ Fp/SmtLibSemantics.lean | 83 +++++++++++++++++++++++-- fp-real-theory/FpRealTheory/Smtlib.lean | 2 +- 3 files changed, 109 insertions(+), 5 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index cdb91d5..cd8aa5c 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1402,6 +1402,26 @@ theorem mul_comm (x y : ExtRat) : x * y = y * x := by end InfinityBehaviour +theorem le_refl (x : ExtRat) : x ≤ x := by + rw [← ExtRat.le_def] + unfold ExtRat.le + grind + +theorem le_trans {x y z : ExtRat} (hxy : x ≤ y) (hyz : y ≤ z) : x ≤ z := by + rw [← ExtRat.le_def] at hxy hyz ⊢ + unfold ExtRat.le at hxy hyz ⊢ + grind + +theorem le_antisymm {x y : ExtRat} (hxy : x ≤ y) (hyx : y ≤ x) : x = y := by + rw [← ExtRat.le_def] at hxy hyx + unfold ExtRat.le at hxy hyx + grind + +instance : Std.IsPartialOrder ExtRat where + le_refl := le_refl + le_trans := by grind [le_trans] + le_antisymm := by grind [le_antisymm] + def isNaN (r : ExtRat) : Bool := r = .NaN @@ -1427,6 +1447,7 @@ def bias (e : Nat) : Nat := namespace PackedFloat +-- delete mkNaN in favour of getNaN def mkNaN (sign := false) (sig := BitVec.intMin s) : PackedFloat e s := { sign, ex := BitVec.allOnes e, sig } @@ -1582,6 +1603,14 @@ theorem toExtRat'_mkInfinity (sign : Bool) (hs : 0 < s := by grind) : grind simp [hs] +@[simp] +theorem toExtRat'_mkNaN (sign : Bool) (hs : 0 < s := by grind) : + (PackedFloat.mkNaN : PackedFloat e s).toExtRat' = .NaN := by + have : (PackedFloat.mkNaN : PackedFloat e s).isNaN = true := by + sorry + simp [PackedFloat.toExtRat'] + simp [this] + @[simp] axiom toExtRat'_getZero (sign : Bool) (hs : 0 < s := by grind) : (PackedFloat.getZero e s sign).toExtRat' = .Number 0 -- := by diff --git a/Fp/SmtLibSemantics.lean b/Fp/SmtLibSemantics.lean index 477dcde..1755afe 100644 --- a/Fp/SmtLibSemantics.lean +++ b/Fp/SmtLibSemantics.lean @@ -103,7 +103,7 @@ structure RoundableLower (X : Type) (R : Type) where lower : R → X /-- The default embedding of packed floats into the extended rationals. -/ -instance embedPackedFloatExtRat (e s) : RoundableEmbed (PackedFloat e s) ExtRat where +instance embedPackedFloatExtRat (e s) : RoundableEmbed (PackedFloat e s) ExtRat where embed (x : PackedFloat e s) : ExtRat := x.toExtRat @@ -263,6 +263,19 @@ end Round def IsLawfulLower [ExtendedNumber R] [RE : RoundableEmbed X R] (r : R) (lower : X) : Prop := RE.embed lower ≤ r ∧ (∀ (lower' : X), RE.embed lower' ≤ r → RE.embed lower' ≤ RE.embed lower) +@[grind →] +theorem IsLawfulLower.functional [ExtendedNumber R] [RE : RoundableEmbed X R] [Std.IsPartialOrder R] (r : R) (lower1 lower2 : X) : + IsLawfulLower r lower1 → IsLawfulLower r lower2 → RE.embed lower1 = RE.embed lower2 := by + intro hl1 hl2 + cases hl1 with + | intro hle1 hglb1 => + cases hl2 with + | intro hle2 hglb2 => + have hle12 : RE.embed lower1 ≤ RE.embed lower2 := hglb2 lower1 hle1 + have hle21 : RE.embed lower2 ≤ RE.embed lower1 := hglb1 lower2 hle2 + grind + + open Classical in noncomputable def smtLibLower [Inhabited X] [ExtendedNumber R] [RoundableEmbed X R] : RoundableLower X R where lower (r : R) : X := @@ -271,10 +284,40 @@ noncomputable def smtLibLower [Inhabited X] [ExtendedNumber R] [RoundableEmbed X else default +theorem embed_smtLibLower_eq_of_IsLawfulLower [Inhabited X] [ExtendedNumber R] [instEmbed : RoundableEmbed X R] [Std.IsPartialOrder R] (r : R) (lower : X) : + IsLawfulLower r lower → instEmbed.embed (smtLibLower.lower r) = instEmbed.embed lower := by + intro hl + simp [smtLibLower] + split + case isTrue h => + obtain ⟨x, hlx⟩ := h + have : instEmbed.embed x = instEmbed.embed lower := by + apply IsLawfulLower.functional r x lower hlx hl + grind + case isFalse h => + simp at h + grind + + + +-- TODO: need to know that IsLawfulLower is functional. + /-- 'upper' is a valid least upper bound for 'r'. -/ def IsLawfulUpper [ExtendedNumber R] [RE : RoundableEmbed X R] (r : R) (upper : X) : Prop := r ≤ RE.embed upper ∧ (∀ (upper' : X), r ≤ RE.embed upper' → RE.embed upper ≤ RE.embed upper') +@[grind →] +theorem IsLawfulUpper.functional [ExtendedNumber R] [RE : RoundableEmbed X R] [Std.IsPartialOrder R] (r : R) (upper1 upper2 : X) : + IsLawfulUpper r upper1 → IsLawfulUpper r upper2 → RE.embed upper1 = RE.embed upper2 := by + intro hu1 hu2 + cases hu1 with + | intro hle1 lub1 => + cases hu2 with + | intro hle2 lub2 => + have hle12 : RE.embed upper2 ≤ RE.embed upper1 := lub2 upper1 hle1 + have hle21 : RE.embed upper1 ≤ RE.embed upper2 := lub1 upper2 hle2 + grind + open Classical in noncomputable def smtLibUpper {X R} [Inhabited X] [ExtendedNumber R] [RoundableEmbed X R] : RoundableUpper X R where upper (r : R) : X := @@ -298,16 +341,48 @@ noncomputable def smtLibV [Inhabited X] [ExtendedNumber R] (embed : RoundableEmb lower := smtLibLower.lower upper := smtLibUpper.upper +/-- TODO: is this the right way to deal with this? -/ +theorem smtLiV.embed_toRoundableEmbed_eq [Inhabited X] [ExtendedNumber R] (embed : RoundableEmbed X R) : + (smtLibV embed).embed = embed.embed := rfl + +/-- TODO: is this the right way to deal with this? -/ +@[simp] +theorem smtLiV.lower_toRoundableEmbed_eq [Inhabited X] [ExtendedNumber R] (embed : RoundableEmbed X R) : + (smtLibV embed).lower = smtLibLower.lower := rfl + +@[simp] +theorem smtLiV.upper_toRoundableEmbed_eq [Inhabited X] [ExtendedNumber R] (embed : RoundableEmbed X R) : + (smtLibV embed).upper = smtLibUpper.upper := rfl + +@[simp] -- TODO: what should be the simp nf? +theorem RoundableEmbed_embedPackedFloatExtRat_eq_smtLibV_embed: + RoundableEmbed.embed (self := embedPackedFloatExtRat e s) = PackedFloat.toExtRat := rfl + + +@[simp] +theorem toExtRat'_smtLibLower_eq_toExtRat'_of_IsLawfulLower (r : ExtRat) (lower : PackedFloat e s) : + IsLawfulLower r lower → (smtLibLower.lower r : PackedFloat e s).toExtRat' = PackedFloat.toExtRat' lower := by + intros h + have := embed_smtLibLower_eq_of_IsLawfulLower r lower h + simp at this + assumption + + instance : LawfulRoundableAdjunction (smtLibV (embedPackedFloatExtRat e s)) where adjunctionLower := by intros r p - simp only [instExtendedRat, ← ExtRat.le_def, ← PackedFloat.le_def, - PackedFloat.toExtRat_eq_toExtRat', Bool.coe_iff_coe] + simp [instExtendedRat, ← PackedFloat.le_def, + PackedFloat.toExtRat_eq_toExtRat'] rw [PackedFloat.toExtRat'] induction p using PackedFloat.classification case nanCase n hn => simp [hn] - sorry + constructor + · intros hr + simp at hr + -- TODO: extract this out into a separate boi. + sorry + · sorry case zeroCase => simp sorry diff --git a/fp-real-theory/FpRealTheory/Smtlib.lean b/fp-real-theory/FpRealTheory/Smtlib.lean index 532801c..d6374db 100644 --- a/fp-real-theory/FpRealTheory/Smtlib.lean +++ b/fp-real-theory/FpRealTheory/Smtlib.lean @@ -159,7 +159,7 @@ instance embedPackedFloatExtReal (e s : Nat) : RoundableEmbed (PackedFloat e s) end ExtReal noncomputable def smtLibRealRounder : RoundMethod (PackedFloat e s) ExtReal := - smtLibRoundMethod e s + smtLibRoundMethod e s (smtLibV (ExtReal.embedPackedFloatExtReal e s)) (smtLibV (ExtReal.embedPackedFloatExtReal e (s + 1))) From 4f35a7ccd115a2648e80f2e5fcaca3b90cdbe3e8 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 9 Feb 2026 15:46:41 +0000 Subject: [PATCH 07/44] chore: more theory --- Fp/Basic.lean | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index cd8aa5c..fc0c5a1 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1090,6 +1090,7 @@ instance : LE ExtRat where le a b := le a b + @[simp] theorem le_def {a b : ExtRat} : a.le b = (a ≤ b) := rfl @@ -1447,7 +1448,6 @@ def bias (e : Nat) : Nat := namespace PackedFloat --- delete mkNaN in favour of getNaN def mkNaN (sign := false) (sig := BitVec.intMin s) : PackedFloat e s := { sign, ex := BitVec.allOnes e, sig } @@ -1602,14 +1602,17 @@ theorem toExtRat'_mkInfinity (sign : Bool) (hs : 0 < s := by grind) : have : (PackedFloat.getInfinity e s sign).isInfinite = true := by grind simp [hs] +@[simp] + +theorem isNaN_mkNaN : (PackedFloat.mkNaN : PackedFloat e s).isNaN = true := by + simp [mkNaN, isNaN] + grind @[simp] -theorem toExtRat'_mkNaN (sign : Bool) (hs : 0 < s := by grind) : +theorem toExtRat'_mkNaN : (PackedFloat.mkNaN : PackedFloat e s).toExtRat' = .NaN := by - have : (PackedFloat.mkNaN : PackedFloat e s).isNaN = true := by - sorry - simp [PackedFloat.toExtRat'] - simp [this] + rw [toExtRat'] + simp @[simp] axiom toExtRat'_getZero (sign : Bool) (hs : 0 < s := by grind) : From 5214aaa55621de97dcfd1d27fa613a5d4ea61351 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Thu, 12 Feb 2026 11:14:35 +0000 Subject: [PATCH 08/44] chore: more theory --- Fp/Basic.lean | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index fc0c5a1..9be5369 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1596,14 +1596,14 @@ theorem toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm {pf : PackedFloat e s} (hp simp [toExtRat', hnan, hinf, hzero, toNumberRat] grind -@[simp] -theorem toExtRat'_mkInfinity (sign : Bool) (hs : 0 < s := by grind) : +@[simp, grind! .] +theorem toExtRat'_getInfinity (sign : Bool) (hs : 0 < s := by grind) : (PackedFloat.getInfinity e s sign).toExtRat' = .Infinity sign := by have : (PackedFloat.getInfinity e s sign).isInfinite = true := by grind simp [hs] -@[simp] +@[simp, grind! .] theorem isNaN_mkNaN : (PackedFloat.mkNaN : PackedFloat e s).isNaN = true := by simp [mkNaN, isNaN] grind @@ -1614,7 +1614,7 @@ theorem toExtRat'_mkNaN : rw [toExtRat'] simp -@[simp] +@[simp, grind! .] axiom toExtRat'_getZero (sign : Bool) (hs : 0 < s := by grind) : (PackedFloat.getZero e s sign).toExtRat' = .Number 0 -- := by -- have : (PackedFloat.getZero e s sign).isZero = true := by From cb5b4b70410aebb6479a12a13293964bf5006d34 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Thu, 12 Feb 2026 12:25:27 +0000 Subject: [PATCH 09/44] chore: add correct <= defn --- Fp/Basic.lean | 20 +++++-- Fp/SmtLibSemantics.lean | 115 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 123 insertions(+), 12 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 9be5369..43fc817 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1482,15 +1482,27 @@ def toExtRat (pf : PackedFloat e s) : ExtRat := pf.toExtDyadic.toExtRat +/-- +'An Automatable Formal Semantics for IEEE-754 Floating-Point Arithmetic', +definition from the model of floating point. +-/ +def le (x y : PackedFloat e s) : Prop := + (x.sign = true ∧ y.sign = false) ∨ + (x.sign = false ∧ y.sign = false ∧ x.ex.toInt < y.ex.toInt) ∨ + (x.sign = false ∧ y.sign = false ∧ x.ex = y.ex ∧ x.sig.toNat ≤ y.sig.toNat) ∨ + (x.sign = true ∧ y.sign = true ∧ y.ex.toInt < x.ex.toInt) ∨ + (x.sign = true ∧ y.sign = true ∧ x.ex = y.ex ∧ y.sig.toNat ≤ x.sig.toNat) + + instance : LE (PackedFloat exWidth sigWidth) where - le x y := x.toExtRat ≤ y.toExtRat + le x y := le x y @[simp] -theorem le_def (x y : PackedFloat e s) : (x.toExtRat ≤ y.toExtRat) = (x ≤ y) := rfl +theorem le_def (x y : PackedFloat e s) : + x.le y = (x ≤ y) := rfl instance {x y : PackedFloat e s} : Decidable (x ≤ y) := by - simp only [← PackedFloat.le_def] - simp only [← ExtRat.le_def] + simp only [← PackedFloat.le_def, PackedFloat.le] infer_instance def toExtRat' (pf : PackedFloat e s) : ExtRat := diff --git a/Fp/SmtLibSemantics.lean b/Fp/SmtLibSemantics.lean index 1755afe..57d4a7f 100644 --- a/Fp/SmtLibSemantics.lean +++ b/Fp/SmtLibSemantics.lean @@ -275,6 +275,14 @@ theorem IsLawfulLower.functional [ExtendedNumber R] [RE : RoundableEmbed X R] [S have hle21 : RE.embed lower2 ≤ RE.embed lower1 := hglb1 lower2 hle2 grind +-- theorem IsLawfulLower_always_exists {X R} [Inhabited X] [ExtendedNumber R] [RE : RoundableEmbed X R] (r : R) : +-- ∃ (x : X), IsLawfulLower r x := by +-- apply Classical.byContradiction +-- intros h +-- simp at h +-- simp [IsLawfulLower] at h + + open Classical in noncomputable def smtLibLower [Inhabited X] [ExtendedNumber R] [RoundableEmbed X R] : RoundableLower X R where @@ -284,6 +292,7 @@ noncomputable def smtLibLower [Inhabited X] [ExtendedNumber R] [RoundableEmbed X else default +@[grind .] theorem embed_smtLibLower_eq_of_IsLawfulLower [Inhabited X] [ExtendedNumber R] [instEmbed : RoundableEmbed X R] [Std.IsPartialOrder R] (r : R) (lower : X) : IsLawfulLower r lower → instEmbed.embed (smtLibLower.lower r) = instEmbed.embed lower := by intro hl @@ -346,20 +355,20 @@ theorem smtLiV.embed_toRoundableEmbed_eq [Inhabited X] [ExtendedNumber R] (embed (smtLibV embed).embed = embed.embed := rfl /-- TODO: is this the right way to deal with this? -/ -@[simp] +@[simp, grind =] theorem smtLiV.lower_toRoundableEmbed_eq [Inhabited X] [ExtendedNumber R] (embed : RoundableEmbed X R) : (smtLibV embed).lower = smtLibLower.lower := rfl -@[simp] +@[simp, grind =] theorem smtLiV.upper_toRoundableEmbed_eq [Inhabited X] [ExtendedNumber R] (embed : RoundableEmbed X R) : (smtLibV embed).upper = smtLibUpper.upper := rfl -@[simp] -- TODO: what should be the simp nf? +@[simp, grind =] -- TODO: what should be the simp nf? theorem RoundableEmbed_embedPackedFloatExtRat_eq_smtLibV_embed: RoundableEmbed.embed (self := embedPackedFloatExtRat e s) = PackedFloat.toExtRat := rfl -@[simp] +@[simp, grind .] theorem toExtRat'_smtLibLower_eq_toExtRat'_of_IsLawfulLower (r : ExtRat) (lower : PackedFloat e s) : IsLawfulLower r lower → (smtLibLower.lower r : PackedFloat e s).toExtRat' = PackedFloat.toExtRat' lower := by intros h @@ -367,8 +376,90 @@ theorem toExtRat'_smtLibLower_eq_toExtRat'_of_IsLawfulLower (r : ExtRat) (lower simp at this assumption +-- theorem isNaN_lower_eq + +@[simp, grind .] +theorem IsLawfulLower_mkInfinity (hs : 0 < s) {sign : Bool} : IsLawfulLower (ExtRat.Infinity sign) (PackedFloat.getInfinity e s sign) := by + constructor + · simp + -- grind + rw [PackedFloat.toExtRat'_getInfinity] + grind + · intros lower hle + simp at hle + simp + rw [PackedFloat.toExtRat'_getInfinity] + grind + +@[simp, grind .] +theorem IsLawfulUpper_mkInfinity (hs : 0 < s) {sign : Bool} : IsLawfulUpper (ExtRat.Infinity sign) (PackedFloat.getInfinity e s sign) := by + constructor + · simp + -- grind + rw [PackedFloat.toExtRat'_getInfinity] + grind + · intros upper hle + simp at hle + simp + rw [PackedFloat.toExtRat'_getInfinity] + grind + +@[simp, grind .] +theorem IsLawfulLower_mkNaN : IsLawfulLower ExtRat.NaN (PackedFloat.mkNaN : PackedFloat e s) := by + constructor + simp + intros lower hLtNaN + simp at hLtNaN + simp [hLtNaN] + +@[simp, grind .] +theorem IsLawfulUpper_mkNaN : IsLawfulUpper ExtRat.NaN (PackedFloat.mkNaN : PackedFloat e s) := by + constructor + simp + intros upper hNaNLe + simp at hNaNLe + simp [hNaNLe] + +@[simp, grind .] +theorem IslawfulUpper_mkNumber (pf : PackedFloat e s): IsLawfulUpper pf.toExtRat' pf := by + constructor + · simp; grind + · simp + +@[simp, grind .] +theorem IsLawfulLower_mkNumber (pf : PackedFloat e s) : IsLawfulLower pf.toExtRat' pf := by + constructor + · simp; grind + · simp + +@[simp] +theorem isNaN_lower_NaN : (smtLibLower.lower ExtRat.NaN : PackedFloat e s).isNaN = true := sorry + +-- TODO: show that toExtRat is injective everywhere away from zero. + +/-- Lower returns the input at all points except zero. -/ +theorem lower_eq_of_ne_zero {r : ExtRat} {lower : PackedFloat e s} + (h : r = lower.toExtRat) (hr : r ≠ 0) : + (smtLibLower.lower r : PackedFloat e s) = lower := by sorry + + +theorem lower_eq_plus_zero : + (smtLibLower.lower (0 : ExtRat) : PackedFloat e s) = PackedFloat.getZero e s false := sorry + +theorem upper_eq_minus_zero : + (smtLibLower.lower (0 : ExtRat) : PackedFloat e s) = PackedFloat.getZero e s true := sorry + +/-- Upper returns the input at all points except zero. -/ +theorem upper_eq_of_ne_zero {r : ExtRat} {upper : PackedFloat e s} (h : r = upper.toExtRat) (hr : r ≠ 0) : + (smtLibUpper.upper r : PackedFloat e s) = upper := by sorry -instance : LawfulRoundableAdjunction (smtLibV (embedPackedFloatExtRat e s)) where + theorem lower_le (r : ExtRat) : + ((smtLibLower.lower r) : PackedFloat e s).toExtRat ≤ r := sorry + +theorem le_upper (r : ExtRat) : + r ≤ ((smtLibUpper.upper r) : PackedFloat e s).toExtRat := sorry + +instance (he : 0 < e) (hs : 0 < s) : LawfulRoundableAdjunction (smtLibV (embedPackedFloatExtRat e s)) where adjunctionLower := by intros r p simp [instExtendedRat, ← PackedFloat.le_def, @@ -381,11 +472,19 @@ instance : LawfulRoundableAdjunction (smtLibV (embedPackedFloatExtRat e s)) wher · intros hr simp at hr -- TODO: extract this out into a separate boi. + subst hr + apply PackedFloat.toExtRat'_eq_NaN_of_isNaN (smtLibLower.lower ExtRat.NaN) sorry · sorry - case zeroCase => - simp - sorry + case zeroCase sign => + simp [he, show (e = 0) = False by grind, show (s = 0) = False by grind] + constructor + · intros h + sorry + · intros h + simp at h + sorry + case infCase sign => sorry case numCase n hnum => sorry adjunctionUpper := sorry From 5b9cd8593ed3592167d863971ab9fc657213b131 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Thu, 12 Feb 2026 12:29:11 +0000 Subject: [PATCH 10/44] chore: add more --- Fp/Basic.lean | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 43fc817..a11cb6d 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1494,6 +1494,9 @@ def le (x y : PackedFloat e s) : Prop := (x.sign = true ∧ y.sign = true ∧ x.ex = y.ex ∧ y.sig.toNat ≤ x.sig.toNat) +instance {x y : PackedFloat e s} : Decidable (le x y) := by + simp [le]; infer_instance + instance : LE (PackedFloat exWidth sigWidth) where le x y := le x y @@ -1501,8 +1504,12 @@ instance : LE (PackedFloat exWidth sigWidth) where theorem le_def (x y : PackedFloat e s) : x.le y = (x ≤ y) := rfl +@[simp] +theorem minus_zero_le_plus_zero : PackedFloat.getZero e s true ≤ PackedFloat.getZero e s false := by + simp [getZero, ← PackedFloat.le_def, PackedFloat.le] + instance {x y : PackedFloat e s} : Decidable (x ≤ y) := by - simp only [← PackedFloat.le_def, PackedFloat.le] + simp only [← PackedFloat.le_def] infer_instance def toExtRat' (pf : PackedFloat e s) : ExtRat := From 1ea628e30e129f09e754caaa3f4e5054c2316904 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Thu, 12 Feb 2026 14:09:42 +0000 Subject: [PATCH 11/44] chore: add the lemmas needed about packed float ordering --- Foo.lean | 39 ++++++++++++++++++ Fp/Basic.lean | 107 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 Foo.lean diff --git a/Foo.lean b/Foo.lean new file mode 100644 index 0000000..23c48c7 --- /dev/null +++ b/Foo.lean @@ -0,0 +1,39 @@ +theorem concat_truncate_zero_eq_mul_pow2 + {n : Nat} (x : BitVec n) (tz : Nat) (htz : tz ≤ n) : + ((x.truncate (n - tz)) ++ (0#tz) |>.cast (by omega)) = x * (1#n <<< tz) := by + ext i + simp + rw [BitVec.getElem_append] + have : 1#n <<< tz = BitVec.twoPow _ tz := by + ext i + simp + by_cases hi : i < tz <;> simp [hi] <;> omega + rw [this] + rw [← BitVec.shiftLeft_eq_mul_twoPow] + rw [BitVec.getElem_shiftLeft] + by_cases hi : i < tz + · simp [hi] + · simp [hi] + rw [BitVec.getLsbD_eq_getElem] + + +/-- +appending 'tz' trailing zeroes to the truncated 'x' is equal to +multiplying 'x' by 2^tz. +-/ +theorem extractLsb'_append_zero_eq_mul_shiftLeft {n : Nat} + (x : BitVec n) (tz : Nat) (htz : tz ≤ n) : + ((x.extractLsb' 0 (n - tz) ++ (0#tz)).cast (by omega)) + = x * (1#n <<< tz) := by + ext i + simp only [BitVec.getElem_cast] + rw [BitVec.getElem_append] + have : 1#n <<< tz = BitVec.twoPow _ tz := by + ext i + simp only [BitVec.getElem_shiftLeft, BitVec.getElem_one, BitVec.getElem_twoPow] + by_cases hi : i < tz <;> simp [hi] <;> omega + rw [this, ← BitVec.shiftLeft_eq_mul_twoPow, BitVec.getElem_shiftLeft] + by_cases hi : i < tz + · simp [hi] + · simp [hi] + rw [BitVec.getLsbD_eq_getElem] diff --git a/Fp/Basic.lean b/Fp/Basic.lean index a11cb6d..6a69a67 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1448,9 +1448,22 @@ def bias (e : Nat) : Nat := namespace PackedFloat +-- | TODO: delete one of mkNaN or getNaN def mkNaN (sign := false) (sig := BitVec.intMin s) : PackedFloat e s := { sign, ex := BitVec.allOnes e, sig } +@[simp, grind =] +theorem sign_mkNaN (sign : Bool) (sig : BitVec s) : + (mkNaN sign sig : PackedFloat e s).sign = sign := rfl + +@[simp, grind =] +theorem ex_mkNaN (sign : Bool) (sig : BitVec s) : + (mkNaN sign sig : PackedFloat e s).ex = BitVec.allOnes e := rfl + +@[simp] +theorem sig_mkNaN {s e} (sign : Bool) (sig : BitVec s) : + (mkNaN sign sig : PackedFloat e s).sig = sig := rfl + def toExtDyadic (pf : PackedFloat e s) : ExtDyadic := bif pf.isNaN then .NaN @@ -1487,12 +1500,14 @@ def toExtRat (pf : PackedFloat e s) : ExtRat := definition from the model of floating point. -/ def le (x y : PackedFloat e s) : Prop := - (x.sign = true ∧ y.sign = false) ∨ - (x.sign = false ∧ y.sign = false ∧ x.ex.toInt < y.ex.toInt) ∨ - (x.sign = false ∧ y.sign = false ∧ x.ex = y.ex ∧ x.sig.toNat ≤ y.sig.toNat) ∨ - (x.sign = true ∧ y.sign = true ∧ y.ex.toInt < x.ex.toInt) ∨ - (x.sign = true ∧ y.sign = true ∧ x.ex = y.ex ∧ y.sig.toNat ≤ x.sig.toNat) - + (x.isNaN ∧ y.isNaN) ∨ + (¬ x.isNaN ∧ ¬ y.isNaN ∧ + (x.sign = true ∧ y.sign = false) ∨ -- x negative, y positive. + (x.sign = false ∧ y.sign = false ∧ x.ex.toInt < y.ex.toInt) ∨ -- both +ve, x smaller ex. + (x.sign = false ∧ y.sign = false ∧ x.ex = y.ex ∧ x.sig.toNat ≤ y.sig.toNat) ∨ -- both +ve, x smaller sig. + (x.sign = true ∧ y.sign = true ∧ y.ex.toInt < x.ex.toInt) ∨ -- both -ve, y smaller ex. + (x.sign = true ∧ y.sign = true ∧ x.ex = y.ex ∧ y.sig.toNat ≤ x.sig.toNat) + ) instance {x y : PackedFloat e s} : Decidable (le x y) := by simp [le]; infer_instance @@ -1505,8 +1520,17 @@ theorem le_def (x y : PackedFloat e s) : x.le y = (x ≤ y) := rfl @[simp] -theorem minus_zero_le_plus_zero : PackedFloat.getZero e s true ≤ PackedFloat.getZero e s false := by - simp [getZero, ← PackedFloat.le_def, PackedFloat.le] +theorem minus_zero_le_plus_zero {e s} : + (PackedFloat.getZero e s true ≤ PackedFloat.getZero e s false) = + (e = 0 → s = 0 ∨ ¬s = 0) := by + simp [getZero, ← PackedFloat.le_def, PackedFloat.le, PackedFloat.isNaN] + +@[simp] +theorem plus_zero_not_le_minus_zero : + (PackedFloat.getZero e s false ≤ PackedFloat.getZero e s true) = + (e = 0 ∧ s = 0) := by + simp [getZero, ← PackedFloat.le_def, PackedFloat.le, PackedFloat.isNaN] + instance {x y : PackedFloat e s} : Decidable (x ≤ y) := by simp only [← PackedFloat.le_def] @@ -1667,6 +1691,73 @@ theorem classification {P : PackedFloat e s → Prop} · grind · grind +@[simp, grind .] +theorem PackedFloat.le_refl (x : PackedFloat e s) : x ≤ x := by simp [← PackedFloat.le_def, PackedFloat.le] + +theorem PackedFloat.le_NaN (x : PackedFloat e s) (he : 0 < e) (hs : 0 < s): + x ≤ PackedFloat.mkNaN ↔ x.isNaN := by + -- simp only [← PackedFloat.le_def, PackedFloat.le] + -- simp + by_cases hx : x.isNaN + · simp only [hx, iff_true] + simp only [← PackedFloat.le_def, PackedFloat.le, hx] + simp only [isNaN_mkNaN, and_self, not_true_eq_false, false_and, false_or, true_or] + · simp [hx] + simp only [← PackedFloat.le_def, PackedFloat.le] + simp [hx] + simp [PackedFloat.isNaN] at hx + by_cases hs : x.sign + · simp [hs] + · simp [hs] + simp [he] + sorry + +theorem PackedFloat.NaN_le (x : PackedFloat e s) + (he : 0 < e) (hs : 0 < s) : PackedFloat.mkNaN ≤ x ↔ x.isNaN := by + simp only [← PackedFloat.le_def, PackedFloat.le, PackedFloat.isNaN] + simp only [ex_mkNaN, BEq.rfl, sig_mkNaN, BitVec.zero_eq, Bool.true_and, Bool.or_eq_true, + beq_iff_eq, bne_iff_ne, ne_eq, Bool.and_eq_true, not_or, + Decidable.not_not, not_and_self, not_and, sign_mkNaN, Bool.false_eq_true, false_and, and_false, + and_self, Int.reduceNeg, true_and, BitVec.toNat_intMin, or_self, or_false, + false_or] + simp [show ¬ s = 0 by grind] + by_cases hsign : x.sign + · simp [hsign] + · simp [hsign] + simp [he] + intros h + sorry + +@[simp] +theorem le_iff_toExtRat_le_toExtRat_of_not_isZero (x y : PackedFloat e s) (hx : ¬ x.isZero) (hy : ¬ y.isZero) : + x ≤ y ↔ x.toExtRat ≤ y.toExtRat := sorry + +-- recall that -0 ≤ +0. So if x has sign = false, then y also needs sign = false +@[simp] +theorem le_iff_sign_eq_of_isZero (x y : PackedFloat e s) + (hx : x.isZero) (hy : y.isZero) : x ≤ y ↔ (x.sign = false → y.sign = false) := by + sorry + +theorem PackedFloat.le_antisymm (he : 0 < e) (hs : 0 < s) {x y : PackedFloat e s} + (hxy : x ≤ y) (hyx : y ≤ x) : + x = y := by + sorry + -- grind (splits := 10) [PackedFloat] + +theorem PackedFloat.le_trans + {x y z : PackedFloat e s} (hxy : x ≤ y) (hyz : y ≤ z) : x ≤ z := by + sorry + +@[simp] +theorem le_zero_iff_sign_eq_true {x : PackedFloat e s} : + (x ≤ PackedFloat.getZero e s true) ↔ (x.sign = true) := by + simp only [PackedFloat.getZero, ← PackedFloat.le_def, PackedFloat.le] + sorry + +@[simp] +theorem zero_le_iff_sign_eq_false {x : PackedFloat e s} : + (PackedFloat.getZero e s false ≤ x) ↔ (x.sign = false) := by sorry + end PackedFloat From f1ec468d418744ac945dcf9c8792f99327223702 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Fri, 13 Feb 2026 10:52:38 +0000 Subject: [PATCH 12/44] chore: delete Foo.lean --- Foo.lean | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 Foo.lean diff --git a/Foo.lean b/Foo.lean deleted file mode 100644 index 23c48c7..0000000 --- a/Foo.lean +++ /dev/null @@ -1,39 +0,0 @@ -theorem concat_truncate_zero_eq_mul_pow2 - {n : Nat} (x : BitVec n) (tz : Nat) (htz : tz ≤ n) : - ((x.truncate (n - tz)) ++ (0#tz) |>.cast (by omega)) = x * (1#n <<< tz) := by - ext i - simp - rw [BitVec.getElem_append] - have : 1#n <<< tz = BitVec.twoPow _ tz := by - ext i - simp - by_cases hi : i < tz <;> simp [hi] <;> omega - rw [this] - rw [← BitVec.shiftLeft_eq_mul_twoPow] - rw [BitVec.getElem_shiftLeft] - by_cases hi : i < tz - · simp [hi] - · simp [hi] - rw [BitVec.getLsbD_eq_getElem] - - -/-- -appending 'tz' trailing zeroes to the truncated 'x' is equal to -multiplying 'x' by 2^tz. --/ -theorem extractLsb'_append_zero_eq_mul_shiftLeft {n : Nat} - (x : BitVec n) (tz : Nat) (htz : tz ≤ n) : - ((x.extractLsb' 0 (n - tz) ++ (0#tz)).cast (by omega)) - = x * (1#n <<< tz) := by - ext i - simp only [BitVec.getElem_cast] - rw [BitVec.getElem_append] - have : 1#n <<< tz = BitVec.twoPow _ tz := by - ext i - simp only [BitVec.getElem_shiftLeft, BitVec.getElem_one, BitVec.getElem_twoPow] - by_cases hi : i < tz <;> simp [hi] <;> omega - rw [this, ← BitVec.shiftLeft_eq_mul_twoPow, BitVec.getElem_shiftLeft] - by_cases hi : i < tz - · simp [hi] - · simp [hi] - rw [BitVec.getLsbD_eq_getElem] From 514e91a3d749470f09f69016fd13b0fc9c183f8b Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Fri, 13 Feb 2026 10:56:27 +0000 Subject: [PATCH 13/44] chore: fix sorry --- Fp/SmtLibSemantics.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Fp/SmtLibSemantics.lean b/Fp/SmtLibSemantics.lean index 57d4a7f..be68576 100644 --- a/Fp/SmtLibSemantics.lean +++ b/Fp/SmtLibSemantics.lean @@ -473,7 +473,7 @@ instance (he : 0 < e) (hs : 0 < s) : LawfulRoundableAdjunction (smtLibV (embedPa simp at hr -- TODO: extract this out into a separate boi. subst hr - apply PackedFloat.toExtRat'_eq_NaN_of_isNaN (smtLibLower.lower ExtRat.NaN) + -- apply PackedFloat.toExtRat'_eq_NaN_of_isNaN (smtLibLower.lower ExtRat.NaN) sorry · sorry case zeroCase sign => From 67ce95632f357d70028864fd92a456159a18227d Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Fri, 13 Feb 2026 11:12:07 +0000 Subject: [PATCH 14/44] chore: add a lot of theory around the ordering --- Fp/Basic.lean | 105 ++++++++++++++++++++------------------------------ 1 file changed, 41 insertions(+), 64 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 6a69a67..b5c7850 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -394,7 +394,7 @@ the canonical NaN for `exWidth = 3` and `sigWidth = 4` is `0.111.1000`. def getNaN (exWidth sigWidth : Nat) : PackedFloat exWidth sigWidth where sign := false ex := BitVec.allOnes exWidth - sig := BitVec.ofNat sigWidth (2 ^ (sigWidth - 1)) + sig := BitVec.intMin sigWidth @[simp] theorem sign_getNaN (exWidth sigWidth : Nat) : @@ -406,7 +406,7 @@ theorem ex_getNaN (exWidth sigWidth : Nat) : @[simp] theorem sig_getNaN (exWidth sigWidth : Nat) : - (PackedFloat.getNaN exWidth sigWidth).sig = BitVec.ofNat sigWidth (2 ^ (sigWidth - 1)) := rfl + (PackedFloat.getNaN exWidth sigWidth).sig = BitVec.intMin sigWidth := rfl /-- Returns the infinity value of the specified sign for the given floating point @@ -809,16 +809,7 @@ theorem isNaN_getZero {e s : Nat} (sign : Bool) : theorem isNaN_getNaN {e s : Nat} : (PackedFloat.getNaN e s).isNaN = true := by simp [getNaN, isNaN] - by_cases hs : s = 0 - · simp [hs] - · simp [hs] - intros hcontra - have := BitVec.toNat_inj.mpr hcontra - simp at this - rw [Nat.mod_eq_of_lt] at this - · have : 0 < 2 ^ (s - 1) := by exact Nat.two_pow_pos (s - 1) - grind - · apply Nat.pow_lt_pow_of_lt <;> grind + grind /-- Returns the `PackedFloat` representation for the given `BitVec`. @@ -1448,21 +1439,6 @@ def bias (e : Nat) : Nat := namespace PackedFloat --- | TODO: delete one of mkNaN or getNaN -def mkNaN (sign := false) (sig := BitVec.intMin s) : PackedFloat e s := - { sign, ex := BitVec.allOnes e, sig } - -@[simp, grind =] -theorem sign_mkNaN (sign : Bool) (sig : BitVec s) : - (mkNaN sign sig : PackedFloat e s).sign = sign := rfl - -@[simp, grind =] -theorem ex_mkNaN (sign : Bool) (sig : BitVec s) : - (mkNaN sign sig : PackedFloat e s).ex = BitVec.allOnes e := rfl - -@[simp] -theorem sig_mkNaN {s e} (sign : Bool) (sig : BitVec s) : - (mkNaN sign sig : PackedFloat e s).sig = sig := rfl def toExtDyadic (pf : PackedFloat e s) : ExtDyadic := bif pf.isNaN then @@ -1502,11 +1478,11 @@ definition from the model of floating point. def le (x y : PackedFloat e s) : Prop := (x.isNaN ∧ y.isNaN) ∨ (¬ x.isNaN ∧ ¬ y.isNaN ∧ - (x.sign = true ∧ y.sign = false) ∨ -- x negative, y positive. + ((x.sign = true ∧ y.sign = false) ∨ -- x negative, y positive. (x.sign = false ∧ y.sign = false ∧ x.ex.toInt < y.ex.toInt) ∨ -- both +ve, x smaller ex. (x.sign = false ∧ y.sign = false ∧ x.ex = y.ex ∧ x.sig.toNat ≤ y.sig.toNat) ∨ -- both +ve, x smaller sig. (x.sign = true ∧ y.sign = true ∧ y.ex.toInt < x.ex.toInt) ∨ -- both -ve, y smaller ex. - (x.sign = true ∧ y.sign = true ∧ x.ex = y.ex ∧ y.sig.toNat ≤ x.sig.toNat) + (x.sign = true ∧ y.sign = true ∧ x.ex = y.ex ∧ y.sig.toNat ≤ x.sig.toNat)) ) instance {x y : PackedFloat e s} : Decidable (le x y) := by @@ -1647,13 +1623,13 @@ theorem toExtRat'_getInfinity (sign : Bool) (hs : 0 < s := by grind) : simp [hs] @[simp, grind! .] -theorem isNaN_mkNaN : (PackedFloat.mkNaN : PackedFloat e s).isNaN = true := by - simp [mkNaN, isNaN] +theorem isNaN_mkNaN : (PackedFloat.getNaN e s).isNaN = true := by + simp [getNaN, isNaN] grind @[simp] theorem toExtRat'_mkNaN : - (PackedFloat.mkNaN : PackedFloat e s).toExtRat' = .NaN := by + (PackedFloat.getNaN e s).toExtRat' = .NaN := by rw [toExtRat'] simp @@ -1694,42 +1670,29 @@ theorem classification {P : PackedFloat e s → Prop} @[simp, grind .] theorem PackedFloat.le_refl (x : PackedFloat e s) : x ≤ x := by simp [← PackedFloat.le_def, PackedFloat.le] -theorem PackedFloat.le_NaN (x : PackedFloat e s) (he : 0 < e) (hs : 0 < s): - x ≤ PackedFloat.mkNaN ↔ x.isNaN := by - -- simp only [← PackedFloat.le_def, PackedFloat.le] - -- simp +@[simp, grind .] +theorem PackedFloat.le_NaN (x : PackedFloat e s) : + x ≤ PackedFloat.getNaN e s ↔ x.isNaN := by by_cases hx : x.isNaN · simp only [hx, iff_true] simp only [← PackedFloat.le_def, PackedFloat.le, hx] - simp only [isNaN_mkNaN, and_self, not_true_eq_false, false_and, false_or, true_or] + simp only [isNaN_mkNaN, and_self, not_true_eq_false, false_and, true_or] · simp [hx] simp only [← PackedFloat.le_def, PackedFloat.le] simp [hx] - simp [PackedFloat.isNaN] at hx - by_cases hs : x.sign - · simp [hs] - · simp [hs] - simp [he] - sorry +@[simp, grind .] theorem PackedFloat.NaN_le (x : PackedFloat e s) - (he : 0 < e) (hs : 0 < s) : PackedFloat.mkNaN ≤ x ↔ x.isNaN := by - simp only [← PackedFloat.le_def, PackedFloat.le, PackedFloat.isNaN] - simp only [ex_mkNaN, BEq.rfl, sig_mkNaN, BitVec.zero_eq, Bool.true_and, Bool.or_eq_true, - beq_iff_eq, bne_iff_ne, ne_eq, Bool.and_eq_true, not_or, - Decidable.not_not, not_and_self, not_and, sign_mkNaN, Bool.false_eq_true, false_and, and_false, - and_self, Int.reduceNeg, true_and, BitVec.toNat_intMin, or_self, or_false, - false_or] - simp [show ¬ s = 0 by grind] - by_cases hsign : x.sign - · simp [hsign] - · simp [hsign] - simp [he] - intros h - sorry - -@[simp] -theorem le_iff_toExtRat_le_toExtRat_of_not_isZero (x y : PackedFloat e s) (hx : ¬ x.isZero) (hy : ¬ y.isZero) : + : PackedFloat.getNaN e s ≤ x ↔ x.isNaN := by + simp only [← PackedFloat.le_def, PackedFloat.le] + simp only [isNaN_getNaN] + by_cases hx : x.isNaN + · simp [hx] + · simp [hx] + +@[simp] +theorem le_iff_toExtRat_le_toExtRat_of_not_isZero (x y : PackedFloat e s) + (hx : ¬ x.isZero) (hy : ¬ y.isZero) : x ≤ y ↔ x.toExtRat ≤ y.toExtRat := sorry -- recall that -0 ≤ +0. So if x has sign = false, then y also needs sign = false @@ -1738,11 +1701,25 @@ theorem le_iff_sign_eq_of_isZero (x y : PackedFloat e s) (hx : x.isZero) (hy : y.isZero) : x ≤ y ↔ (x.sign = false → y.sign = false) := by sorry -theorem PackedFloat.le_antisymm (he : 0 < e) (hs : 0 < s) {x y : PackedFloat e s} - (hxy : x ≤ y) (hyx : y ≤ x) : +attribute [grind .] BitVec.toNat_inj +attribute [grind .] BitVec.toInt_inj + +theorem PackedFloat.le_antisymm_of_ne_NaN + {x y : PackedFloat e s} + (hxy : x ≤ y) (hyx : y ≤ x) (hx : ¬ x.isNaN) (hy : ¬ y.isNaN) : x = y := by - sorry - -- grind (splits := 10) [PackedFloat] + simp only [← PackedFloat.le_def] at hxy hyx + simp only [PackedFloat.le] at hxy hyx + simp [hx] at hxy hyx + simp [hy] at hxy hyx + grind [PackedFloat] + +theorem PackedFloat.le_antisymm_iff {x y : PackedFloat e s} + (hxy : x ≤ y) (hyx : y ≤ x) : + (x.isNaN ∧ y.isNaN) ∨ (¬ x.isNaN ∧ ¬ y.isNaN ∧ x = y) := by + by_cases hx : x.isNaN + · sorry + · sorry theorem PackedFloat.le_trans {x y z : PackedFloat e s} (hxy : x ≤ y) (hyz : y ≤ z) : x ≤ z := by From a9187c1d629064c6a3e006b941ca559577a9df09 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 16 Feb 2026 10:19:20 +0000 Subject: [PATCH 15/44] chore: simplify theory --- Fp/Basic.lean | 80 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 12 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index b5c7850..7c6464d 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1474,9 +1474,17 @@ def toExtRat (pf : PackedFloat e s) : ExtRat := /-- 'An Automatable Formal Semantics for IEEE-754 Floating-Point Arithmetic', definition from the model of floating point. + +We differ in one aspect: Since we have multiple NaNs, +we declare that two NaNs are equal iff they are bit-pattern +equivalent. + +This changes the partial order, such that we have +one isolated NaN for each NaN bit-pattern, +along with the usual ordering for all other values. -/ def le (x y : PackedFloat e s) : Prop := - (x.isNaN ∧ y.isNaN) ∨ + (x.isNaN ∧ y.isNaN ∧ x = y) ∨ (¬ x.isNaN ∧ ¬ y.isNaN ∧ ((x.sign = true ∧ y.sign = false) ∨ -- x negative, y positive. (x.sign = false ∧ y.sign = false ∧ x.ex.toInt < y.ex.toInt) ∨ -- both +ve, x smaller ex. @@ -1672,34 +1680,82 @@ theorem PackedFloat.le_refl (x : PackedFloat e s) : x ≤ x := by simp [← Pack @[simp, grind .] theorem PackedFloat.le_NaN (x : PackedFloat e s) : - x ≤ PackedFloat.getNaN e s ↔ x.isNaN := by + x ≤ PackedFloat.getNaN e s ↔ x = PackedFloat.getNaN e s := by by_cases hx : x.isNaN - · simp only [hx, iff_true] - simp only [← PackedFloat.le_def, PackedFloat.le, hx] - simp only [isNaN_mkNaN, and_self, not_true_eq_false, false_and, true_or] - · simp [hx] - simp only [← PackedFloat.le_def, PackedFloat.le] + · simp only [← PackedFloat.le_def, PackedFloat.le, hx] + simp only [isNaN_mkNaN, and_self, not_true_eq_false, false_and] + grind + · simp only [← PackedFloat.le_def, PackedFloat.le] simp [hx] + grind @[simp, grind .] theorem PackedFloat.NaN_le (x : PackedFloat e s) - : PackedFloat.getNaN e s ≤ x ↔ x.isNaN := by + : PackedFloat.getNaN e s ≤ x ↔ x = PackedFloat.getNaN e s := by simp only [← PackedFloat.le_def, PackedFloat.le] simp only [isNaN_getNaN] by_cases hx : x.isNaN · simp [hx] + grind · simp [hx] + grind + +@[simp, grind .] +theorem PackedFloat.le_iff_eq_of_isNaN (x y : PackedFloat e s) + (hx : x.isNaN) : x ≤ y ↔ x = y := by + simp only [← PackedFloat.le_def, PackedFloat.le, hx] + simp only [not_true_eq_false, false_and] + grind + +@[simp, grind .] +theorem PackedFloat.le_iff_eq_of_isNaN' (x y : PackedFloat e s) + (hy : y.isNaN) : x ≤ y ↔ x = y := by + simp only [← PackedFloat.le_def, PackedFloat.le, hy] + simp only [not_true_eq_false] + grind @[simp] -theorem le_iff_toExtRat_le_toExtRat_of_not_isZero (x y : PackedFloat e s) - (hx : ¬ x.isZero) (hy : ¬ y.isZero) : - x ≤ y ↔ x.toExtRat ≤ y.toExtRat := sorry +theorem le_iff_toExtRat_le_toExtRat_of_not_isZero (he : 0 < e) (hs : 0 < s) + (x y : PackedFloat e s) + (hxzero : ¬ x.isZero) (hyzero : ¬ y.isZero) (hxnan : ¬ x.isNaN) (hynan : ¬ y.isNaN) : + x ≤ y ↔ x.toExtRat ≤ y.toExtRat := by + simp [← PackedFloat.le_def, PackedFloat.le, hxnan, hynan] + simp [PackedFloat.isZero] at hxzero hyzero + -- simp [PackedFloat.isNaN] at hxnan hynan + specialize hxzero (by grind) + specialize hyzero (by grind) + by_cases hxsign : x.sign + · simp [hxsign] + by_cases hysign : y.sign + · simp [hysign] + -- both negative. + sorry + · simp [hysign] + -- x negative, y positive. + sorry + · sorry -- recall that -0 ≤ +0. So if x has sign = false, then y also needs sign = false @[simp] theorem le_iff_sign_eq_of_isZero (x y : PackedFloat e s) (hx : x.isZero) (hy : y.isZero) : x ≤ y ↔ (x.sign = false → y.sign = false) := by - sorry + rw [← PackedFloat.le_def, PackedFloat.le] + have hxnan : ¬ x.isNaN := by grind [isNaN, isZero] + have hynan : ¬ y.isNaN := by grind [isNaN, isZero] + simp [hxnan, hynan] + simp [PackedFloat.isZero] at hx hy + by_cases hxsign : x.sign + · simp [hxsign] + by_cases hysign : y.sign + · simp [hysign] + -- both negative zero. + grind + · simp [hysign] + · simp [hxsign] + by_cases hysign : y.sign + · simp [hysign] + · simp [hysign] + grind attribute [grind .] BitVec.toNat_inj attribute [grind .] BitVec.toInt_inj From e3ca0b3de3d5013a75c3bbf2642009c7c997aeb9 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 16 Feb 2026 10:20:29 +0000 Subject: [PATCH 16/44] chore: add more --- Fp/Basic.lean | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 7c6464d..323e6b2 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1760,6 +1760,7 @@ theorem le_iff_sign_eq_of_isZero (x y : PackedFloat e s) attribute [grind .] BitVec.toNat_inj attribute [grind .] BitVec.toInt_inj +@[grind .] theorem PackedFloat.le_antisymm_of_ne_NaN {x y : PackedFloat e s} (hxy : x ≤ y) (hyx : y ≤ x) (hx : ¬ x.isNaN) (hy : ¬ y.isNaN) : @@ -1774,8 +1775,16 @@ theorem PackedFloat.le_antisymm_iff {x y : PackedFloat e s} (hxy : x ≤ y) (hyx : y ≤ x) : (x.isNaN ∧ y.isNaN) ∨ (¬ x.isNaN ∧ ¬ y.isNaN ∧ x = y) := by by_cases hx : x.isNaN - · sorry - · sorry + · simp [hx] + simp [hx] at hxy hyx + grind + · simp [hx] + by_cases hy : y.isNaN + · simp [hy] + simp [hy] at hxy hyx + grind + · simp [hy] + grind [PackedFloat.le_antisymm_of_ne_NaN hxy hyx hx hy] theorem PackedFloat.le_trans {x y z : PackedFloat e s} (hxy : x ≤ y) (hyz : y ≤ z) : x ≤ z := by From 467851b4048289b6af5030186fe29e6cfa4fc4ee Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 16 Feb 2026 10:43:29 +0000 Subject: [PATCH 17/44] chore: stuck on Zero FP ordering property, confusing --- Fp/Basic.lean | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 323e6b2..3091022 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1788,13 +1788,37 @@ theorem PackedFloat.le_antisymm_iff {x y : PackedFloat e s} theorem PackedFloat.le_trans {x y z : PackedFloat e s} (hxy : x ≤ y) (hyz : y ≤ z) : x ≤ z := by - sorry + simp only [← PackedFloat.le_def] at hxy hyz ⊢ + simp only [PackedFloat.le] at hxy hyz ⊢ + by_cases hx : x.isNaN + · simp [hx] at hxy + simp at hyz + grind + · simp [hx] at hxy + simp at hyz + by_cases hy : y.isNaN + · simp [hy] at hxy + · simp [hy] at hxy hyz + by_cases hz : z.isNaN + · simp [hz] at hxy hyz + · simp [hz] at hxy hyz + grind (splits := 10) + @[simp] -theorem le_zero_iff_sign_eq_true {x : PackedFloat e s} : - (x ≤ PackedFloat.getZero e s true) ↔ (x.sign = true) := by - simp only [PackedFloat.getZero, ← PackedFloat.le_def, PackedFloat.le] - sorry +theorem le_zero_iff_sign_eq_true {x : PackedFloat e s} (he : 0 < e): + (x ≤ PackedFloat.getZero e s true) ↔ (x.sign = true ∧ ¬ x.isNaN) := by + rw [← PackedFloat.le_def, PackedFloat.le] + by_cases hx : x.isNaN + · simp [hx] at ⊢ + simp [show ¬ e = 0 by grind] + · simp [hx] at ⊢ + simp [show ¬ e = 0 by grind] + by_cases hxsign : x.sign + · simp [hxsign] + sorry + . simp [hxsign] + @[simp] theorem zero_le_iff_sign_eq_false {x : PackedFloat e s} : From 57af1b0b1c19f8681d00cd8a34b92e7cff17f8a3 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 16 Feb 2026 11:35:09 +0000 Subject: [PATCH 18/44] chore: of course, the theorem I claimed is wrong, because I messed up the ordering defn around 0 --- Fp/Basic.lean | 71 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 3091022..3a688e8 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -441,15 +441,15 @@ def getZero (exWidth sigWidth : Nat) (sign : Bool) ex := 0 sig := 0 -@[simp] +@[simp, grind =] theorem sign_getZero (exWidth sigWidth : Nat) (sign : Bool) : (PackedFloat.getZero exWidth sigWidth sign).sign = sign := rfl -@[simp] +@[simp, grind =] theorem sig_getZero (exWidth sigWidth : Nat) (sign : Bool) : (PackedFloat.getZero exWidth sigWidth sign).sig = 0 := rfl -@[simp] +@[simp, grind =] theorem ex_getZero (exWidth sigWidth : Nat) (sign : Bool) : (PackedFloat.getZero exWidth sigWidth sign).ex = 0 := rfl @@ -1503,16 +1503,15 @@ instance : LE (PackedFloat exWidth sigWidth) where theorem le_def (x y : PackedFloat e s) : x.le y = (x ≤ y) := rfl -@[simp] +@[simp, grind =] theorem minus_zero_le_plus_zero {e s} : (PackedFloat.getZero e s true ≤ PackedFloat.getZero e s false) = - (e = 0 → s = 0 ∨ ¬s = 0) := by + (e = 0 → s ≠ 0) := by simp [getZero, ← PackedFloat.le_def, PackedFloat.le, PackedFloat.isNaN] -@[simp] +@[simp, grind .] theorem plus_zero_not_le_minus_zero : - (PackedFloat.getZero e s false ≤ PackedFloat.getZero e s true) = - (e = 0 ∧ s = 0) := by + ¬ (PackedFloat.getZero e s false ≤ PackedFloat.getZero e s true) := by simp [getZero, ← PackedFloat.le_def, PackedFloat.le, PackedFloat.isNaN] @@ -1672,8 +1671,8 @@ theorem classification {P : PackedFloat e s → Prop} · by_cases h4 : x.isNonzeroSubnorm · grind · by_cases h5 : x.isNorm - · grind - · grind + · grind only [→ isNormOrSubnorm_of_isNorm, #d1e2] + · grind only @[simp, grind .] theorem PackedFloat.le_refl (x : PackedFloat e s) : x ≤ x := by simp [← PackedFloat.le_def, PackedFloat.le] @@ -1805,20 +1804,56 @@ theorem PackedFloat.le_trans grind (splits := 10) +/- +If the numbers are notNaN, then 'x ≤ y' if the x is negative and y is positive. +-/ +@[simp] +theorem le_of_sign_eq_true_sign_eq_false {x y : PackedFloat e s} + (hxnan : ¬ x.isNaN) (hynan : ¬ y.isNaN) (hxsign : x.sign = true) (hysign : y.sign = false) : + (x ≤ y) := by + rw [← PackedFloat.le_def, PackedFloat.le] + grind + +/- +If the numbers are notNaN, then 'x ≤ y' if the x is negative and y is positive. +-/ +@[simp, grind .] +theorem not_le_of_sign_eq_false_of_sign_eq_true {x y : PackedFloat e s} + (hxnan : ¬ x.isNaN) (hxsign : x.sign = false) (hysign : y.sign = true) : + ¬ (x ≤ y) := by + rw [← PackedFloat.le_def, PackedFloat.le] + grind + +@[simp, grind .] +theorem le_eq_of_sign_eq_false_of_sign_eq_false {x y : PackedFloat e s} (hxnan : ¬ x.isNaN) (hynan : ¬ y.isNaN) + (hxsign : x.sign = false) (hysign : y.sign = false) : + (x ≤ y) = (x.ex.toInt < y.ex.toInt) ∨ (x.ex.toInt = y.ex.toInt ∧ x.sig.toNat ≤ y.sig.toNat) := by + rw [← PackedFloat.le_def, PackedFloat.le] + grind + +@[simp, grind .] +theorem le_eq_of_sign_eq_true_of_sign_eq_true {x y : PackedFloat e s} (hxnan : ¬ x.isNaN) (hynan : ¬ y.isNaN) + (hxsign : x.sign = true) (hysign : y.sign = true) : + (x ≤ y) = (y.ex.toInt < x.ex.toInt) ∨ (x.ex.toInt = y.ex.toInt ∧ y.sig.toNat ≤ x.sig.toNat) := by + rw [← PackedFloat.le_def, PackedFloat.le] + grind + @[simp] theorem le_zero_iff_sign_eq_true {x : PackedFloat e s} (he : 0 < e): (x ≤ PackedFloat.getZero e s true) ↔ (x.sign = true ∧ ¬ x.isNaN) := by - rw [← PackedFloat.le_def, PackedFloat.le] - by_cases hx : x.isNaN - · simp [hx] at ⊢ - simp [show ¬ e = 0 by grind] - · simp [hx] at ⊢ - simp [show ¬ e = 0 by grind] + by_cases hxNaN : x.isNaN + · grind only [→ not_isZero_of_isNaN, PackedFloat.le_iff_eq_of_isNaN, isZero_getZero] + · simp [hxNaN] by_cases hxsign : x.sign · simp [hxsign] + rw [← PackedFloat.le_def, PackedFloat.le] + simp [hxNaN] + simp [show ¬ e = 0 by grind] + simp [hxsign] sorry - . simp [hxsign] - + · simp [hxsign] + have : (getZero e s true).sign = true := by grind only [= sign_getZero] + grind only [not_le_of_sign_eq_false_of_sign_eq_true] @[simp] theorem zero_le_iff_sign_eq_false {x : PackedFloat e s} : From 2bea9bd9cbea8d6ba00423855d95329ed0e5d5aa Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 16 Feb 2026 12:37:26 +0000 Subject: [PATCH 19/44] chore: fix all tests --- Fp/Basic.lean | 34 +++++++++++++++++-------- Fp/Packing.lean | 2 +- Fp/SmtLibSemantics.lean | 12 ++++----- Fp/Tests/SmtLibSemanticsComputable.lean | 6 ++--- Fp/Theorems/UnpackedRound.lean | 20 +++++++-------- Main.lean | 2 +- 6 files changed, 44 insertions(+), 32 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 3a688e8..dc595d1 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1643,10 +1643,6 @@ theorem toExtRat'_mkNaN : @[simp, grind! .] axiom toExtRat'_getZero (sign : Bool) (hs : 0 < s := by grind) : (PackedFloat.getZero e s sign).toExtRat' = .Number 0 -- := by - -- have : (PackedFloat.getZero e s sign).isZero = true := by - -- sorry - -- simp [this] - -- grind /-- Case splitting on the different values a packed float @@ -1827,20 +1823,20 @@ theorem not_le_of_sign_eq_false_of_sign_eq_true {x y : PackedFloat e s} @[simp, grind .] theorem le_eq_of_sign_eq_false_of_sign_eq_false {x y : PackedFloat e s} (hxnan : ¬ x.isNaN) (hynan : ¬ y.isNaN) (hxsign : x.sign = false) (hysign : y.sign = false) : - (x ≤ y) = (x.ex.toInt < y.ex.toInt) ∨ (x.ex.toInt = y.ex.toInt ∧ x.sig.toNat ≤ y.sig.toNat) := by + (x ≤ y) = ((x.ex.toInt < y.ex.toInt) ∨ (x.ex.toInt = y.ex.toInt ∧ x.sig.toNat ≤ y.sig.toNat)):= by rw [← PackedFloat.le_def, PackedFloat.le] grind @[simp, grind .] theorem le_eq_of_sign_eq_true_of_sign_eq_true {x y : PackedFloat e s} (hxnan : ¬ x.isNaN) (hynan : ¬ y.isNaN) (hxsign : x.sign = true) (hysign : y.sign = true) : - (x ≤ y) = (y.ex.toInt < x.ex.toInt) ∨ (x.ex.toInt = y.ex.toInt ∧ y.sig.toNat ≤ x.sig.toNat) := by + (x ≤ y) = ((y.ex.toInt < x.ex.toInt) ∨ (x.ex.toInt = y.ex.toInt ∧ y.sig.toNat ≤ x.sig.toNat)):= by rw [← PackedFloat.le_def, PackedFloat.le] grind @[simp] theorem le_zero_iff_sign_eq_true {x : PackedFloat e s} (he : 0 < e): - (x ≤ PackedFloat.getZero e s true) ↔ (x.sign = true ∧ ¬ x.isNaN) := by + (x ≤ PackedFloat.getZero e s true) ↔ (x.sign = true ∧ ¬ x.isNaN ∧ x.ex.toInt ≥ 0) := by by_cases hxNaN : x.isNaN · grind only [→ not_isZero_of_isNaN, PackedFloat.le_iff_eq_of_isNaN, isZero_getZero] · simp [hxNaN] @@ -1850,15 +1846,31 @@ theorem le_zero_iff_sign_eq_true {x : PackedFloat e s} (he : 0 < e): simp [hxNaN] simp [show ¬ e = 0 by grind] simp [hxsign] - sorry + have := BitVec.toInt_inj (x := x.ex) (y := 0#_) + grind · simp [hxsign] have : (getZero e s true).sign = true := by grind only [= sign_getZero] grind only [not_le_of_sign_eq_false_of_sign_eq_true] @[simp] -theorem zero_le_iff_sign_eq_false {x : PackedFloat e s} : - (PackedFloat.getZero e s false ≤ x) ↔ (x.sign = false) := by sorry - +theorem zero_le_iff_sign_eq_false {x : PackedFloat e s} (he : 0 < e): + (PackedFloat.getZero e s false ≤ x) ↔ (x.sign = false ∧ ¬ x.isNaN ∧ 0 ≤ x.ex.toInt) := by + by_cases hxNaN : x.isNaN + · simp [hxNaN] + grind + · simp [hxNaN] + by_cases hxsign : x.sign + · simp [hxsign] + rw [← PackedFloat.le_def, PackedFloat.le] + simp [hxNaN] + simp [show ¬ e = 0 by grind] + simp [hxsign] + · simp [hxsign] + have : (getZero e s false).sign = false := by grind only [= sign_getZero] + have := le_eq_of_sign_eq_false_of_sign_eq_false (x := (getZero e s false)) (y := x) + (by grind) (by grind) (by grind) (by grind) + simp at this + grind end PackedFloat diff --git a/Fp/Packing.lean b/Fp/Packing.lean index 32b2b24..524580a 100644 --- a/Fp/Packing.lean +++ b/Fp/Packing.lean @@ -122,7 +122,7 @@ theorem PackedFloat.unpack_eq_NaN_of_isNaN (pf : PackedFloat e s) (hpf : pf.isNa @[simp] theorem EUnpackedFloat.mkNaN_pack_eq_mkNaN : (EUnpackedFloat.mkNaN : EUnpackedFloat _ _).pack = - (PackedFloat.mkNaN : PackedFloat e s) := rfl + (PackedFloat.getNaN e s) := rfl @[simp] theorem EUnpackedFloat.mkInfinity_pack_eq_getInfinity (sign : Bool) : diff --git a/Fp/SmtLibSemantics.lean b/Fp/SmtLibSemantics.lean index be68576..4e0fb18 100644 --- a/Fp/SmtLibSemantics.lean +++ b/Fp/SmtLibSemantics.lean @@ -196,7 +196,7 @@ def RoundMethod.roundRNE : PackedFloat e s := else if ¬ (isZero r) ∧ roundMethod.tieBreak r ∧ roundMethod.isEven (roundMethod.lower r) then roundMethod.lower r else if ¬ (isZero r) ∧ roundMethod.tieBreak r ∧ roundMethod.isEven (roundMethod.upper r) then roundMethod.upper r else if ¬ (isZero r) ∧ !roundMethod.lowerHalf r ∧ !roundMethod.tieBreak r then roundMethod.upper r - else .mkNaN -- does not occur. + else .getNaN e s -- does not occur. def RoundMethod.roundRNA : PackedFloat e s := if isNaN r then roundMethod.lower r @@ -205,14 +205,14 @@ def RoundMethod.roundRNA : PackedFloat e s := else if ¬ (isZero r) ∧ roundMethod.tieBreak r ∧ roundMethod.isEven (roundMethod.lower r) then roundMethod.lower r else if ¬ (isZero r) ∧ roundMethod.tieBreak r ∧ roundMethod.isEven (roundMethod.upper r) then roundMethod.upper r else if ¬ (isZero r) ∧ !roundMethod.lowerHalf r ∧ !roundMethod.tieBreak r then roundMethod.lower r - else .mkNaN -- does not occur. + else .getNaN e s -- does not occur. def RoundMethod.roundRTP : PackedFloat e s := if isNaN r then roundMethod.lower r else if isZero r then roundMethod.rounderForSign sign r else if ¬ (isZero r) ∧ (gtZero r) then roundMethod.upper r else if ¬ (isZero r) ∧ (ltZero r) then roundMethod.rounderForSign sign r - else .mkNaN -- does not occur. + else .getNaN e s -- does not occur. def RoundMethod.roundRTN : PackedFloat e s := @@ -223,7 +223,7 @@ def RoundMethod.roundRTZ : PackedFloat e s := if isZero r then roundMethod.rounderForSign sign r else if gtZero r then roundMethod.lower r else if ltZero r then roundMethod.upper r - else .mkNaN -- does not occur. + else .getNaN e s -- does not occur. /-- define the rounding function for a given choice of 'RoundMethod'. -/ @@ -405,7 +405,7 @@ theorem IsLawfulUpper_mkInfinity (hs : 0 < s) {sign : Bool} : IsLawfulUpper (Ext grind @[simp, grind .] -theorem IsLawfulLower_mkNaN : IsLawfulLower ExtRat.NaN (PackedFloat.mkNaN : PackedFloat e s) := by +theorem IsLawfulLower_mkNaN : IsLawfulLower ExtRat.NaN (PackedFloat.getNaN e s) := by constructor simp intros lower hLtNaN @@ -413,7 +413,7 @@ theorem IsLawfulLower_mkNaN : IsLawfulLower ExtRat.NaN (PackedFloat.mkNaN : Pack simp [hLtNaN] @[simp, grind .] -theorem IsLawfulUpper_mkNaN : IsLawfulUpper ExtRat.NaN (PackedFloat.mkNaN : PackedFloat e s) := by +theorem IsLawfulUpper_mkNaN : IsLawfulUpper ExtRat.NaN (PackedFloat.getNaN e s) := by constructor simp intros upper hNaNLe diff --git a/Fp/Tests/SmtLibSemanticsComputable.lean b/Fp/Tests/SmtLibSemanticsComputable.lean index c81f01b..b2be3f9 100644 --- a/Fp/Tests/SmtLibSemanticsComputable.lean +++ b/Fp/Tests/SmtLibSemanticsComputable.lean @@ -20,8 +20,8 @@ def lowerFromEmbedByEnumeration {e s} RoundableLower (PackedFloat e s) ExtRat where lower := fun (r : ExtRat) => match r with - | .NaN => .mkNaN - | .Infinity x => .getInfinity e s x -- infinity is representable. + | .NaN => PackedFloat.getNaN e s -- NaN is representable. + | .Infinity x => PackedFloat.getInfinity e s x -- infinity is representable. | .Number r => -- for a number, if it is below the min number, return -inf match enum.greatestLowerBound r with @@ -37,7 +37,7 @@ def upperFromEmbedByEnumeration {e s} RoundableUpper (PackedFloat e s) ExtRat where upper := fun (r : ExtRat) => match r with - | .NaN => .mkNaN + | .NaN => PackedFloat.getNaN e s -- NaN is representable. | .Infinity x => .getInfinity e s x -- infinity is representable. | .Number r => -- for a number, if it is above the max number, return +inf diff --git a/Fp/Theorems/UnpackedRound.lean b/Fp/Theorems/UnpackedRound.lean index 262984e..01319e5 100644 --- a/Fp/Theorems/UnpackedRound.lean +++ b/Fp/Theorems/UnpackedRound.lean @@ -18,20 +18,20 @@ theorem roundQ_eq (eout sout : Nat) (rm : RoundingMode) (sign : Bool) (r : ExtRa set_option warn.sorry false in @[simp] -theorem lower_NaN_eq_PackedFloat_mkNaN : - SmtLibSemantics.smtLibLower.lower ExtRat.NaN = (PackedFloat.mkNaN : PackedFloat e s) := sorry +theorem lower_NaN_eq_PackedFloat_getNaN : + SmtLibSemantics.smtLibLower.lower ExtRat.NaN = (PackedFloat.getNaN e s) := sorry set_option warn.sorry false in @[simp] -theorem upper_NaN_eq_PackedFloat_mkNaN : - SmtLibSemantics.smtLibUpper.upper ExtRat.NaN = (PackedFloat.mkNaN : PackedFloat e s) := sorry +theorem upper_NaN_eq_PackedFloat_getNaN : + SmtLibSemantics.smtLibUpper.upper ExtRat.NaN = (PackedFloat.getNaN e s) := sorry @[simp] theorem roundRNA_mkNaN (eout sout : Nat) (sign : Bool) : (SmtLibSemantics.smtLibRoundMethod eout sout (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1)))).roundRNA sign - (ExtRat.NaN) = PackedFloat.mkNaN := by + (ExtRat.NaN) = PackedFloat.getNaN eout sout := by simp [SmtLibSemantics.RoundMethod.roundRNA] @[simp] @@ -39,7 +39,7 @@ theorem roundRNE_mkNaN (eout sout : Nat) (sign : Bool) : (SmtLibSemantics.smtLibRoundMethod eout sout (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1)))).roundRNE sign - (ExtRat.NaN) = PackedFloat.mkNaN := by + (ExtRat.NaN) = PackedFloat.getNaN eout sout := by simp [SmtLibSemantics.RoundMethod.roundRNE] @[simp] @@ -47,7 +47,7 @@ theorem roundRTP_mkNaN (eout sout : Nat) (sign : Bool) : (SmtLibSemantics.smtLibRoundMethod eout sout (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1)))).roundRTP sign - (ExtRat.NaN) = PackedFloat.mkNaN := by + (ExtRat.NaN) = PackedFloat.getNaN eout sout := by simp [SmtLibSemantics.RoundMethod.roundRTP] @[simp] @@ -55,7 +55,7 @@ theorem roundRTN_mkNaN (eout sout : Nat) (sign : Bool) : (SmtLibSemantics.smtLibRoundMethod eout sout (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1)))).roundRTN sign - (ExtRat.NaN) = PackedFloat.mkNaN := by + (ExtRat.NaN) = PackedFloat.getNaN eout sout := by simp [SmtLibSemantics.RoundMethod.roundRTN] rcases sign <;> simp @@ -64,7 +64,7 @@ theorem rountRTZ_mkNaN (eout sout : Nat) (sign : Bool) : (SmtLibSemantics.smtLibRoundMethod eout sout (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1)))).roundRTZ sign - (ExtRat.NaN) = PackedFloat.mkNaN := by + (ExtRat.NaN) = PackedFloat.getNaN eout sout := by simp [SmtLibSemantics.RoundMethod.roundRTZ] rcases sign <;> simp @@ -74,7 +74,7 @@ theorem round_eq_mkNaN_of_NaN {sign} {eout sout : Nat} {rm : RoundingMode} : (SmtLibSemantics.smtLibRoundMethod eout sout (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout sout)) (SmtLibSemantics.smtLibV (SmtLibSemantics.embedPackedFloatExtRat eout (sout + 1)))).round - rm sign ExtRat.NaN = PackedFloat.mkNaN := by + rm sign ExtRat.NaN = PackedFloat.getNaN eout sout := by rcases rm · simp · simp diff --git a/Main.lean b/Main.lean index b07c6a9..f88984e 100644 --- a/Main.lean +++ b/Main.lean @@ -54,7 +54,7 @@ theorem h (f : FP8Format) end FP8Format def PackedFloat.toBits' (pf : PackedFloat e s) (normNaN : Bool := true) := - let pf := if pf.isNaN && normNaN then .mkNaN else pf + let pf := if pf.isNaN && normNaN then PackedFloat.getNaN e s else pf pf.toBits def toDigits (b : BitVec n) : String := From 8601b197167bd26bf40799b863627436028a48d7 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 16 Feb 2026 13:12:37 +0000 Subject: [PATCH 20/44] chore: prove sth i had 'axiom'd about toNumberRat --- Fp/Basic.lean | 92 +++++++++++++++++++++++++++++++++++++++------------ Fp/Grind.lean | 6 ++++ 2 files changed, 76 insertions(+), 22 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index dc595d1..6afd171 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1430,9 +1430,14 @@ def ExtDyadic.toExtRat (ed : ExtDyadic) : ExtRat := | .Infinity sign => .Infinity sign | .Number d => .Number d.toRat +@[grind .] def Bool.toSign (b : Bool) : Int := if b then -1 else 1 +@[simp] +theorem Bool.toSign_ne_zero (b : Bool) : b.toSign ≠ 0 := by + cases b <;> simp [Bool.toSign] + @[bv_normalize] def bias (e : Nat) : Nat := 2 ^ (e - 1) - 1 @@ -1560,9 +1565,60 @@ def toNumberRat {e s} (pf : PackedFloat e s) : Rat := else pf.sign.toSign * (0 + pf.sig.toNat / 2 ^ s) * 2 ^ (-(bias e - 1 : Nat) : Int) -/-- This is true, because 'sign' -/ -axiom toNumberRat_ne_zero {pf : PackedFloat e s} (h : pf.isNormOrNonzeroSubnorm) : - pf.toNumberRat ≠ 0 +private theorem Rat.mul_ne_zero_iff {x y : Rat} : (¬ (x * y = 0)) ↔ x ≠ 0 ∧ y ≠ 0 := by + grind + +private theorem Rat.ne_zero_of_zero_lt {r : Rat} (h : 0 < r) : r ≠ 0 := by + grind + +@[simp] +private theorem Rat.zero_add {x : Rat} : 0 + x = x := by + grind + +@[grind . ] +theorem sig_ne_zero_of_isNormOrNonzeroSubnorm_of_not_isNorm {pf : PackedFloat e s} (h : pf.isNormOrNonzeroSubnorm) (hnorm : ¬ pf.isNorm) : + pf.sig.toNat ≠ 0 := by + simp [isNorm] at hnorm + simp [isNormOrNonzeroSubnorm] at h + grind + +theorem toNumberRat_ne_zero {pf : PackedFloat e s} (h : pf.isNormOrNonzeroSubnorm) : + pf.toNumberRat ≠ 0 := by + simp [toNumberRat] + split + case isTrue => + have : pf.sign.toSign ≠ 0 := by grind + have : (pf.sig.toNat : Rat) / (2 : Rat) ^ s ≥ 0 := by grind only [Fp.Rat.div_nonneg, + Rat.pow_nonneg] + have : 1 + (pf.sig.toNat : Rat) / (2 : Rat) ^ s > 0 := by grind only + have : (2 : Rat) ^ (pf.ex.toNat - bias e : Int) > 0 := by grind only [Fp.Rat.two_pow_pos] + simp only [ne_eq] + rw [Rat.mul_ne_zero_iff] + simp only [ne_eq] + rw [Rat.mul_ne_zero_iff] + simp + grind only [#e92ae97e031d7a23] + case isFalse hnorm => + rw [Rat.mul_ne_zero_iff] + simp only [ne_eq] + rw [Rat.mul_ne_zero_iff] + simp only [ne_eq, Rat.intCast_eq_zero_iff, Bool.toSign_ne_zero, not_false_eq_true, true_and] + simp only [isNormOrNonzeroSubnorm, BitVec.zero_eq, Bool.and_eq_true, bne_iff_ne, ne_eq, + Bool.or_eq_true] at h + simp [isNorm] at hnorm + have : pf.sig ≠ 0#s := by grind + have : pf.sig.toNat ≠ 0 := by grind + constructor + · apply Rat.ne_zero_of_zero_lt + -- | TODO: extract into theorem. + have : (pf.sig.toNat : Rat) ≠ 0 := by + intros hcontra + rw [Rat.natCast_eq_zero_iff] at hcontra + grind only + grind only [Fp.Rat.div_pos, Rat.pow_pos] + · apply Rat.ne_zero_of_zero_lt + grind only [Fp.Rat.two_pow_pos] + @[simp, grind →, grind =] theorem sign_iff_toNumberRat_neg {pf : PackedFloat e s} (h : pf.isNormOrNonzeroSubnorm) : @@ -1610,7 +1666,7 @@ theorem sign_iff_toNumberRat_neg {pf : PackedFloat e s} (h : pf.isNormOrNonzeroS · grind @[simp] -theorem toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm {pf : PackedFloat e s} (hp : pf.isNormOrNonzeroSubnorm) : +theorem toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm (pf : PackedFloat e s) (hp : pf.isNormOrNonzeroSubnorm := by grind) : pf.toExtRat' = .Number pf.toNumberRat := by have hnan : pf.isNaN = false := by @@ -1622,6 +1678,7 @@ theorem toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm {pf : PackedFloat e s} (hp simp [toExtRat', hnan, hinf, hzero, toNumberRat] grind + @[simp, grind! .] theorem toExtRat'_getInfinity (sign : Bool) (hs : 0 < s := by grind) : (PackedFloat.getInfinity e s sign).toExtRat' = .Infinity sign := by @@ -1641,8 +1698,12 @@ theorem toExtRat'_mkNaN : simp @[simp, grind! .] -axiom toExtRat'_getZero (sign : Bool) (hs : 0 < s := by grind) : - (PackedFloat.getZero e s sign).toExtRat' = .Number 0 -- := by +theorem toExtRat'_getZero (sign : Bool) (he : 0 < e := by grind) (hs : 0 < s := by grind) : + (PackedFloat.getZero e s sign).toExtRat' = .Number 0 := by + rw [toExtRat'] + simp [show ¬ s = 0 by grind] + simp [show ¬ e = 0 by grind] + simp [he] /-- Case splitting on the different values a packed float @@ -1710,25 +1771,12 @@ theorem PackedFloat.le_iff_eq_of_isNaN' (x y : PackedFloat e s) grind @[simp] -theorem le_iff_toExtRat_le_toExtRat_of_not_isZero (he : 0 < e) (hs : 0 < s) +theorem le_iff_toExtRat'_le_toExtRat'_of_not_isZero (he : 0 < e) (hs : 0 < s) (x y : PackedFloat e s) (hxzero : ¬ x.isZero) (hyzero : ¬ y.isZero) (hxnan : ¬ x.isNaN) (hynan : ¬ y.isNaN) : - x ≤ y ↔ x.toExtRat ≤ y.toExtRat := by + x ≤ y ↔ x.toExtRat' ≤ y.toExtRat' := by simp [← PackedFloat.le_def, PackedFloat.le, hxnan, hynan] - simp [PackedFloat.isZero] at hxzero hyzero - -- simp [PackedFloat.isNaN] at hxnan hynan - specialize hxzero (by grind) - specialize hyzero (by grind) - by_cases hxsign : x.sign - · simp [hxsign] - by_cases hysign : y.sign - · simp [hysign] - -- both negative. - sorry - · simp [hysign] - -- x negative, y positive. - sorry - · sorry + sorry -- recall that -0 ≤ +0. So if x has sign = false, then y also needs sign = false @[simp] diff --git a/Fp/Grind.lean b/Fp/Grind.lean index ef80da4..6539782 100644 --- a/Fp/Grind.lean +++ b/Fp/Grind.lean @@ -20,6 +20,12 @@ theorem Rat.inv_nonneg {a : Rat} (ha : 0 ≤ a) : 0 ≤ a⁻¹ := by @[grind .] theorem Rat.div_nonneg (a b : Rat) (hb : 0 ≤ b) (ha : 0 ≤ a) : 0 ≤ a / b := by grind +@[grind .] +theorem Rat.div_pos (a b : Rat) (hb : 0 < b) (ha : 0 < a) : 0 < a / b := by + apply (Rat.lt_div_iff hb).mpr + simp + grind + @[grind .] theorem Rat.two_pow_pos (n : Int) : 0 < (2 : Rat) ^ n := by exact Rat.zpow_pos rfl From acb9d385041160fdb32bc972cebb535ee8a845e4 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 16 Feb 2026 14:21:10 +0000 Subject: [PATCH 21/44] chore: typo in def, next need to fix --- Fp/Basic.lean | 56 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 6afd171..367d373 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1770,13 +1770,65 @@ theorem PackedFloat.le_iff_eq_of_isNaN' (x y : PackedFloat e s) simp only [not_true_eq_false] grind +/-- +x is infinite iff it is equal to the infinity value with the same sign. +-/ +@[grind .] +theorem PackedFloat.eq_getInfinity_iff_isInfinity (hs : 0 < s) + {x : PackedFloat e s} : + (x.isInfinite) ↔ x = .getInfinity e s x.sign := by + simp [getInfinity, isInfinite] + grind [PackedFloat] + +/- +hle : getInfinity e s false ≤ y +⊢ y = getInfinity e s false +-/ + +theorem PackedFloat.eq_getInfinity_of_getInfinity_le (hs : 0 < s) (y : PackedFloat e s) + (hle : PackedFloat.getInfinity e s false ≤ y) : + y = .getInfinity e s false := by + rw [← PackedFloat.le_def, PackedFloat.le] at hle + simp at hle + by_cases hy : y.isNaN + · simp [hy] at hle + grind + · simp [hy] at hle + + +/-- +If a number is +infty, then only +infty is larger than it. +-/ +theorem PackedFloat.le_iff_eq_of_isInfinite_of_sign_eq_false (hs : 0 < s) (x y : PackedFloat e s) + (hxnan : ¬ x.isNaN) : + (PackedFloat.getInfinity e s false) ≤ y ↔ y = .getInfinity e s false := by + constructor + · intros hle + + + · intros h + grind + + + + + + @[simp] theorem le_iff_toExtRat'_le_toExtRat'_of_not_isZero (he : 0 < e) (hs : 0 < s) (x y : PackedFloat e s) (hxzero : ¬ x.isZero) (hyzero : ¬ y.isZero) (hxnan : ¬ x.isNaN) (hynan : ¬ y.isNaN) : x ≤ y ↔ x.toExtRat' ≤ y.toExtRat' := by - simp [← PackedFloat.le_def, PackedFloat.le, hxnan, hynan] - sorry + -- rw [toExtRat', toExtRat'] + -- simp [hxzero, hyzero, hxnan, hynan] + by_cases hxinf : x.isInfinite + · simp [hxinf] + sorry + · by_cases hyinf : y.isInfinite + · simp [hyinf] + sorry + · sorry + -- recall that -0 ≤ +0. So if x has sign = false, then y also needs sign = false @[simp] From c7fe34367b4811c5d5e5e430f4749b1c495dd7a0 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 16 Feb 2026 15:36:42 +0000 Subject: [PATCH 22/44] chore: prove about inf ordering --- Fp/Basic.lean | 199 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 132 insertions(+), 67 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 367d373..2a56166 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -492,6 +492,41 @@ def isNaN (pf : PackedFloat e s) : Bool := -- Prioritize `NaN` over `Infinity`. pf.ex == .allOnes e && (s == 0 || pf.sig != .zero s) +@[grind .] +private theorem BitVec.eq_allOnes_iff_toNat_eq (x : BitVec w) : + x = .allOnes w ↔ x.toNat = 2 ^ w - 1 := by + constructor + · intros h + subst h + simp + · intros h + apply BitVec.toNat_inj.mp + simp [h] + + +@[grind .] +private theorem BitVec.eq_zero_iff_toNat_eq (x : BitVec w) : + x = .zero w ↔ x.toNat = 0 := by + constructor + · intros h + subst h + simp + · intros h + apply BitVec.toNat_inj.mp + simp [h] + +@[grind =>] +theorem isNaN_iff_ex_eq_sig_eq (pf : PackedFloat e s) (hs : 0 < s) : + pf.isNaN ↔ (pf.ex = .allOnes e ∧ pf.sig ≠ 0#s) := by + simp [isNaN] + grind + +@[grind =>] +theorem not_isNaN_iff_ex_ne_or_sig_ne (pf : PackedFloat e s) (hs : 0 < s) : + (¬ pf.isNaN) ↔ (pf.ex ≠ .allOnes e ∨ pf.sig = 0#s) := by + simp [isNaN] + grind + @[bv_normalize] def isInfinite (pf : PackedFloat e s) : Bool := -- Prioritize `Infinity` over `0`. This is somewhat arbitrary. @@ -1492,9 +1527,9 @@ def le (x y : PackedFloat e s) : Prop := (x.isNaN ∧ y.isNaN ∧ x = y) ∨ (¬ x.isNaN ∧ ¬ y.isNaN ∧ ((x.sign = true ∧ y.sign = false) ∨ -- x negative, y positive. - (x.sign = false ∧ y.sign = false ∧ x.ex.toInt < y.ex.toInt) ∨ -- both +ve, x smaller ex. + (x.sign = false ∧ y.sign = false ∧ x.ex.toNat < y.ex.toNat) ∨ -- both +ve, x smaller ex. (x.sign = false ∧ y.sign = false ∧ x.ex = y.ex ∧ x.sig.toNat ≤ y.sig.toNat) ∨ -- both +ve, x smaller sig. - (x.sign = true ∧ y.sign = true ∧ y.ex.toInt < x.ex.toInt) ∨ -- both -ve, y smaller ex. + (x.sign = true ∧ y.sign = true ∧ y.ex.toNat < x.ex.toNat) ∨ -- both -ve, y smaller ex. (x.sign = true ∧ y.sign = true ∧ x.ex = y.ex ∧ y.sig.toNat ≤ x.sig.toNat)) ) @@ -1651,7 +1686,7 @@ theorem sign_iff_toNumberRat_neg {pf : PackedFloat e s} (h : pf.isNormOrNonzeroS have : ¬ ((pf.sig.toNat : Rat) / 2 ^ s = 0) := by intros hcontra rw [hcontra] at hnum - simp [Rat.zero_add, Rat.mul_zero, Rat.zero_mul] at hnum + simp [Rat.mul_zero, Rat.zero_mul] at hnum have : (0 + pf.sig.toNat / 2^s) * (2 : Rat) ^ (-(bias e - 1 : Nat) : Int) > 0 := by apply Rat.mul_pos <;> grind rw [Rat.mul_assoc] @@ -1780,55 +1815,6 @@ theorem PackedFloat.eq_getInfinity_iff_isInfinity (hs : 0 < s) simp [getInfinity, isInfinite] grind [PackedFloat] -/- -hle : getInfinity e s false ≤ y -⊢ y = getInfinity e s false --/ - -theorem PackedFloat.eq_getInfinity_of_getInfinity_le (hs : 0 < s) (y : PackedFloat e s) - (hle : PackedFloat.getInfinity e s false ≤ y) : - y = .getInfinity e s false := by - rw [← PackedFloat.le_def, PackedFloat.le] at hle - simp at hle - by_cases hy : y.isNaN - · simp [hy] at hle - grind - · simp [hy] at hle - - -/-- -If a number is +infty, then only +infty is larger than it. --/ -theorem PackedFloat.le_iff_eq_of_isInfinite_of_sign_eq_false (hs : 0 < s) (x y : PackedFloat e s) - (hxnan : ¬ x.isNaN) : - (PackedFloat.getInfinity e s false) ≤ y ↔ y = .getInfinity e s false := by - constructor - · intros hle - - - · intros h - grind - - - - - - -@[simp] -theorem le_iff_toExtRat'_le_toExtRat'_of_not_isZero (he : 0 < e) (hs : 0 < s) - (x y : PackedFloat e s) - (hxzero : ¬ x.isZero) (hyzero : ¬ y.isZero) (hxnan : ¬ x.isNaN) (hynan : ¬ y.isNaN) : - x ≤ y ↔ x.toExtRat' ≤ y.toExtRat' := by - -- rw [toExtRat', toExtRat'] - -- simp [hxzero, hyzero, hxnan, hynan] - by_cases hxinf : x.isInfinite - · simp [hxinf] - sorry - · by_cases hyinf : y.isInfinite - · simp [hyinf] - sorry - · sorry - -- recall that -0 ≤ +0. So if x has sign = false, then y also needs sign = false @[simp] @@ -1879,7 +1865,7 @@ theorem PackedFloat.le_antisymm_iff {x y : PackedFloat e s} simp [hy] at hxy hyx grind · simp [hy] - grind [PackedFloat.le_antisymm_of_ne_NaN hxy hyx hx hy] + grind only [PackedFloat.le_antisymm_of_ne_NaN hxy hyx hx hy] theorem PackedFloat.le_trans {x y z : PackedFloat e s} (hxy : x ≤ y) (hyz : y ≤ z) : x ≤ z := by @@ -1898,7 +1884,11 @@ theorem PackedFloat.le_trans · simp [hz] at hxy hyz · simp [hz] at hxy hyz grind (splits := 10) - + only [BitVec.toNat_inj, #fde2d389667160e9, #54fdc8e31fc2dc1c, #bf4f9097212d569d, + #0ddaf51762ab63df, #8dc2e2b3e678dc39, #b28eea1a75158fe1, #71fd579644e57b0a, + #ca7289c2a156499b, #8f9092e537ef6258, #ef1611d882ec5869, #e781b8f11c51b17b, + #2d6d3bcdb3a3b35c, #31dd348e5c4aee2a, #a7908cd812b01724, #f811994cd6c34475, + #7d54ade5a8b64ca3, #b35f21abfee2096d] /- If the numbers are notNaN, then 'x ≤ y' if the x is negative and y is positive. @@ -1908,7 +1898,7 @@ theorem le_of_sign_eq_true_sign_eq_false {x y : PackedFloat e s} (hxnan : ¬ x.isNaN) (hynan : ¬ y.isNaN) (hxsign : x.sign = true) (hysign : y.sign = false) : (x ≤ y) := by rw [← PackedFloat.le_def, PackedFloat.le] - grind + grind only /- If the numbers are notNaN, then 'x ≤ y' if the x is negative and y is positive. @@ -1918,25 +1908,99 @@ theorem not_le_of_sign_eq_false_of_sign_eq_true {x y : PackedFloat e s} (hxnan : ¬ x.isNaN) (hxsign : x.sign = false) (hysign : y.sign = true) : ¬ (x ≤ y) := by rw [← PackedFloat.le_def, PackedFloat.le] - grind + grind only @[simp, grind .] -theorem le_eq_of_sign_eq_false_of_sign_eq_false {x y : PackedFloat e s} (hxnan : ¬ x.isNaN) (hynan : ¬ y.isNaN) - (hxsign : x.sign = false) (hysign : y.sign = false) : - (x ≤ y) = ((x.ex.toInt < y.ex.toInt) ∨ (x.ex.toInt = y.ex.toInt ∧ x.sig.toNat ≤ y.sig.toNat)):= by +theorem le_eq_of_sign_eq_false_of_sign_eq_false {x y : PackedFloat e s} + (hxnan : ¬ x.isNaN := by solve | simp | grind) + (hynan : ¬ y.isNaN := by solve | simp | grind) + (hxsign : x.sign = false := by solve | simp | grind) + (hysign : y.sign = false := by solve | simp | grind) : + (x ≤ y) = ((x.ex.toNat < y.ex.toNat) ∨ (x.ex.toNat = y.ex.toNat ∧ x.sig.toNat ≤ y.sig.toNat)):= by rw [← PackedFloat.le_def, PackedFloat.le] - grind + grind only [BitVec.toNat_inj, #0bf13ef81725cfb4] @[simp, grind .] -theorem le_eq_of_sign_eq_true_of_sign_eq_true {x y : PackedFloat e s} (hxnan : ¬ x.isNaN) (hynan : ¬ y.isNaN) - (hxsign : x.sign = true) (hysign : y.sign = true) : - (x ≤ y) = ((y.ex.toInt < x.ex.toInt) ∨ (x.ex.toInt = y.ex.toInt ∧ y.sig.toNat ≤ x.sig.toNat)):= by +theorem le_eq_of_sign_eq_true_of_sign_eq_true {x y : PackedFloat e s} + (hxnan : ¬ x.isNaN := by solve | simp | grind) + (hynan : ¬ y.isNaN := by solve | simp | grind) + (hxsign : x.sign = true := by solve | simp | grind) + (hysign : y.sign = true := by solve | simp | grind) : + (x ≤ y) = ((y.ex.toNat < x.ex.toNat) ∨ (x.ex.toNat = y.ex.toNat ∧ y.sig.toNat ≤ x.sig.toNat)):= by rw [← PackedFloat.le_def, PackedFloat.le] - grind + grind only [BitVec.toNat_inj, #1f279ccc014b26d2] + +/-- +Every number is less than +∞ +-/ +theorem PackedFloat.le_getInfinity (hs : 0 < s) (y : PackedFloat e s) : + (y ≤ PackedFloat.getInfinity e s false) ↔ ¬ y.isNaN := by + by_cases hnan : y.isNaN + · simp [hnan] + grind only [→ not_isInfinite_of_isNaN, !isInfinite_getInfinity] + · simp [hnan] + rw [← PackedFloat.le_def, PackedFloat.le] + simp [hnan, hs] + by_cases hysign : y.sign + · simp [hysign] + · simp [hysign] + grind only [=> isNaN_iff_ex_eq_sig_eq, usr Nat.pow_pos, usr BitVec.isLt, + BitVec.eq_allOnes_iff_toNat_eq, = BitVec.toNat_ofNat, = BitVec.toNat_zero] + +@[simp, grind →] +theorem PackedFloat.eq_getInfinity_of_getInfinity_le (hs : 0 < s) (y : PackedFloat e s) + (hle : PackedFloat.getInfinity e s false ≤ y) : + y = .getInfinity e s false := by + have : (getInfinity e s false).sign = false := by grind + by_cases hysign : y.sign + · have : ¬ ((getInfinity e s false) ≤ y) := by grind only [le_iff_eq_of_isNaN, + not_le_of_sign_eq_false_of_sign_eq_true] + grind only + · simp at hysign + have hle' := hle + rw [le_eq_of_sign_eq_false_of_sign_eq_false] at hle' + simp at hle' + have : y.ex.toNat = 2 ^ e - 1 := by grind only [usr Nat.pow_pos, usr BitVec.isLt] + have : y.sig = 0#s := by grind only [le_iff_eq_of_isNaN', !isInfinite_getInfinity, + => isNaN_iff_ex_eq_sig_eq, eq_getInfinity_iff_isInfinity, → not_isNaN_of_isInfinite, + BitVec.eq_allOnes_iff_toNat_eq] + apply PackedFloat.ext <;> simp <;> grind + + +/-- +If a number is +infty, then only +infty is larger than it. +-/ +@[simp] +theorem PackedFloat.le_iff_eq_of_isInfinite_of_sign_eq_false (hs : 0 < s) + (y : PackedFloat e s) : + (PackedFloat.getInfinity e s false) ≤ y ↔ y = .getInfinity e s false := by + constructor + · intros h + grind only [→ eq_getInfinity_of_getInfinity_le] + · intros h + subst h + grind only [le_refl] + + +@[simp] +theorem le_iff_toExtRat'_le_toExtRat'_of_not_isZero (he : 0 < e) (hs : 0 < s) + (x y : PackedFloat e s) + (hxzero : ¬ x.isZero) (hyzero : ¬ y.isZero) (hxnan : ¬ x.isNaN) (hynan : ¬ y.isNaN) : + x ≤ y ↔ x.toExtRat' ≤ y.toExtRat' := by + -- rw [toExtRat', toExtRat'] + -- simp [hxzero, hyzero, hxnan, hynan] + by_cases hxinf : x.isInfinite + · simp [hxinf] + sorry + · by_cases hyinf : y.isInfinite + · simp [hyinf] + sorry + · sorry + @[simp] theorem le_zero_iff_sign_eq_true {x : PackedFloat e s} (he : 0 < e): - (x ≤ PackedFloat.getZero e s true) ↔ (x.sign = true ∧ ¬ x.isNaN ∧ x.ex.toInt ≥ 0) := by + (x ≤ PackedFloat.getZero e s true) ↔ (x.sign = true ∧ ¬ x.isNaN) := by by_cases hxNaN : x.isNaN · grind only [→ not_isZero_of_isNaN, PackedFloat.le_iff_eq_of_isNaN, isZero_getZero] · simp [hxNaN] @@ -1946,7 +2010,8 @@ theorem le_zero_iff_sign_eq_true {x : PackedFloat e s} (he : 0 < e): simp [hxNaN] simp [show ¬ e = 0 by grind] simp [hxsign] - have := BitVec.toInt_inj (x := x.ex) (y := 0#_) + have := BitVec.toNat_inj (x := x.ex) (y := 0#_) + simp at this grind · simp [hxsign] have : (getZero e s true).sign = true := by grind only [= sign_getZero] @@ -1954,7 +2019,7 @@ theorem le_zero_iff_sign_eq_true {x : PackedFloat e s} (he : 0 < e): @[simp] theorem zero_le_iff_sign_eq_false {x : PackedFloat e s} (he : 0 < e): - (PackedFloat.getZero e s false ≤ x) ↔ (x.sign = false ∧ ¬ x.isNaN ∧ 0 ≤ x.ex.toInt) := by + (PackedFloat.getZero e s false ≤ x) ↔ (x.sign = false ∧ ¬ x.isNaN) := by by_cases hxNaN : x.isNaN · simp [hxNaN] grind From b482717d86c8ee0ae16688e1254a19c17cbc46e0 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 16 Feb 2026 15:41:45 +0000 Subject: [PATCH 23/44] chore: prove theorems about infinity --- Fp/Basic.lean | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 2a56166..0ab9e72 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1933,7 +1933,8 @@ theorem le_eq_of_sign_eq_true_of_sign_eq_true {x y : PackedFloat e s} /-- Every number is less than +∞ -/ -theorem PackedFloat.le_getInfinity (hs : 0 < s) (y : PackedFloat e s) : +@[grind =>] +theorem PackedFloat.le_getInfinity_false_of_not_isNaN (hs : 0 < s) (y : PackedFloat e s) : (y ≤ PackedFloat.getInfinity e s false) ↔ ¬ y.isNaN := by by_cases hnan : y.isNaN · simp [hnan] @@ -1971,7 +1972,7 @@ theorem PackedFloat.eq_getInfinity_of_getInfinity_le (hs : 0 < s) (y : PackedFlo If a number is +infty, then only +infty is larger than it. -/ @[simp] -theorem PackedFloat.le_iff_eq_of_isInfinite_of_sign_eq_false (hs : 0 < s) +theorem PackedFloat.getInfinity_false_le_iff_eq (hs : 0 < s) (y : PackedFloat e s) : (PackedFloat.getInfinity e s false) ≤ y ↔ y = .getInfinity e s false := by constructor @@ -1982,6 +1983,34 @@ theorem PackedFloat.le_iff_eq_of_isInfinite_of_sign_eq_false (hs : 0 < s) grind only [le_refl] +@[grind =>] +theorem PackedFloat.getInfinity_true_le_of_not_isNaN (hs : 0 < s) (y : PackedFloat e s) : + (PackedFloat.getInfinity e s true ≤ y) ↔ ¬ y.isNaN := by + by_cases hnan : y.isNaN + · simp [hnan] + grind only [→ not_isInfinite_of_isNaN, !isInfinite_getInfinity] + · simp [hnan] + rw [← PackedFloat.le_def, PackedFloat.le] + simp [hnan, hs] + by_cases hysign : y.sign + · simp [hysign] + grind + · simp [hysign] + + +@[simp, grind =>] +theorem PackedFloat.le_getInfinity_true_iff_eq (hs : 0 < s) + (y : PackedFloat e s) : + y ≤ PackedFloat.getInfinity e s true ↔ y = .getInfinity e s true := by + constructor + · intros h + grind only [le_iff_eq_of_isNaN, le_iff_eq_of_isNaN', le_antisymm_of_ne_NaN, + => getInfinity_true_le_of_not_isNaN] + · intros h + subst h + grind only [le_refl] + + @[simp] theorem le_iff_toExtRat'_le_toExtRat'_of_not_isZero (he : 0 < e) (hs : 0 < s) (x y : PackedFloat e s) From e278dc9b7016f4b2f9cba301e8df9151f018923f Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Tue, 17 Feb 2026 11:32:52 +0000 Subject: [PATCH 24/44] chore: fixup --- Fp/Basic.lean | 86 +++++++++++++++++++++++++++++----- Fp/Theorems/UnpackedRound.lean | 4 +- 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 0ab9e72..17a9413 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1600,6 +1600,37 @@ def toNumberRat {e s} (pf : PackedFloat e s) : Rat := else pf.sign.toSign * (0 + pf.sig.toNat / 2 ^ s) * 2 ^ (-(bias e - 1 : Nat) : Int) +theorem toNumberRat_eq {e s} (pf : PackedFloat e s) : + pf.toNumberRat = if pf.isNorm then + pf.sign.toSign * (1 + pf.sig.toNat / 2 ^ s) * (2 : Rat) ^ (pf.ex.toNat - bias e : Int) + else + pf.sign.toSign * (0 + pf.sig.toNat / 2 ^ s) * (2 : Rat) ^ (-(bias e - 1 : Nat) : Int) := rfl + +@[simp] +theorem toNumberRat_eq_ofIsNorm {e s} (pf : PackedFloat e s) (hnorm : pf.isNorm := by solve | simp | grind) : + pf.toNumberRat = pf.sign.toSign * (1 + pf.sig.toNat / 2 ^ s) * (2 : Rat) ^ (pf.ex.toNat - bias e : Int) := by + rw [toNumberRat_eq, hnorm] + simp + +@[simp] +theorem toNumberRat_eq_of_not_isNorm {e s} (pf : PackedFloat e s) (hnorm : ¬ pf.isNorm := by solve | simp | grind) : + pf.toNumberRat = pf.sign.toSign * (0 + pf.sig.toNat / 2 ^ s) * (2 : Rat) ^ (-(bias e - 1 : Nat) : Int) := by + rw [toNumberRat_eq] + grind + +theorem toNumberRat_eq_Zero_of_isZero {e s} (pf : PackedFloat e s) (hp : pf.isZero) : + pf.toNumberRat = 0 := by + rw [toNumberRat_eq_of_not_isNorm (hnorm := by grind)] + simp [PackedFloat.isZero] at hp + obtain ⟨⟨he, hex⟩, hsig⟩ := hp + simp only [hsig, BitVec.toNat_ofNat, Nat.zero_mod, Rat.natCast_ofNat] + grind + +theorem isZero_of_toNumberRat_eq {e s} (pf : PackedFloat e s) (h : pf.toNumberRat = 0) : pf.isZero := by + rw [PackedFloat.toNumberRat] at h + · sorry + + private theorem Rat.mul_ne_zero_iff {x y : Rat} : (¬ (x * y = 0)) ↔ x ≠ 0 ∧ y ≠ 0 := by grind @@ -1701,7 +1732,7 @@ theorem sign_iff_toNumberRat_neg {pf : PackedFloat e s} (h : pf.isNormOrNonzeroS · grind @[simp] -theorem toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm (pf : PackedFloat e s) (hp : pf.isNormOrNonzeroSubnorm := by grind) : +theorem toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm {pf : PackedFloat e s} (hp : pf.isNormOrNonzeroSubnorm := by grind) : pf.toExtRat' = .Number pf.toNumberRat := by have hnan : pf.isNaN = false := by @@ -1715,7 +1746,7 @@ theorem toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm (pf : PackedFloat e s) (hp @[simp, grind! .] -theorem toExtRat'_getInfinity (sign : Bool) (hs : 0 < s := by grind) : +theorem toExtRat'_getInfinity {sign : Bool} (hs : 0 < s := by grind) : (PackedFloat.getInfinity e s sign).toExtRat' = .Infinity sign := by have : (PackedFloat.getInfinity e s sign).isInfinite = true := by grind @@ -2011,21 +2042,54 @@ theorem PackedFloat.le_getInfinity_true_iff_eq (hs : 0 < s) grind only [le_refl] +theorem eq_of_toExtRat'_eq (x y : PackedFloat e s) + (hx : ¬ x.isNaN) (hy : ¬ y.isNaN) (hxzero : ¬ x.isZero) (hyzero : ¬ y.isZero) + (h : x.toExtRat' = y.toExtRat') : x = y := by + simp [toExtRat'] at h + simp [hx, hy] at h + by_cases hxinf : x.isInfinite + · simp [hxinf] at h + by_cases hyinf : y.isInfinite + · simp [hyinf] at h + grind only [PackedFloat.eq_getInfinity_iff_isInfinity, → eq_mkInfinity_of_isInfinite, + !isInfinite_getInfinity, #5505cb0d9cd21b53] + · by_cases hyinf : y.isInfinite + · simp [hyinf] at h + grind only + · simp [hyinf] at h + grind only [PackedFloat.eq_getInfinity_iff_isInfinity, → eq_mkInfinity_of_isInfinite, + !isInfinite_getInfinity, !toExtRat'_getInfinity] + · simp [hxinf] at h + by_cases hyinf : y.isInfinite + · simp [hyinf] at h + grind only + · simp [hyinf] at h + simp [hxzero] at h + simp [hyzero] at h + by_cases hx : x.isNorm + · simp [hx] at h + by_cases hy : y.isNorm + · simp [hy] at h + sorry + · simp [hy] at h + sorry + · simp [hx] at h + by_cases hy : y.isNorm + · simp [hy] at h + sorry + · simp [hy] at h + sorry + @[simp] theorem le_iff_toExtRat'_le_toExtRat'_of_not_isZero (he : 0 < e) (hs : 0 < s) (x y : PackedFloat e s) (hxzero : ¬ x.isZero) (hyzero : ¬ y.isZero) (hxnan : ¬ x.isNaN) (hynan : ¬ y.isNaN) : x ≤ y ↔ x.toExtRat' ≤ y.toExtRat' := by - -- rw [toExtRat', toExtRat'] - -- simp [hxzero, hyzero, hxnan, hynan] - by_cases hxinf : x.isInfinite - · simp [hxinf] + constructor + · intros h + sorry + · intros h sorry - · by_cases hyinf : y.isInfinite - · simp [hyinf] - sorry - · sorry - @[simp] theorem le_zero_iff_sign_eq_true {x : PackedFloat e s} (he : 0 < e): diff --git a/Fp/Theorems/UnpackedRound.lean b/Fp/Theorems/UnpackedRound.lean index 01319e5..d71e072 100644 --- a/Fp/Theorems/UnpackedRound.lean +++ b/Fp/Theorems/UnpackedRound.lean @@ -248,7 +248,9 @@ theorem mul_eq_mul {ein sin : Nat} (hsin : 0 < sin) (he : 0 < ein) simp [this] rw [← ExtRat.mul_def, ExtRat.mul] simp only [SmtLibSemantics.instExtendedRat, SmtLibSemantics.instExtendedRat.eq_1, roundQ_eq] - simp [show a.toNumberRat < 0 ↔ a.sign = true by sorry] + simp [show a.toNumberRat < 0 ↔ a.sign = true by grind only [→ + PackedFloat.sign_iff_toNumberRat_neg, + #34bd]] simp [show a.toNumberRat = 0 ↔ a.isZero by sorry] simp [show ¬ a.isZero by grind] rw [roundQ_eq_round_of_Infinity] From 4827de01076babbae311371ff1dcf1a1d6a64067 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Tue, 17 Feb 2026 12:52:55 +0000 Subject: [PATCH 25/44] chore: change defn of toNumber to be much more workable, but this needs an update of proofs in Packing, which I will do next --- Fp/Basic.lean | 302 ++++++++++++++++++++++++-------------------------- 1 file changed, 143 insertions(+), 159 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 17a9413..9769cd9 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1469,10 +1469,19 @@ def ExtDyadic.toExtRat (ed : ExtDyadic) : ExtRat := def Bool.toSign (b : Bool) : Int := if b then -1 else 1 +@[simp] +theorem toSign_true : Bool.toSign true = -1 := rfl + +@[simp] +theorem toSign_false : Bool.toSign false = 1 := rfl + @[simp] theorem Bool.toSign_ne_zero (b : Bool) : b.toSign ≠ 0 := by cases b <;> simp [Bool.toSign] +theorem Bool.toSign_lt_zero_iff (b : Bool) : b.toSign < 0 ↔ b = true := by + cases b <;> simp [Bool.toSign] + @[bv_normalize] def bias (e : Nat) : Nat := 2 ^ (e - 1) - 1 @@ -1559,77 +1568,20 @@ instance {x y : PackedFloat e s} : Decidable (x ≤ y) := by simp only [← PackedFloat.le_def] infer_instance -def toExtRat' (pf : PackedFloat e s) : ExtRat := - bif pf.isNaN then - .NaN - else bif pf.isInfinite then - .Infinity pf.sign - else bif pf.isZero then - .Number 0 - else bif pf.isNorm then - .Number (pf.sign.toSign * (1 + pf.sig.toNat / 2 ^ s) * 2 ^ (pf.ex.toNat - bias e : Int)) - else - -- `-(bias e - 1)` is slightly different from SMT-LIB standard `1 - bias e` to allow `e = 1`. - .Number (pf.sign.toSign * (0 + pf.sig.toNat / 2 ^ s) * 2 ^ (-(bias e - 1 : Nat) : Int)) - - - -@[simp] -theorem toExtRat'_eq_zero_of_isZero (pf : PackedFloat e s) (hp : pf.isZero) : - pf.toExtRat' = .Number 0 := by - have hnan : pf.isNaN = false := by - grind [isNaN, isZero] - have hinf : pf.isInfinite = false := by - grind [isInfinite, isZero] - simp [toExtRat', hp, hnan, hinf] - -@[simp] -theorem toExtRat'_eq_NaN_of_isNaN (pf : PackedFloat e s) (hp : pf.isNaN) : - pf.toExtRat' = .NaN := by - simp [toExtRat', hp] - -@[simp] -theorem toExtRat'_eq_Infinity_of_isInfinite (pf : PackedFloat e s) (hp : pf.isInfinite) : - pf.toExtRat' = .Infinity pf.sign := by - rw [toExtRat', hp] - grind [not_isNaN_of_isInfinite] - -def toNumberRat {e s} (pf : PackedFloat e s) : Rat := +def toNumberRatSig {e s} (pf : PackedFloat e s) : Rat := if pf.isNorm then - pf.sign.toSign * (1 + pf.sig.toNat / 2 ^ s) * 2 ^ (pf.ex.toNat - bias e : Int) + 1 + pf.sig.toNat / 2 ^ s else - pf.sign.toSign * (0 + pf.sig.toNat / 2 ^ s) * 2 ^ (-(bias e - 1 : Nat) : Int) + 0 + pf.sig.toNat / 2 ^ s -theorem toNumberRat_eq {e s} (pf : PackedFloat e s) : - pf.toNumberRat = if pf.isNorm then - pf.sign.toSign * (1 + pf.sig.toNat / 2 ^ s) * (2 : Rat) ^ (pf.ex.toNat - bias e : Int) +def toNumberRatExp {e s} (pf : PackedFloat e s) : Int := + if pf.isNorm then + pf.ex.toNat - bias e else - pf.sign.toSign * (0 + pf.sig.toNat / 2 ^ s) * (2 : Rat) ^ (-(bias e - 1 : Nat) : Int) := rfl - -@[simp] -theorem toNumberRat_eq_ofIsNorm {e s} (pf : PackedFloat e s) (hnorm : pf.isNorm := by solve | simp | grind) : - pf.toNumberRat = pf.sign.toSign * (1 + pf.sig.toNat / 2 ^ s) * (2 : Rat) ^ (pf.ex.toNat - bias e : Int) := by - rw [toNumberRat_eq, hnorm] - simp - -@[simp] -theorem toNumberRat_eq_of_not_isNorm {e s} (pf : PackedFloat e s) (hnorm : ¬ pf.isNorm := by solve | simp | grind) : - pf.toNumberRat = pf.sign.toSign * (0 + pf.sig.toNat / 2 ^ s) * (2 : Rat) ^ (-(bias e - 1 : Nat) : Int) := by - rw [toNumberRat_eq] - grind - -theorem toNumberRat_eq_Zero_of_isZero {e s} (pf : PackedFloat e s) (hp : pf.isZero) : - pf.toNumberRat = 0 := by - rw [toNumberRat_eq_of_not_isNorm (hnorm := by grind)] - simp [PackedFloat.isZero] at hp - obtain ⟨⟨he, hex⟩, hsig⟩ := hp - simp only [hsig, BitVec.toNat_ofNat, Nat.zero_mod, Rat.natCast_ofNat] - grind - -theorem isZero_of_toNumberRat_eq {e s} (pf : PackedFloat e s) (h : pf.toNumberRat = 0) : pf.isZero := by - rw [PackedFloat.toNumberRat] at h - · sorry + -(bias e - 1 : Nat) +def toNumberRat {e s} (pf : PackedFloat e s) : Rat := + pf.sign.toSign * pf.toNumberRatSig * 2 ^ (pf.toNumberRatExp) private theorem Rat.mul_ne_zero_iff {x y : Rat} : (¬ (x * y = 0)) ↔ x ≠ 0 ∧ y ≠ 0 := by grind @@ -1641,95 +1593,101 @@ private theorem Rat.ne_zero_of_zero_lt {r : Rat} (h : 0 < r) : r ≠ 0 := by private theorem Rat.zero_add {x : Rat} : 0 + x = x := by grind +@[simp] +theorem toNumberRatSig_eq_zero_of_isZero {e s} (pf : PackedFloat e s) (hzero : pf.isZero := by grind) : + pf.toNumberRatSig = 0 := by + simp [toNumberRatSig] + have hnorm : ¬ pf.isNorm := by grind + simp [hnorm] + have : pf.sig = 0#s := by grind + simp only [this, BitVec.toNat_ofNat, Nat.zero_mod, Rat.natCast_ofNat] + grind + +@[simp, grind .] +theorem zero_le_toNumberRatSig {e s} (pf : PackedFloat e s) : + 0 ≤ pf.toNumberRatSig := by + simp [toNumberRatSig] + by_cases hnorm : pf.isNorm + · simp [hnorm] + have : (pf.sig.toNat : Rat) / (2 : Rat) ^ s ≥ 0 := by grind only [Fp.Rat.div_nonneg, + Rat.pow_nonneg] + have : (1 + pf.sig.toNat / 2^s) ≥ 0 := by grind + grind + · simp [hnorm] + have : (pf.sig.toNat : Rat) / (2 : Rat) ^ s ≥ 0 := by grind + have : (0 + pf.sig.toNat / 2^s) ≥ 0 := by grind + grind + @[grind . ] theorem sig_ne_zero_of_isNormOrNonzeroSubnorm_of_not_isNorm {pf : PackedFloat e s} (h : pf.isNormOrNonzeroSubnorm) (hnorm : ¬ pf.isNorm) : - pf.sig.toNat ≠ 0 := by + pf.sig ≠ 0#s := by simp [isNorm] at hnorm simp [isNormOrNonzeroSubnorm] at h grind -theorem toNumberRat_ne_zero {pf : PackedFloat e s} (h : pf.isNormOrNonzeroSubnorm) : - pf.toNumberRat ≠ 0 := by - simp [toNumberRat] - split - case isTrue => - have : pf.sign.toSign ≠ 0 := by grind +attribute [grind .] Rat.natCast_eq_zero_iff + +@[simp] +theorem toNumberRatSig_ne_zero_of_isNormOrNonzeroSubnorm {pf : PackedFloat e s} (h : pf.isNormOrNonzeroSubnorm) : + pf.toNumberRatSig ≠ 0 := by + simp [toNumberRatSig] + by_cases hnorm : pf.isNorm + · simp [hnorm] have : (pf.sig.toNat : Rat) / (2 : Rat) ^ s ≥ 0 := by grind only [Fp.Rat.div_nonneg, Rat.pow_nonneg] - have : 1 + (pf.sig.toNat : Rat) / (2 : Rat) ^ s > 0 := by grind only - have : (2 : Rat) ^ (pf.ex.toNat - bias e : Int) > 0 := by grind only [Fp.Rat.two_pow_pos] - simp only [ne_eq] - rw [Rat.mul_ne_zero_iff] - simp only [ne_eq] - rw [Rat.mul_ne_zero_iff] - simp - grind only [#e92ae97e031d7a23] - case isFalse hnorm => - rw [Rat.mul_ne_zero_iff] simp only [ne_eq] - rw [Rat.mul_ne_zero_iff] - simp only [ne_eq, Rat.intCast_eq_zero_iff, Bool.toSign_ne_zero, not_false_eq_true, true_and] - simp only [isNormOrNonzeroSubnorm, BitVec.zero_eq, Bool.and_eq_true, bne_iff_ne, ne_eq, - Bool.or_eq_true] at h - simp [isNorm] at hnorm - have : pf.sig ≠ 0#s := by grind + grind + · simp [hnorm] + have := sig_ne_zero_of_isNormOrNonzeroSubnorm_of_not_isNorm h (hnorm := by grind) have : pf.sig.toNat ≠ 0 := by grind - constructor - · apply Rat.ne_zero_of_zero_lt - -- | TODO: extract into theorem. - have : (pf.sig.toNat : Rat) ≠ 0 := by - intros hcontra - rw [Rat.natCast_eq_zero_iff] at hcontra - grind only - grind only [Fp.Rat.div_pos, Rat.pow_pos] - · apply Rat.ne_zero_of_zero_lt - grind only [Fp.Rat.two_pow_pos] + have : (pf.sig.toNat : Rat) ≠ 0 := by + grind only [Rat.natCast_eq_zero_iff] + grind only [Fp.Rat.div_pos, Rat.pow_pos] +@[simp, grind =] +theorem toNumberRat_eq_Zero_of_isZero {e s} (pf : PackedFloat e s) (hp : pf.isZero) : + pf.toNumberRat = 0 := by + rw [toNumberRat] + have : pf.toNumberRatSig = 0 := by exact toNumberRatSig_eq_zero_of_isZero pf hp + grind + +@[simp] +theorem Rat.natCast_ne_zero_iff {n : Nat} : ((n : Rat) ≠ 0) ↔ n ≠ 0 := by + grind + +theorem toNumberRat_ne_zero {pf : PackedFloat e s} (h : pf.isNormOrNonzeroSubnorm) : + pf.toNumberRat ≠ 0 := by + simp [toNumberRat] + have : pf.sign.toSign ≠ 0 := by grind only [Bool.toSign, #26b7] + have : pf.toNumberRatSig ≠ 0 := by exact toNumberRatSig_ne_zero_of_isNormOrNonzeroSubnorm h + have : (2 : Rat) ^ (pf.toNumberRatExp) > 0 := by grind only [Fp.Rat.two_pow_pos] + rw [Rat.mul_ne_zero_iff] + simp only [ne_eq] + rw [Rat.mul_ne_zero_iff] + constructor + · constructor + · simp + · grind only + · grind only @[simp, grind →, grind =] theorem sign_iff_toNumberRat_neg {pf : PackedFloat e s} (h : pf.isNormOrNonzeroSubnorm) : pf.sign = decide (pf.toNumberRat < 0) := by - have hnum : pf.toNumberRat ≠ 0 := toNumberRat_ne_zero h - simp [toNumberRat, Bool.toSign] at hnum ⊢ - by_cases hnorm : pf.isNorm - · simp [hnorm] at hnum ⊢ - have : (pf.sig.toNat : Rat) ≥ 0 := by grind - have : (2 : Rat) ^s ≥ 0 := by grind - have : (pf.sig.toNat : Rat) / 2^s ≥ 0 := by grind - have : (1 + pf.sig.toNat / 2^s) ≥ 0 := by grind - have : (2 : Rat) ^ ((pf.ex.toNat : Int) - (bias e : Int)) > 0 := by grind - have : (1 + pf.sig.toNat / 2^s) * (2 : Rat) ^ ((pf.ex.toNat : Int) - (bias e : Int)) > 0 := by - apply Rat.mul_pos <;> grind - rw [Rat.mul_assoc] - by_cases hsign : pf.sign - · simp [hsign] - rw [Rat.mul_neg_iff_of_pos_right] - · grind - · grind - · simp [hsign] - apply Rat.mul_nonneg - · grind - · grind - · simp [hnorm] at hnum ⊢ - have : (pf.sig.toNat : Rat) ≥ 0 := by grind - have : (2 : Rat) ^s > 0 := by grind - have : (pf.sig.toNat : Rat) / 2^s ≥ 0 := by grind -- grind - have : ¬ ((pf.sig.toNat : Rat) / 2 ^ s = 0) := by - intros hcontra - rw [hcontra] at hnum - simp [Rat.mul_zero, Rat.zero_mul] at hnum - have : (0 + pf.sig.toNat / 2^s) * (2 : Rat) ^ (-(bias e - 1 : Nat) : Int) > 0 := by - apply Rat.mul_pos <;> grind - rw [Rat.mul_assoc] - by_cases hsign : pf.sign - · simp [hsign] - rw [Rat.mul_neg_iff_of_pos_right] - · grind - · grind - · simp [hsign] - apply Rat.mul_nonneg - · grind - · grind + rw [toNumberRat] + have : pf.toNumberRatSig ≠ 0 := by + exact toNumberRatSig_ne_zero_of_isNormOrNonzeroSubnorm h + have : 0 ≤ pf.toNumberRatSig := by exact zero_le_toNumberRatSig pf + have : (2 : Rat) ^ pf.toNumberRatExp > 0 := by grind only [Fp.Rat.two_pow_pos] + by_cases hsign : pf.sign <;> simp [hsign] <;> grind + + +def toExtRat' (pf : PackedFloat e s) : ExtRat := + bif pf.isNaN then + .NaN + else bif pf.isInfinite then + .Infinity pf.sign + else .Number pf.toNumberRat + @[simp] theorem toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm {pf : PackedFloat e s} (hp : pf.isNormOrNonzeroSubnorm := by grind) : @@ -1741,9 +1699,30 @@ theorem toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm {pf : PackedFloat e s} (hp grind [isInfinite, isNormOrNonzeroSubnorm] have hzero : pf.isZero = false := by grind [isZero, isNormOrNonzeroSubnorm] - simp [toExtRat', hnan, hinf, hzero, toNumberRat] + simp [toExtRat', hnan, hinf, toNumberRat] + + +@[simp] +theorem toExtRat'_eq_zero_of_isZero (pf : PackedFloat e s) (hp : pf.isZero) : + pf.toExtRat' = .Number 0 := by + have hnan : pf.isNaN = false := by + grind [isNaN, isZero] + have hinf : pf.isInfinite = false := by + grind [isInfinite, isZero] + simp only [toExtRat', hnan, hinf, cond_false, ExtRat.Number.injEq] grind +@[simp] +theorem toExtRat'_eq_NaN_of_isNaN (pf : PackedFloat e s) (hp : pf.isNaN) : + pf.toExtRat' = .NaN := by + simp [toExtRat', hp] + +@[simp] +theorem toExtRat'_eq_Infinity_of_isInfinite (pf : PackedFloat e s) (hp : pf.isInfinite) : + pf.toExtRat' = .Infinity pf.sign := by + rw [toExtRat', hp] + grind [not_isNaN_of_isInfinite] + @[simp, grind! .] theorem toExtRat'_getInfinity {sign : Bool} (hs : 0 < s := by grind) : @@ -2028,6 +2007,8 @@ theorem PackedFloat.getInfinity_true_le_of_not_isNaN (hs : 0 < s) (y : PackedFlo grind · simp [hysign] +theorem PackedFloat.sign_eq_of_toNumberRat_eq {x y : PackedFloat e s} (heq : x.toNumberRat = y.toNumberRat) : x.sign = y.sign := by + sorry @[simp, grind =>] theorem PackedFloat.le_getInfinity_true_iff_eq (hs : 0 < s) @@ -2057,28 +2038,31 @@ theorem eq_of_toExtRat'_eq (x y : PackedFloat e s) · simp [hyinf] at h grind only · simp [hyinf] at h - grind only [PackedFloat.eq_getInfinity_iff_isInfinity, → eq_mkInfinity_of_isInfinite, - !isInfinite_getInfinity, !toExtRat'_getInfinity] · simp [hxinf] at h by_cases hyinf : y.isInfinite · simp [hyinf] at h - grind only · simp [hyinf] at h - simp [hxzero] at h - simp [hyzero] at h - by_cases hx : x.isNorm - · simp [hx] at h - by_cases hy : y.isNorm - · simp [hy] at h - sorry - · simp [hy] at h - sorry - · simp [hx] at h - by_cases hy : y.isNorm - · simp [hy] at h - sorry - · simp [hy] at h - sorry + + -- 1. signs are equal if toNumberRat is equal. + -- 2. Then, sig * exp are equal. However, because these have different 'ranges', it must be that sig and exp are separately equal? + -- How to make this formal? Is this true? + sorry + + -- simp [hxzero] at h + -- simp [hyzero] at h + -- by_cases hx : x.isNorm + -- · simp [hx] at h + -- by_cases hy : y.isNorm + -- · simp [hy] at h + -- sorry + -- · simp [hy] at h + -- sorry + -- · simp [hx] at h + -- by_cases hy : y.isNorm + -- · simp [hy] at h + -- sorry + -- · simp [hy] at h + -- sorry @[simp] theorem le_iff_toExtRat'_le_toExtRat'_of_not_isZero (he : 0 < e) (hs : 0 < s) From 6c8b7b395e76beb05f1cefd047ccd9332d080cec Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Tue, 17 Feb 2026 13:49:21 +0000 Subject: [PATCH 26/44] chore: cleanup packing proof --- Fp/Theorems/Packing.lean | 47 +++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/Fp/Theorems/Packing.lean b/Fp/Theorems/Packing.lean index b7d19cb..d85994e 100644 --- a/Fp/Theorems/Packing.lean +++ b/Fp/Theorems/Packing.lean @@ -289,6 +289,10 @@ theorem exp_lt_max_of_isNorm {pf : PackedFloat e s} : pf.isNorm → pf.ex.toNat < 2 ^ e - 1 := by grind [isNorm, BitVec.allOnes, BitVec.ofNatLT_toNat] +theorem Rat.zpow_sub {q : Rat} (hq : q ≠ 0) {a b : Int} : q ^ (a - b) = q ^ a * q ^ (-b) := by + rw [Int.sub_eq_add_neg] + rw [Rat.zpow_add hq] + @[simp] theorem toExtRat_eq_toExtRat' {pf : PackedFloat e s} : pf.toExtRat = pf.toExtRat' := by @@ -296,11 +300,12 @@ theorem toExtRat_eq_toExtRat' {pf : PackedFloat e s} cases hInf : pf.isInfinite <;> cases hZero : pf.isZero <;> cases hNorm : pf.isNorm <;> - simp only [toExtRat, toExtDyadic, ExtDyadic.toExtRat, toExtRat', hNaN, hInf, hZero, hNorm, cond_true, - cond_false, Dyadic.toRat_zero] + all_goals simp only [toExtRat, toExtDyadic, ExtDyadic.toExtRat, toExtRat', hNaN, hInf, hZero, hNorm, cond_true, + cond_false, Dyadic.toRat_zero, PackedFloat.toNumberRat, PackedFloat.toNumberRatSig, PackedFloat.toNumberRatExp, Bool.false_eq_true, if_false, if_true] all_goals simp only [Dyadic.toRat_ofIntWithPrec_eq_mul_two_pow, ExtRat.Number.injEq, Int.neg_add, Int.neg_sub, Bool.apply_cond] - all_goals (simp only [BitVec.toInt_setWidth'_of_lt (Nat.lt_succ_self (s + 1)), Rat.intCast_natCast, BitVec.toInt_neg_eq_of_msb (BitVec.msb_setWidth'_of_lt (Nat.lt_succ_self (s + 1))), BitVec.toNat_cons', Bool.toNat, cond_false, cond_true, Nat.zero_shiftLeft, Nat.one_shiftLeft, Nat.zero_add, Int.natCast_add, Rat.intCast_neg, Rat.intCast_add, Rat.natCast_pow, Rat.natCast_ofNat]) + -- all_goals (simp only [PackedFloat.toNumberRat, PackedFloat.toNumberRatSig, PackedFloat.toNumberRatExp, if_true, if_false, hNorm, Bool.false_eq_true]) + all_goals (try simp only [BitVec.toInt_setWidth'_of_lt (Nat.lt_succ_self (s + 1)), Rat.intCast_natCast, BitVec.toInt_neg_eq_of_msb (BitVec.msb_setWidth'_of_lt (Nat.lt_succ_self (s + 1))), BitVec.toNat_cons', Bool.toNat, cond_false, cond_true, Nat.zero_shiftLeft, Nat.one_shiftLeft, Nat.zero_add, Int.natCast_add, Rat.intCast_neg, Rat.intCast_add, Rat.natCast_pow, Rat.natCast_ofNat]) all_goals (cases pf.sign <;> simp only [cond_false, cond_true, Bool.toSign, if_true, Bool.false_eq_true, if_false, Rat.intCast_ofNat, Rat.intCast_neg, Rat.zero_add, Rat.one_mul, Rat.neg_mul]) all_goals (rewrite [Rat.div_def]) all_goals (try rewrite [← Rat.zpow_natCast, ← Rat.zpow_neg]) @@ -318,19 +323,27 @@ theorem toExtRat_eq_toExtRat' {pf : PackedFloat e s} congr 1 · simp only [← Rat.zpow_add (q := 2) (hq := by decide)] congr 1 - grind + grind only · rw [Rat.zpow_add (q := 2) (hq := by decide)] - grind - · simp only [Rat.add_mul] - ac_nf - simp + grind only + · -- TODO: disgusting, write a solver for this that does power-of-2 simplification. + simp only [Rat.add_mul] + simp only [Rat.zpow_add (q := 2) (hq := by decide)] + simp only [Rat.zpow_sub (q := 2) (hq := by decide)] + simp only [Rat.one_mul] simp only [← Rat.zpow_add (q := 2) (hq := by decide)] norm_cast congr 2 · congr 1 - grind + grind only · rw [Rat.zpow_add (q := 2) (hq := by decide)] - grind + grind only + · grind only [→ eq_mkZero_of_isZero, Rat.natCast_eq_zero_iff, = sig_getZero, + = BitVec.ofNat_eq_ofNat, = BitVec.toNat_zero, #a5422ce67b0854c8] + · grind only [→ eq_mkZero_of_isZero, Rat.natCast_eq_zero_iff, = sig_getZero, + = BitVec.ofNat_eq_ofNat, = BitVec.toNat_zero, #a5422ce67b0854c8] + · grind only [→ not_isNorm_of_isZero] + · grind only [→ not_isNorm_of_isZero] theorem bias_fits₁ : -2 ^ (exponentWidth e s - 1) ≤ (bias e : Int) := by apply Int.le_trans (b := 0) @@ -357,6 +370,8 @@ theorem minNormalExp_fits₂ : minNormalExp e < 2 ^ (exponentWidth e s - 1) := b theorem exponentWidth_gt_zero : exponentWidth e s > 0 := by simp [exponentWidth] +-- | TODO: refactor by pulling out lemmas that talk about the 'toNat' of the various +-- significand, and so on. theorem toExtRat_unpack_eq_toExtRat {pf : PackedFloat e s} : pf.unpack.toExtRat = pf.toExtRat := by simp only [unpack, unpackNormOrNonzeroSubnorm, BitVec.truncate_eq_setWidth, toExtRat_eq_toExtRat'] @@ -366,11 +381,13 @@ theorem toExtRat_unpack_eq_toExtRat {pf : PackedFloat e s} · cases hNorm : pf.isNorm · simp only [EUnpackedFloat.toExtRat, Bool.false_eq_true, ↓reduceIte, cond_false, EUnpackedFloat.isNaN_mkNumber, EUnpackedFloat.isInfinite_mkNumber, - EUnpackedFloat.num_mkNumber, toExtRat', hNaN, hInf, hZero, hNorm, ExtRat.Number.injEq] + EUnpackedFloat.num_mkNumber, toExtRat', hNaN, hInf, ExtRat.Number.injEq] + simp only [PackedFloat.toNumberRat, PackedFloat.toNumberRatSig, PackedFloat.toNumberRatExp] + simp [hNorm] rewrite [UnpackedFloat.toRat_normalize_eq_toRat UnpackedFloat.sigWidth_lt_exponentWidth_sub_one] · simp only [UnpackedFloat.toRat_eq, Rat.mul_assoc] congr 1 - simp only [BitVec.toNat_cons, Bool.toNat_false, Nat.zero_shiftLeft, Nat.zero_or, Rat.zero_add, Rat.div_def, Rat.mul_assoc] + simp only [BitVec.toNat_cons, Bool.toNat_false, Nat.zero_shiftLeft, Nat.zero_or, Rat.div_def, Rat.mul_assoc] congr simp only [← Rat.zpow_natCast, ← Rat.zpow_neg, ← @Rat.zpow_add 2 (by decide)] simp only [BitVec.toInt_ofInt_eq_self exponentWidth_gt_zero minNormalExp_fits₁ minNormalExp_fits₂] @@ -417,10 +434,13 @@ theorem toExtRat_unpack_eq_toExtRat {pf : PackedFloat e s} norm_cast simp · simp [Int.pow_pos] - · simp only [EUnpackedFloat.toExtRat, ↓reduceIte, cond_false, + · + simp only [EUnpackedFloat.toExtRat, ↓reduceIte, cond_false, EUnpackedFloat.isNaN_mkNumber, EUnpackedFloat.isInfinite_mkNumber, EUnpackedFloat.num_mkNumber, toExtRat', hNaN, hInf, hZero, hNorm, cond_true, ExtRat.Number.injEq] + simp only [PackedFloat.toNumberRat, PackedFloat.toNumberRatSig, PackedFloat.toNumberRatExp, + hZero, hNorm, if_true] simp only [UnpackedFloat.toRat_eq, Rat.mul_assoc] congr 1 simp only [BitVec.toNat_cons', Nat.shiftLeft_eq, Bool.toNat, cond_true] @@ -464,6 +484,7 @@ theorem toExtRat_unpack_eq_toExtRat {pf : PackedFloat e s} · simp only [EUnpackedFloat.toExtRat, cond_true, cond_false, EUnpackedFloat.mkZero_not_isNaN, EUnpackedFloat.mkZero_not_isInfinite, toExtRat', hNaN, hInf, hZero] simp [EUnpackedFloat.mkZero] + simp [hZero] · simp only [EUnpackedFloat.toExtRat, cond_true, cond_false, EUnpackedFloat.mkInfinity_not_isNaN, EUnpackedFloat.mkInfinity_isInfinite, toExtRat', hNaN, hInf] From 5a145010523829314dd5aab89204ae923654c7a9 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Tue, 17 Feb 2026 15:41:35 +0000 Subject: [PATCH 27/44] chore: tehcnical lemma --- Fp/Basic.lean | 110 +++++++++++++++++++++++++++++++++++++++++++++----- Fp/Utils.lean | 37 +++++++++++++++++ 2 files changed, 136 insertions(+), 11 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 9769cd9..49ab563 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -625,10 +625,13 @@ theorem isZeroOrSubnorm_of_isZero {pf : PackedFloat e s} : pf.isZero → pf.isZeroOrSubnorm := by grind [isZero, isZeroOrSubnorm] +@[grind →] theorem isZeroOrSubnorm_of_isNonzeroSubnorm {pf : PackedFloat e s} : pf.isNonzeroSubnorm → pf.isZeroOrSubnorm := by grind [isNonzeroSubnorm, isZeroOrSubnorm] + + @[grind →] theorem isZero_of_isNZero {pf : PackedFloat e s} : pf.isNZero → pf.isZero := by @@ -1574,6 +1577,43 @@ def toNumberRatSig {e s} (pf : PackedFloat e s) : Rat := else 0 + pf.sig.toNat / 2 ^ s +theorem toNumberRatSig_lt_one_of_not_isNorm {e s} (pf : PackedFloat e s) (hnorm : ¬ pf.isNorm) : + pf.toNumberRatSig < 1 := by + simp [toNumberRatSig, hnorm] + have : (pf.sig.toNat : Rat) / (2 : Rat) ^ s ≥ 0 := by grind + have : pf.sig.toNat < 2^s := by grind + apply Rat.div_lt_iff .. |>.mpr + · simp + norm_cast + · grind => instantiate only [Rat.pow_pos] + +theorem toNumberRatSig_lt_two_of_not_isNorm {e s} (pf : PackedFloat e s) (hnorm : pf.isNorm): + pf.toNumberRatSig < 2 := by + simp [toNumberRatSig, hnorm] + have : (pf.sig.toNat : Rat) / (2 : Rat) ^ s ≥ 0 := by grind + have : pf.sig.toNat < 2^s := by grind + suffices (pf.sig.toNat : Rat) / (2 : Rat) ^ s < 1 from by + grind + apply Rat.div_lt_iff .. |>.mpr + · simp + norm_cast + · grind => instantiate only [Rat.pow_pos] + +@[grind! .] +theorem toNumberRatSig_lt_ite {e s} (pf : PackedFloat e s) : + pf.toNumberRatSig < 1 + pf.isNorm.toNat := by + by_cases hnorm : pf.isNorm + · simp [hnorm]; grind [toNumberRatSig_lt_two_of_not_isNorm pf hnorm] + · simp [hnorm]; grind [toNumberRatSig_lt_one_of_not_isNorm pf hnorm] + +@[grind .] +theorem toNumberRatSig_lt_two {e s} (pf : PackedFloat e s) : + pf.toNumberRatSig < 2 := by + have := toNumberRatSig_lt_ite pf + by_cases hnorm : pf.isNorm + · grind [toNumberRatSig_lt_two_of_not_isNorm pf hnorm] + · grind [toNumberRatSig_lt_one_of_not_isNorm pf hnorm] + def toNumberRatExp {e s} (pf : PackedFloat e s) : Int := if pf.isNorm then pf.ex.toNat - bias e @@ -1583,15 +1623,6 @@ def toNumberRatExp {e s} (pf : PackedFloat e s) : Int := def toNumberRat {e s} (pf : PackedFloat e s) : Rat := pf.sign.toSign * pf.toNumberRatSig * 2 ^ (pf.toNumberRatExp) -private theorem Rat.mul_ne_zero_iff {x y : Rat} : (¬ (x * y = 0)) ↔ x ≠ 0 ∧ y ≠ 0 := by - grind - -private theorem Rat.ne_zero_of_zero_lt {r : Rat} (h : 0 < r) : r ≠ 0 := by - grind - -@[simp] -private theorem Rat.zero_add {x : Rat} : 0 + x = x := by - grind @[simp] theorem toNumberRatSig_eq_zero_of_isZero {e s} (pf : PackedFloat e s) (hzero : pf.isZero := by grind) : @@ -1627,7 +1658,7 @@ theorem sig_ne_zero_of_isNormOrNonzeroSubnorm_of_not_isNorm {pf : PackedFloat e attribute [grind .] Rat.natCast_eq_zero_iff -@[simp] +@[simp, grind .] theorem toNumberRatSig_ne_zero_of_isNormOrNonzeroSubnorm {pf : PackedFloat e s} (h : pf.isNormOrNonzeroSubnorm) : pf.toNumberRatSig ≠ 0 := by simp [toNumberRatSig] @@ -2007,9 +2038,34 @@ theorem PackedFloat.getInfinity_true_le_of_not_isNaN (hs : 0 < s) (y : PackedFlo grind · simp [hysign] -theorem PackedFloat.sign_eq_of_toNumberRat_eq {x y : PackedFloat e s} (heq : x.toNumberRat = y.toNumberRat) : x.sign = y.sign := by +@[simp, grind .] +theorem PackedFloat.sign_eq_of_toNumberRat_eq {x y : PackedFloat e s} + (hx : x.isNormOrNonzeroSubnorm) (hy : y.isNormOrNonzeroSubnorm) + (heq : x.toNumberRat = y.toNumberRat) : x.sign = y.sign := by + by_cases hxsign : x.sign <;> grind only [→ sign_iff_toNumberRat_neg] + +@[simp, grind .] +theorem PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq {x y : PackedFloat e s} + (hx : x.isNormOrNonzeroSubnorm) (hy : y.isNormOrNonzeroSubnorm) + (heq : x.toNumberRat = y.toNumberRat) : x.sign = y.sign ∧ x.sig = y.sig ∧ x.ex = y.ex := by + have hSignEq : x.sign = y.sign := by + apply PackedFloat.sign_eq_of_toNumberRat_eq hx hy heq + simp [PackedFloat.toNumberRat] at heq + rw [hSignEq] at heq + simp [hSignEq] + have : x.toNumberRatSig * 2 ^ x.toNumberRatExp = y.toNumberRatSig * 2 ^ y.toNumberRatExp := by + rw [← Rat.mul_cancel_left (x := x.sign.toSign)] + · grind + · simp + have xsigNeZero : x.toNumberRatSig ≠ 0 := by grind + have ySigNeZero : y.toNumberRatSig ≠ 0 := by grind sorry + + + + + @[simp, grind =>] theorem PackedFloat.le_getInfinity_true_iff_eq (hs : 0 < s) (y : PackedFloat e s) : @@ -2022,6 +2078,32 @@ theorem PackedFloat.le_getInfinity_true_iff_eq (hs : 0 < s) subst h grind only [le_refl] +@[grind =] +theorem isNormOrNonzeroSubnorm_of_not_NaN_not_Infinite_not_Zero {pf : PackedFloat e s} (h : ¬ pf.isNaN) (h2 : ¬ pf.isInfinite) (h3 : ¬ pf.isZero) : + pf.isNormOrNonzeroSubnorm := by + simp [isNormOrNonzeroSubnorm] + simp [isNaN] at h + simp [isInfinite] at h2 + simp [isZero] at h3 + grind + +theorem technical_lemma_when_normal (base0 base1 : Rat) + (pow0 pow1 : Int) (h : base0 * (2 : Rat) ^ pow0 = base1 * (2 : Rat) ^ pow1) + (hLtBase0 : 1 ≤ base0) + (hLtBase1 : 1 ≤ base1) + (hBase0Lt : base0 < 2) + (hBase1Lt : base1 < 2) : + base0 = base1 ∧ pow0 = pow1 := by + by_cases hbase : base0 = base1 + · subst hbase + sorry + · have : pow0 ≠ pow1 := by + intros hpow + subst hpow + rw [Rat.mul_cancel_right] at h + · grind + · grind + sorry theorem eq_of_toExtRat'_eq (x y : PackedFloat e s) (hx : ¬ x.isNaN) (hy : ¬ y.isNaN) (hxzero : ¬ x.isZero) (hyzero : ¬ y.isZero) @@ -2042,6 +2124,12 @@ theorem eq_of_toExtRat'_eq (x y : PackedFloat e s) by_cases hyinf : y.isInfinite · simp [hyinf] at h · simp [hyinf] at h + apply PackedFloat.ext + · apply PackedFloat.sign_eq_of_toNumberRat_eq + · grind + · grind + · grind + · sorry -- 1. signs are equal if toNumberRat is equal. -- 2. Then, sig * exp are equal. However, because these have different 'ranges', it must be that sig and exp are separately equal? diff --git a/Fp/Utils.lean b/Fp/Utils.lean index dad5216..b16168d 100644 --- a/Fp/Utils.lean +++ b/Fp/Utils.lean @@ -2,6 +2,43 @@ import Std.Tactic.BVDecide import Fp.Tactics import Fp.Grind + +theorem Rat.mul_ne_zero_iff {x y : Rat} : (¬ (x * y = 0)) ↔ x ≠ 0 ∧ y ≠ 0 := by + grind + +theorem Rat.ne_zero_of_zero_lt {r : Rat} (h : 0 < r) : r ≠ 0 := by + grind + +attribute [simp] Rat.zero_add +attribute [simp] Rat.add_zero +attribute [simp] Rat.zero_mul +attribute [simp] Rat.mul_zero +attribute [simp] Rat.mul_one +attribute [simp] Rat.one_mul + +attribute [simp] Rat.natCast_eq_zero_iff +attribute [simp] Rat.natCast_inj +attribute [simp] Rat.intCast_inj + + +@[simp] +theorem Rat.mul_cancel_left {x y z : Rat} (hx : x ≠ 0) : x * y = x * z ↔ y = z := by + grind + + +@[simp] +theorem Rat.mul_cancel_right {x y z : Rat} (hx : x ≠ 0) : y * x = z * x ↔ y = z := by + grind + +@[simp] +theorem Rat.natCast_ne_natCast_iff {r s : Nat} : (r : Rat) ≠ (s : Rat) ↔ r ≠ s := by + apply not_congr; simp + + +@[simp] +theorem Rat.intCast_ne_intCast_iff {r s : Int} : (r : Rat) ≠ (s : Rat) ↔ r ≠ s := by + apply not_congr; simp + /-- convert the sign bit to an integer value. Morally, this is (-1)^s -/ def signToInt (s : Bool) : Int := if s then -1 else 1 From 8a7e5da789aad4ea8400b9bf3d4dab915fdbfef8 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Tue, 17 Feb 2026 16:30:10 +0000 Subject: [PATCH 28/44] chore: shit tons more theory --- Fp/Basic.lean | 98 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 87 insertions(+), 11 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 49ab563..c2efafe 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -593,6 +593,12 @@ theorem isZero_getInfinity {exWidth sigWidth : Nat} (sign : Bool) : def isNonzeroSubnorm (pf : PackedFloat e s) : Bool := e != 0 && pf.ex == .zero e && pf.sig != .zero s +@[simp] +theorem exp_eq_of_isNonzeroSubnorm {pf : PackedFloat e s} (h : pf.isNonzeroSubnorm) : + pf.ex = 0#e := by + simp [isNonzeroSubnorm] at h + simp [h] + -- | does this also need a 'e != 0' condition? @[bv_normalize] def isNorm {e s} (pf : PackedFloat e s) : Bool := @@ -1577,6 +1583,15 @@ def toNumberRatSig {e s} (pf : PackedFloat e s) : Rat := else 0 + pf.sig.toNat / 2 ^ s + +theorem toRatNumberSig_eq_of_isNorm {e s} {pf : PackedFloat e s} (hnorm : pf.isNorm) : + pf.toNumberRatSig = 1 + pf.sig.toNat / 2 ^ s := by + simp [toNumberRatSig, hnorm] + +theorem toRatNumberSig_eq_of_not_isNorm {e s} {pf : PackedFloat e s} (hnorm : ¬ pf.isNorm) : + pf.toNumberRatSig = pf.sig.toNat / 2 ^ s := by + simp [toNumberRatSig, hnorm] + theorem toNumberRatSig_lt_one_of_not_isNorm {e s} (pf : PackedFloat e s) (hnorm : ¬ pf.isNorm) : pf.toNumberRatSig < 1 := by simp [toNumberRatSig, hnorm] @@ -1620,6 +1635,14 @@ def toNumberRatExp {e s} (pf : PackedFloat e s) : Int := else -(bias e - 1 : Nat) +theorem toNumberRatExp_eq_of_not_isNorm {e s} {pf : PackedFloat e s} (hnorm : ¬ pf.isNorm) : + pf.toNumberRatExp = -(bias e - 1 : Nat) := by + simp [toNumberRatExp, hnorm] + +theorem toNumberRatExp_eq_of_isNorm {e s} {pf : PackedFloat e s} (hnorm : pf.isNorm) : + pf.toNumberRatExp = pf.ex.toNat - bias e := by + simp [toNumberRatExp, hnorm] + def toNumberRat {e s} (pf : PackedFloat e s) : Rat := pf.sign.toSign * pf.toNumberRatSig * 2 ^ (pf.toNumberRatExp) @@ -2044,9 +2067,17 @@ theorem PackedFloat.sign_eq_of_toNumberRat_eq {x y : PackedFloat e s} (heq : x.toNumberRat = y.toNumberRat) : x.sign = y.sign := by by_cases hxsign : x.sign <;> grind only [→ sign_iff_toNumberRat_neg] +@[simp, grind .] +theorem Rat.div_cancel {p q d : Rat} (hd : d ≠ 0) : + (p / d = q / d) <-> p = q := by + rw [Rat.div_def, Rat.div_def] + rw [Rat.mul_cancel_right] + · grind + @[simp, grind .] theorem PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq {x y : PackedFloat e s} (hx : x.isNormOrNonzeroSubnorm) (hy : y.isNormOrNonzeroSubnorm) + (hnormState : x.isNorm ↔ y.isNorm) (heq : x.toNumberRat = y.toNumberRat) : x.sign = y.sign ∧ x.sig = y.sig ∧ x.ex = y.ex := by have hSignEq : x.sign = y.sign := by apply PackedFloat.sign_eq_of_toNumberRat_eq hx hy heq @@ -2059,7 +2090,27 @@ theorem PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq {x y : PackedFloat e s} · simp have xsigNeZero : x.toNumberRatSig ≠ 0 := by grind have ySigNeZero : y.toNumberRatSig ≠ 0 := by grind - sorry + by_cases hxnorm : x.isNorm + · sorry + · have xSubnorm : x.isNonzeroSubnorm := by grind + have ySubnorm : y.isNonzeroSubnorm := by grind + have expEq : x.toNumberRatExp = y.toNumberRatExp := by + simp [x.toNumberRatExp_eq_of_not_isNorm (by grind)] + simp [y.toNumberRatExp_eq_of_not_isNorm (by grind)] + have sigEq : x.toNumberRatSig = y.toNumberRatSig := by + rw [← Rat.mul_cancel_right (x := 2 ^ x.toNumberRatExp)] + rw [expEq] + grind only + have : x.sig = y.sig := by + rw [x.toRatNumberSig_eq_of_not_isNorm (by grind)] at sigEq + rw [y.toRatNumberSig_eq_of_not_isNorm (by grind)] at sigEq + rw [Rat.div_cancel] at sigEq + simp at sigEq + apply BitVec.eq_of_toNat_eq + assumption + simp [this] + simp [PackedFloat.exp_eq_of_isSubnormal] + @@ -2087,6 +2138,39 @@ theorem isNormOrNonzeroSubnorm_of_not_NaN_not_Infinite_not_Zero {pf : PackedFloa simp [isZero] at h3 grind +@[grind ., simp] +theorem Rat.two_pow_ne_zero (n : Int) : (2 : Rat) ^ n ≠ 0 := by + apply Rat.ne_zero_of_zero_lt + norm_cast + grind + +theorem Rat.lt_of_lt_mul_of_one_lt {left right large: Rat} (hr : 1 < large) (h : left < right) + : left < right * large := by + sorry + + +/-- a lower bound on the division of two numbers. -/ +theorem lt_div {num den lnum uden : Rat} + (huden : 0 < den) + (hnum : lnum < num) (hden : den < uden) : + lnum / uden < num / den := by + rw [Rat.div_lt_iff] + · rw [Rat.div_def] + have : 1 < den⁻¹ * uden := by + rw [Rat.mul_comm] + rw [← Rat.div_def] + rw [Rat.lt_div_iff] + · simp; grind + · grind + · rw [Rat.mul_assoc] + apply Rat.lt_of_lt_mul_of_one_lt this + grind + · grind only + + +-- when subnormal, note that the exponent is zero, so it follows trivially that the +-- bases are zero. +-- When normal, the bases are in [1, 2), so we can use the fact that the function 'base * 2^pow' is injective on this domain. theorem technical_lemma_when_normal (base0 base1 : Rat) (pow0 pow1 : Int) (h : base0 * (2 : Rat) ^ pow0 = base1 * (2 : Rat) ^ pow1) (hLtBase0 : 1 ≤ base0) @@ -2094,16 +2178,7 @@ theorem technical_lemma_when_normal (base0 base1 : Rat) (hBase0Lt : base0 < 2) (hBase1Lt : base1 < 2) : base0 = base1 ∧ pow0 = pow1 := by - by_cases hbase : base0 = base1 - · subst hbase - sorry - · have : pow0 ≠ pow1 := by - intros hpow - subst hpow - rw [Rat.mul_cancel_right] at h - · grind - · grind - sorry + sorry theorem eq_of_toExtRat'_eq (x y : PackedFloat e s) (hx : ¬ x.isNaN) (hy : ¬ y.isNaN) (hxzero : ¬ x.isZero) (hyzero : ¬ y.isZero) @@ -2124,6 +2199,7 @@ theorem eq_of_toExtRat'_eq (x y : PackedFloat e s) by_cases hyinf : y.isInfinite · simp [hyinf] at h · simp [hyinf] at h + rw [PackedFloat.toNumberRat, PackedFloat.toNumberRat] at h apply PackedFloat.ext · apply PackedFloat.sign_eq_of_toNumberRat_eq · grind From 6863e62bd5f07575bab877b65451ba605a8bd013 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Tue, 17 Feb 2026 20:05:05 +0000 Subject: [PATCH 29/44] chore: cleanup normal case --- Fp/Basic.lean | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index c2efafe..b779ad9 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -594,7 +594,8 @@ def isNonzeroSubnorm (pf : PackedFloat e s) : Bool := e != 0 && pf.ex == .zero e && pf.sig != .zero s @[simp] -theorem exp_eq_of_isNonzeroSubnorm {pf : PackedFloat e s} (h : pf.isNonzeroSubnorm) : +theorem exp_eq_of_isNonzeroSubnorm {pf : PackedFloat e s} + (h : pf.isNonzeroSubnorm := by solve | simp | grind) : pf.ex = 0#e := by simp [isNonzeroSubnorm] at h simp [h] @@ -2074,6 +2075,12 @@ theorem Rat.div_cancel {p q d : Rat} (hd : d ≠ 0) : rw [Rat.mul_cancel_right] · grind +@[grind .] +theorem Rat.twoPowNeZero (n : Int) : (2 : Rat) ^ n ≠ 0 := by + apply Rat.ne_zero_of_zero_lt + norm_cast + grind only [Fp.Rat.two_pow_pos] + @[simp, grind .] theorem PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq {x y : PackedFloat e s} (hx : x.isNormOrNonzeroSubnorm) (hy : y.isNormOrNonzeroSubnorm) @@ -2088,33 +2095,33 @@ theorem PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq {x y : PackedFloat e s} rw [← Rat.mul_cancel_left (x := x.sign.toSign)] · grind · simp - have xsigNeZero : x.toNumberRatSig ≠ 0 := by grind - have ySigNeZero : y.toNumberRatSig ≠ 0 := by grind + have xsigNeZero : x.toNumberRatSig ≠ 0 := by grind only [toNumberRatSig_ne_zero_of_isNormOrNonzeroSubnorm] + have ySigNeZero : y.toNumberRatSig ≠ 0 := by grind only [toNumberRatSig_ne_zero_of_isNormOrNonzeroSubnorm] by_cases hxnorm : x.isNorm · sorry - · have xSubnorm : x.isNonzeroSubnorm := by grind - have ySubnorm : y.isNonzeroSubnorm := by grind + · have xSubnorm : x.isNonzeroSubnorm := by grind only [isNormOrSubnorm_eq_isNorm_or_isSubnorm] + have ySubnorm : y.isNonzeroSubnorm := by grind only [isNormOrSubnorm_eq_isNorm_or_isSubnorm] have expEq : x.toNumberRatExp = y.toNumberRatExp := by - simp [x.toNumberRatExp_eq_of_not_isNorm (by grind)] - simp [y.toNumberRatExp_eq_of_not_isNorm (by grind)] + simp [x.toNumberRatExp_eq_of_not_isNorm (by grind only)] + simp [y.toNumberRatExp_eq_of_not_isNorm (by grind only)] have sigEq : x.toNumberRatSig = y.toNumberRatSig := by rw [← Rat.mul_cancel_right (x := 2 ^ x.toNumberRatExp)] - rw [expEq] - grind only + · rw [expEq] + grind only + · grind only [Rat.twoPowNeZero] have : x.sig = y.sig := by - rw [x.toRatNumberSig_eq_of_not_isNorm (by grind)] at sigEq - rw [y.toRatNumberSig_eq_of_not_isNorm (by grind)] at sigEq - rw [Rat.div_cancel] at sigEq + rw [x.toRatNumberSig_eq_of_not_isNorm (by grind only)] at sigEq + rw [y.toRatNumberSig_eq_of_not_isNorm (by grind only)] at sigEq + have hTwoPowNeZero : (2 : Rat) ^ s ≠ 0 := by norm_cast; grind only [usr Nat.pow_pos] + rw [Rat.div_cancel hTwoPowNeZero] at sigEq simp at sigEq apply BitVec.eq_of_toNat_eq assumption simp [this] - simp [PackedFloat.exp_eq_of_isSubnormal] - - - - + rw [exp_eq_of_isNonzeroSubnorm (by grind only)] + rw [exp_eq_of_isNonzeroSubnorm (by grind only)] +#exit @[simp, grind =>] From 6ea92589297748840336e0a5a9074d790d78a54d Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Wed, 18 Feb 2026 14:00:57 +0000 Subject: [PATCH 30/44] chore: write a bit more about the Packed -> ExtRat inj --- Fp/Basic.lean | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index b779ad9..0b04e53 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -2081,11 +2081,25 @@ theorem Rat.twoPowNeZero (n : Int) : (2 : Rat) ^ n ≠ 0 := by norm_cast grind only [Fp.Rat.two_pow_pos] +-- when subnormal, note that the exponent is zero, so it follows trivially that the +-- bases are zero. +-- When normal, the bases are in [1, 2), so we can use the fact that the function 'base * 2^pow' is injective on this domain. +theorem mul_two_pow_inj (base0 base1 : Rat) + (pow0 pow1 : Int) (h : base0 * (2 : Rat) ^ pow0 = base1 * (2 : Rat) ^ pow1) + (hLtBase0 : 1 ≤ base0) + (hLtBase1 : 1 ≤ base1) + (hBase0Lt : base0 < 2) + (hBase1Lt : base1 < 2) : + base0 = base1 ∧ pow0 = pow1 := by + sorry + + @[simp, grind .] theorem PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq {x y : PackedFloat e s} (hx : x.isNormOrNonzeroSubnorm) (hy : y.isNormOrNonzeroSubnorm) (hnormState : x.isNorm ↔ y.isNorm) - (heq : x.toNumberRat = y.toNumberRat) : x.sign = y.sign ∧ x.sig = y.sig ∧ x.ex = y.ex := by + (heq : x.toNumberRat = y.toNumberRat) : + x.sign = y.sign ∧ x.sig = y.sig ∧ x.ex = y.ex := by have hSignEq : x.sign = y.sign := by apply PackedFloat.sign_eq_of_toNumberRat_eq hx hy heq simp [PackedFloat.toNumberRat] at heq @@ -2098,7 +2112,18 @@ theorem PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq {x y : PackedFloat e s} have xsigNeZero : x.toNumberRatSig ≠ 0 := by grind only [toNumberRatSig_ne_zero_of_isNormOrNonzeroSubnorm] have ySigNeZero : y.toNumberRatSig ≠ 0 := by grind only [toNumberRatSig_ne_zero_of_isNormOrNonzeroSubnorm] by_cases hxnorm : x.isNorm - · sorry + · have := mul_two_pow_inj + x.toNumberRatSig + y.toNumberRatSig + x.toNumberRatExp + y.toNumberRatExp + sorry + sorry + sorry + sorry + sorry + -- now I need to know that 'toNumberRatSig', 'toNumberRatExp' are equal. + sorry · have xSubnorm : x.isNonzeroSubnorm := by grind only [isNormOrSubnorm_eq_isNorm_or_isSubnorm] have ySubnorm : y.isNonzeroSubnorm := by grind only [isNormOrSubnorm_eq_isNorm_or_isSubnorm] have expEq : x.toNumberRatExp = y.toNumberRatExp := by @@ -2108,7 +2133,7 @@ theorem PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq {x y : PackedFloat e s} rw [← Rat.mul_cancel_right (x := 2 ^ x.toNumberRatExp)] · rw [expEq] grind only - · grind only [Rat.twoPowNeZero] + · grind? have : x.sig = y.sig := by rw [x.toRatNumberSig_eq_of_not_isNorm (by grind only)] at sigEq rw [y.toRatNumberSig_eq_of_not_isNorm (by grind only)] at sigEq From 8fc5ceb99a8afbb1666c51d5c16304cbc0f984f7 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Wed, 18 Feb 2026 14:47:44 +0000 Subject: [PATCH 31/44] chore: more proofs, grab bounds --- Fp/Basic.lean | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 0b04e53..26fb8d9 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -2094,6 +2094,46 @@ theorem mul_two_pow_inj (base0 base1 : Rat) sorry +attribute [grind .] Rat.pow_pos + +@[grind .] +theorem Rat.two_pow_int_ne_zero {n : Int} : (2 : Rat) ^ n ≠ 0 := by + apply Rat.ne_zero_of_zero_lt + norm_cast + apply Rat.zpow_pos + grind only + +@[grind .] +theorem Rat.two_pow_nat_ne_zero {n : Nat} : (2 : Rat) ^ n ≠ 0 := by + apply Rat.ne_zero_of_zero_lt + norm_cast + exact Nat.two_pow_pos n + +theorem sig_eq_of_toNumberRatSig_eq_toNumberRatSig + {x y : PackedFloat e s} + (h : x.toNumberRatSig = y.toNumberRatSig) + (hnorm : x.isNorm = y.isNorm) : x.sig = y.sig := by +simp [toNumberRatSig] at h +by_cases hxnorm : x.isNorm +· simp [hxnorm] at hnorm + simp [hxnorm, hnorm] at h + have : (2 ^ s : Rat) ≠ 0 := by + apply Rat.ne_zero_of_zero_lt + grind only [Rat.pow_pos] + have : (x.sig.toNat : Rat) = (y.sig.toNat : Rat) := by + grind only + simp only [Rat.natCast_inj] at this + apply BitVec.eq_of_toNat_eq + grind +· simp [hxnorm] at hnorm + simp [hxnorm, hnorm] at h + have : (2 ^ s : Rat) ≠ 0 := by grind + have : (x.sig.toNat : Rat) = (y.sig.toNat : Rat) := by + grind only + simp only [Rat.natCast_inj] at this + apply BitVec.eq_of_toNat_eq + grind + @[simp, grind .] theorem PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq {x y : PackedFloat e s} (hx : x.isNormOrNonzeroSubnorm) (hy : y.isNormOrNonzeroSubnorm) From 66cd60a96bc6bb2be6ed6e9817eb5b5f6bcff09a Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Wed, 18 Feb 2026 14:56:57 +0000 Subject: [PATCH 32/44] chore: more bounds --- Fp/Basic.lean | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 26fb8d9..1ed74af 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -2109,6 +2109,35 @@ theorem Rat.two_pow_nat_ne_zero {n : Nat} : (2 : Rat) ^ n ≠ 0 := by norm_cast exact Nat.two_pow_pos n + +/-- +Show that the exponents are equal +if their interpretation as Rats are equal. +-/ +theorem exp_eq_of_toNumberRatExp_eq + (x y : PackedFloat e s) + (h : x.toNumberRatExp = y.toNumberRatExp) + (hx : x.isNormOrNonzeroSubnorm) (hy : y.isNormOrNonzeroSubnorm) + (hnorm : x.isNorm = y.isNorm) : x.ex = y.ex := by +simp [toNumberRatExp] at h +by_cases hxnorm : x.isNorm +· simp [hxnorm] at hnorm + simp [hxnorm, hnorm] at h + have : (x.ex.toNat : Int) = (y.ex.toNat : Int) := by grind only + simp only [Int.natCast_inj] at this + apply BitVec.eq_of_toNat_eq + grind +· simp [hxnorm] at hnorm + simp [hxnorm, hnorm] at h + have : x.isNonzeroSubnorm := by grind + simp [this] + have : y.isNonzeroSubnorm := by grind + simp [this] + +/-- +Show that the significands are equal +if their interpretation as Rats are equal. +-/ theorem sig_eq_of_toNumberRatSig_eq_toNumberRatSig {x y : PackedFloat e s} (h : x.toNumberRatSig = y.toNumberRatSig) @@ -2157,13 +2186,18 @@ theorem PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq {x y : PackedFloat e s} y.toNumberRatSig x.toNumberRatExp y.toNumberRatExp - sorry - sorry + (by grind) + (by sorry) sorry sorry sorry -- now I need to know that 'toNumberRatSig', 'toNumberRatExp' are equal. - sorry + have hSigEq := sig_eq_of_toNumberRatSig_eq_toNumberRatSig + (x := x) (y := y) (by grind) (by grind) + simp [hSigEq] + have hExpEq := exp_eq_of_toNumberRatExp_eq + (x := x) (y := y) (by grind) (by grind) (by grind) (by grind) + simp [hExpEq] · have xSubnorm : x.isNonzeroSubnorm := by grind only [isNormOrSubnorm_eq_isNorm_or_isSubnorm] have ySubnorm : y.isNonzeroSubnorm := by grind only [isNormOrSubnorm_eq_isNorm_or_isSubnorm] have expEq : x.toNumberRatExp = y.toNumberRatExp := by From 5b4c0b75d533fab099fb778f5f67bbb0c882a062 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Thu, 19 Feb 2026 11:56:44 +0000 Subject: [PATCH 33/44] chore: kill another sorry --- Fp/Basic.lean | 187 ++++++++++++++++++++++++++++---------------------- 1 file changed, 105 insertions(+), 82 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 1ed74af..f72af6c 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1585,6 +1585,25 @@ def toNumberRatSig {e s} (pf : PackedFloat e s) : Rat := 0 + pf.sig.toNat / 2 ^ s +@[grind .] +theorem one_le_toNumberRatSig_of_isNorm {e s} (pf : PackedFloat e s) (hnorm : pf.isNorm) : + 1 ≤ pf.toNumberRatSig := by + simp [toNumberRatSig, hnorm] + have : (pf.sig.toNat : Rat) / (2 : Rat) ^ s ≥ 0 := by grind + have : (1 + pf.sig.toNat / 2^s) ≥ 0 := by grind + grind + +@[grind .] +theorem zero_le_twoNumberRatSig {e s} (pf : PackedFloat e s) : + 0 ≤ pf.toNumberRatSig := by + have : (pf.sig.toNat : Rat) / (2 : Rat) ^ s ≥ 0 := by grind only [Fp.Rat.div_nonneg, + Rat.pow_nonneg] + have : (0 + pf.sig.toNat / 2^s) ≥ 0 := by grind only + have : pf.sig.toNat / 2^s ≥ 0 := by grind only + have : pf.sig.toNat ≥ 0 := by grind only + simp only [toNumberRatSig, Rat.zero_add, ge_iff_le] + grind only + theorem toRatNumberSig_eq_of_isNorm {e s} {pf : PackedFloat e s} (hnorm : pf.isNorm) : pf.toNumberRatSig = 1 + pf.sig.toNat / 2 ^ s := by simp [toNumberRatSig, hnorm] @@ -2081,18 +2100,81 @@ theorem Rat.twoPowNeZero (n : Int) : (2 : Rat) ^ n ≠ 0 := by norm_cast grind only [Fp.Rat.two_pow_pos] --- when subnormal, note that the exponent is zero, so it follows trivially that the --- bases are zero. --- When normal, the bases are in [1, 2), so we can use the fact that the function 'base * 2^pow' is injective on this domain. +attribute [simp] Rat.zpow_natCast + +theorem Rat.mul_le_mul_of_le_of_le_of_nonneg_of_nonneg + {a b c d : Rat} (hab : a ≤ b) (hcd : c ≤ d) (hac : 0 ≤ a) (hcc : 0 ≤ c) : + a * c ≤ b * d := by + apply (Rat.le_iff_sub_nonneg (a * c) (b * d)).mpr + rw [show b = a + (b - a) by grind only] + rw [Rat.add_mul] + have : (b - a) ≥ 0 := by grind + have : 0 ≤ a * d := by + apply Rat.mul_nonneg <;> grind only + rw [show a * d + (b - a) * d - a * c = (b - a) * d + a * (d - c) by grind only] + have : 0 ≤ a * (d - c) := by + apply Rat.mul_nonneg <;> grind only + have : 0 ≤ (b - a) * d := by + apply Rat.mul_nonneg <;> grind only + grind only + +-- When normal, the bases are in [1, 2), +-- so we can use the fact that the function 'base * 2^pow' is injective on this domain. +-- This follows by a fairly simple argument. +theorem mul_two_pow_inj_aux (base0 base1 : Rat) + (pow0 pow1 : Int) (h : base0 * (2 : Rat) ^ pow0 = base1 * (2 : Rat) ^ pow1) + (hLtBase0 : 1 ≤ base0) + (hLtBase1 : 1 ≤ base1) + (hBase0Lt : base0 < 2) + (hBase1Lt : base1 < 2) + (hle : pow0 ≤ pow1): + base0 = base1 ∧ pow0 = pow1 := + if heq : pow0 = pow1 then by + subst heq + simp only [and_true] at h ⊢ + rw [← Rat.mul_cancel_right (x := 2 ^ pow0)] + · grind only + · grind only [Rat.twoPowNeZero] + else by + have hlt : pow0 < pow1 := by grind only + have : ∃ (k : Nat), pow0 + k = pow1 := by + exact Int.le.dest hle + obtain ⟨k, hk⟩ := this + have : 0 < k := by grind + have : (2 : Rat) ≤ 2 ^ k := by + norm_cast + rcases k with rfl | k + · grind only + · rw [Nat.pow_succ] + have : 1 ≤ 2 ^ k := by grind only [usr Nat.pow_pos] + grind + subst hk + rw [Rat.zpow_add (hq := by grind only)] at h + rw [Rat.mul_comm (2 ^ pow0)] at h + rw [← Rat.mul_assoc base1] at h + simp only [Rat.zpow_natCast] at h + rw [Rat.mul_cancel_right (by grind only [Rat.twoPowNeZero])] at h + have : base1 * (2 : Rat) ^ k < 2 := by + subst h + grind + have : base1 * 2 ^ k ≥ 2 := by + simp only [ge_iff_le] + rw [show (2 : Rat) = 1 * 2 by grind only] + apply Rat.mul_le_mul_of_le_of_le_of_nonneg_of_nonneg <;> grind only + grind only + theorem mul_two_pow_inj (base0 base1 : Rat) (pow0 pow1 : Int) (h : base0 * (2 : Rat) ^ pow0 = base1 * (2 : Rat) ^ pow1) (hLtBase0 : 1 ≤ base0) (hLtBase1 : 1 ≤ base1) (hBase0Lt : base0 < 2) (hBase1Lt : base1 < 2) : - base0 = base1 ∧ pow0 = pow1 := by - sorry - + base0 = base1 ∧ pow0 = pow1 := by + by_cases hle : pow0 ≤ pow1 + · apply mul_two_pow_inj_aux <;> grind only + · have hgt : pow1 < pow0 := by grind only + have := mul_two_pow_inj_aux base1 base0 pow1 pow0 h.symm hLtBase1 hLtBase0 hBase1Lt hBase0Lt (by grind only) + grind only attribute [grind .] Rat.pow_pos @@ -2186,11 +2268,11 @@ theorem PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq {x y : PackedFloat e s} y.toNumberRatSig x.toNumberRatExp y.toNumberRatExp - (by grind) - (by sorry) - sorry - sorry - sorry + (by grind only) + (by grind only [one_le_toNumberRatSig_of_isNorm]) + (by grind only [one_le_toNumberRatSig_of_isNorm]) + (by grind only [toNumberRatSig_lt_two]) + (by grind only [toNumberRatSig_lt_two]) -- now I need to know that 'toNumberRatSig', 'toNumberRatExp' are equal. have hSigEq := sig_eq_of_toNumberRatSig_eq_toNumberRatSig (x := x) (y := y) (by grind) (by grind) @@ -2207,7 +2289,7 @@ theorem PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq {x y : PackedFloat e s} rw [← Rat.mul_cancel_right (x := 2 ^ x.toNumberRatExp)] · rw [expEq] grind only - · grind? + · grind only [Rat.two_pow_int_ne_zero] have : x.sig = y.sig := by rw [x.toRatNumberSig_eq_of_not_isNorm (by grind only)] at sigEq rw [y.toRatNumberSig_eq_of_not_isNorm (by grind only)] at sigEq @@ -2220,7 +2302,6 @@ theorem PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq {x y : PackedFloat e s} rw [exp_eq_of_isNonzeroSubnorm (by grind only)] rw [exp_eq_of_isNonzeroSubnorm (by grind only)] -#exit @[simp, grind =>] @@ -2250,46 +2331,11 @@ theorem Rat.two_pow_ne_zero (n : Int) : (2 : Rat) ^ n ≠ 0 := by norm_cast grind -theorem Rat.lt_of_lt_mul_of_one_lt {left right large: Rat} (hr : 1 < large) (h : left < right) - : left < right * large := by - sorry - - -/-- a lower bound on the division of two numbers. -/ -theorem lt_div {num den lnum uden : Rat} - (huden : 0 < den) - (hnum : lnum < num) (hden : den < uden) : - lnum / uden < num / den := by - rw [Rat.div_lt_iff] - · rw [Rat.div_def] - have : 1 < den⁻¹ * uden := by - rw [Rat.mul_comm] - rw [← Rat.div_def] - rw [Rat.lt_div_iff] - · simp; grind - · grind - · rw [Rat.mul_assoc] - apply Rat.lt_of_lt_mul_of_one_lt this - grind - · grind only - - --- when subnormal, note that the exponent is zero, so it follows trivially that the --- bases are zero. --- When normal, the bases are in [1, 2), so we can use the fact that the function 'base * 2^pow' is injective on this domain. -theorem technical_lemma_when_normal (base0 base1 : Rat) - (pow0 pow1 : Int) (h : base0 * (2 : Rat) ^ pow0 = base1 * (2 : Rat) ^ pow1) - (hLtBase0 : 1 ≤ base0) - (hLtBase1 : 1 ≤ base1) - (hBase0Lt : base0 < 2) - (hBase1Lt : base1 < 2) : - base0 = base1 ∧ pow0 = pow1 := by - sorry - theorem eq_of_toExtRat'_eq (x y : PackedFloat e s) (hx : ¬ x.isNaN) (hy : ¬ y.isNaN) (hxzero : ¬ x.isZero) (hyzero : ¬ y.isZero) (h : x.toExtRat' = y.toExtRat') : x = y := by simp [toExtRat'] at h + have hExtRateq := h simp [hx, hy] at h by_cases hxinf : x.isInfinite · simp [hxinf] at h @@ -2305,45 +2351,22 @@ theorem eq_of_toExtRat'_eq (x y : PackedFloat e s) by_cases hyinf : y.isInfinite · simp [hyinf] at h · simp [hyinf] at h - rw [PackedFloat.toNumberRat, PackedFloat.toNumberRat] at h - apply PackedFloat.ext - · apply PackedFloat.sign_eq_of_toNumberRat_eq - · grind - · grind - · grind - · sorry - - -- 1. signs are equal if toNumberRat is equal. - -- 2. Then, sig * exp are equal. However, because these have different 'ranges', it must be that sig and exp are separately equal? - -- How to make this formal? Is this true? - sorry - - -- simp [hxzero] at h - -- simp [hyzero] at h - -- by_cases hx : x.isNorm - -- · simp [hx] at h - -- by_cases hy : y.isNorm - -- · simp [hy] at h - -- sorry - -- · simp [hy] at h - -- sorry - -- · simp [hx] at h - -- by_cases hy : y.isNorm - -- · simp [hy] at h - -- sorry - -- · simp [hy] at h - -- sorry + have := + PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq (x := x) (y := y) + (by grind only [= isNormOrNonzeroSubnorm_of_not_NaN_not_Infinite_not_Zero]) + (by grind only [= isNormOrNonzeroSubnorm_of_not_NaN_not_Infinite_not_Zero]) + (by sorry) + (by grind) + apply PackedFloat.ext <;> grind only +/- @[simp] theorem le_iff_toExtRat'_le_toExtRat'_of_not_isZero (he : 0 < e) (hs : 0 < s) (x y : PackedFloat e s) (hxzero : ¬ x.isZero) (hyzero : ¬ y.isZero) (hxnan : ¬ x.isNaN) (hynan : ¬ y.isNaN) : x ≤ y ↔ x.toExtRat' ≤ y.toExtRat' := by - constructor - · intros h - sorry - · intros h - sorry + sorry +-/ @[simp] theorem le_zero_iff_sign_eq_true {x : PackedFloat e s} (he : 0 < e): From 841d07beba1b126722def04c6ca995d6d15d491e Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Thu, 19 Feb 2026 15:12:33 +0000 Subject: [PATCH 34/44] chore: remove sorrys from Basic, prove translation to ExtRat --- Fp/Basic.lean | 451 ++++++++++++++++++++++++--------------- Fp/Grind.lean | 2 + Fp/Theorems/Packing.lean | 1 - Fp/Utils.lean | 88 ++++++++ 4 files changed, 369 insertions(+), 173 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index f72af6c..2d19fd8 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -605,6 +605,12 @@ theorem exp_eq_of_isNonzeroSubnorm {pf : PackedFloat e s} def isNorm {e s} (pf : PackedFloat e s) : Bool := pf.ex != .allOnes e && pf.ex != .zero e +@[grind .] +theorem ex_ne_zero_if_isNorm {pf : PackedFloat e s} (h : pf.isNorm := by solve | simp | grind) : + pf.ex != .zero e := by + simp [isNorm] at h + simp [h] + @[simp, bv_normalize] def isNormOrNonzeroSubnorm (pf : PackedFloat e s) : Bool := pf.ex != .allOnes e && (pf.ex != .zero e || pf.sig != .zero s) @@ -1851,10 +1857,10 @@ theorem classification {P : PackedFloat e s → Prop} · grind only @[simp, grind .] -theorem PackedFloat.le_refl (x : PackedFloat e s) : x ≤ x := by simp [← PackedFloat.le_def, PackedFloat.le] +theorem le_refl (x : PackedFloat e s) : x ≤ x := by simp [← le_def, PackedFloat.le] @[simp, grind .] -theorem PackedFloat.le_NaN (x : PackedFloat e s) : +theorem le_NaN (x : PackedFloat e s) : x ≤ PackedFloat.getNaN e s ↔ x = PackedFloat.getNaN e s := by by_cases hx : x.isNaN · simp only [← PackedFloat.le_def, PackedFloat.le, hx] @@ -1865,7 +1871,7 @@ theorem PackedFloat.le_NaN (x : PackedFloat e s) : grind @[simp, grind .] -theorem PackedFloat.NaN_le (x : PackedFloat e s) +theorem NaN_le (x : PackedFloat e s) : PackedFloat.getNaN e s ≤ x ↔ x = PackedFloat.getNaN e s := by simp only [← PackedFloat.le_def, PackedFloat.le] simp only [isNaN_getNaN] @@ -1876,14 +1882,14 @@ theorem PackedFloat.NaN_le (x : PackedFloat e s) grind @[simp, grind .] -theorem PackedFloat.le_iff_eq_of_isNaN (x y : PackedFloat e s) +theorem le_iff_eq_of_isNaN (x y : PackedFloat e s) (hx : x.isNaN) : x ≤ y ↔ x = y := by simp only [← PackedFloat.le_def, PackedFloat.le, hx] simp only [not_true_eq_false, false_and] grind @[simp, grind .] -theorem PackedFloat.le_iff_eq_of_isNaN' (x y : PackedFloat e s) +theorem le_iff_eq_of_isNaN' (x y : PackedFloat e s) (hy : y.isNaN) : x ≤ y ↔ x = y := by simp only [← PackedFloat.le_def, PackedFloat.le, hy] simp only [not_true_eq_false] @@ -1893,7 +1899,7 @@ theorem PackedFloat.le_iff_eq_of_isNaN' (x y : PackedFloat e s) x is infinite iff it is equal to the infinity value with the same sign. -/ @[grind .] -theorem PackedFloat.eq_getInfinity_iff_isInfinity (hs : 0 < s) +theorem eq_getInfinity_iff_isInfinity (hs : 0 < s) {x : PackedFloat e s} : (x.isInfinite) ↔ x = .getInfinity e s x.sign := by simp [getInfinity, isInfinite] @@ -1926,7 +1932,7 @@ attribute [grind .] BitVec.toNat_inj attribute [grind .] BitVec.toInt_inj @[grind .] -theorem PackedFloat.le_antisymm_of_ne_NaN +theorem le_antisymm_of_ne_NaN {x y : PackedFloat e s} (hxy : x ≤ y) (hyx : y ≤ x) (hx : ¬ x.isNaN) (hy : ¬ y.isNaN) : x = y := by @@ -1936,7 +1942,7 @@ theorem PackedFloat.le_antisymm_of_ne_NaN simp [hy] at hxy hyx grind [PackedFloat] -theorem PackedFloat.le_antisymm_iff {x y : PackedFloat e s} +theorem le_antisymm_iff {x y : PackedFloat e s} (hxy : x ≤ y) (hyx : y ≤ x) : (x.isNaN ∧ y.isNaN) ∨ (¬ x.isNaN ∧ ¬ y.isNaN ∧ x = y) := by by_cases hx : x.isNaN @@ -1951,7 +1957,7 @@ theorem PackedFloat.le_antisymm_iff {x y : PackedFloat e s} · simp [hy] grind only [PackedFloat.le_antisymm_of_ne_NaN hxy hyx hx hy] -theorem PackedFloat.le_trans +theorem le_trans {x y z : PackedFloat e s} (hxy : x ≤ y) (hyz : y ≤ z) : x ≤ z := by simp only [← PackedFloat.le_def] at hxy hyz ⊢ simp only [PackedFloat.le] at hxy hyz ⊢ @@ -2018,7 +2024,7 @@ theorem le_eq_of_sign_eq_true_of_sign_eq_true {x y : PackedFloat e s} Every number is less than +∞ -/ @[grind =>] -theorem PackedFloat.le_getInfinity_false_of_not_isNaN (hs : 0 < s) (y : PackedFloat e s) : +theorem le_getInfinity_false_of_not_isNaN (hs : 0 < s) (y : PackedFloat e s) : (y ≤ PackedFloat.getInfinity e s false) ↔ ¬ y.isNaN := by by_cases hnan : y.isNaN · simp [hnan] @@ -2033,7 +2039,7 @@ theorem PackedFloat.le_getInfinity_false_of_not_isNaN (hs : 0 < s) (y : PackedFl BitVec.eq_allOnes_iff_toNat_eq, = BitVec.toNat_ofNat, = BitVec.toNat_zero] @[simp, grind →] -theorem PackedFloat.eq_getInfinity_of_getInfinity_le (hs : 0 < s) (y : PackedFloat e s) +theorem eq_getInfinity_of_getInfinity_le (hs : 0 < s) (y : PackedFloat e s) (hle : PackedFloat.getInfinity e s false ≤ y) : y = .getInfinity e s false := by have : (getInfinity e s false).sign = false := by grind @@ -2087,37 +2093,6 @@ theorem PackedFloat.sign_eq_of_toNumberRat_eq {x y : PackedFloat e s} (heq : x.toNumberRat = y.toNumberRat) : x.sign = y.sign := by by_cases hxsign : x.sign <;> grind only [→ sign_iff_toNumberRat_neg] -@[simp, grind .] -theorem Rat.div_cancel {p q d : Rat} (hd : d ≠ 0) : - (p / d = q / d) <-> p = q := by - rw [Rat.div_def, Rat.div_def] - rw [Rat.mul_cancel_right] - · grind - -@[grind .] -theorem Rat.twoPowNeZero (n : Int) : (2 : Rat) ^ n ≠ 0 := by - apply Rat.ne_zero_of_zero_lt - norm_cast - grind only [Fp.Rat.two_pow_pos] - -attribute [simp] Rat.zpow_natCast - -theorem Rat.mul_le_mul_of_le_of_le_of_nonneg_of_nonneg - {a b c d : Rat} (hab : a ≤ b) (hcd : c ≤ d) (hac : 0 ≤ a) (hcc : 0 ≤ c) : - a * c ≤ b * d := by - apply (Rat.le_iff_sub_nonneg (a * c) (b * d)).mpr - rw [show b = a + (b - a) by grind only] - rw [Rat.add_mul] - have : (b - a) ≥ 0 := by grind - have : 0 ≤ a * d := by - apply Rat.mul_nonneg <;> grind only - rw [show a * d + (b - a) * d - a * c = (b - a) * d + a * (d - c) by grind only] - have : 0 ≤ a * (d - c) := by - apply Rat.mul_nonneg <;> grind only - have : 0 ≤ (b - a) * d := by - apply Rat.mul_nonneg <;> grind only - grind only - -- When normal, the bases are in [1, 2), -- so we can use the fact that the function 'base * 2^pow' is injective on this domain. -- This follows by a fairly simple argument. @@ -2126,7 +2101,7 @@ theorem mul_two_pow_inj_aux (base0 base1 : Rat) (hLtBase0 : 1 ≤ base0) (hLtBase1 : 1 ≤ base1) (hBase0Lt : base0 < 2) - (hBase1Lt : base1 < 2) + -- (hBase1Lt : base1 < 2) (hle : pow0 ≤ pow1): base0 = base1 ∧ pow0 = pow1 := if heq : pow0 = pow1 then by @@ -2173,25 +2148,9 @@ theorem mul_two_pow_inj (base0 base1 : Rat) by_cases hle : pow0 ≤ pow1 · apply mul_two_pow_inj_aux <;> grind only · have hgt : pow1 < pow0 := by grind only - have := mul_two_pow_inj_aux base1 base0 pow1 pow0 h.symm hLtBase1 hLtBase0 hBase1Lt hBase0Lt (by grind only) + have := mul_two_pow_inj_aux base1 base0 pow1 pow0 h.symm hLtBase1 hLtBase0 hBase1Lt (by grind only) grind only -attribute [grind .] Rat.pow_pos - -@[grind .] -theorem Rat.two_pow_int_ne_zero {n : Int} : (2 : Rat) ^ n ≠ 0 := by - apply Rat.ne_zero_of_zero_lt - norm_cast - apply Rat.zpow_pos - grind only - -@[grind .] -theorem Rat.two_pow_nat_ne_zero {n : Nat} : (2 : Rat) ^ n ≠ 0 := by - apply Rat.ne_zero_of_zero_lt - norm_cast - exact Nat.two_pow_pos n - - /-- Show that the exponents are equal if their interpretation as Rats are equal. @@ -2245,119 +2204,7 @@ by_cases hxnorm : x.isNorm apply BitVec.eq_of_toNat_eq grind -@[simp, grind .] -theorem PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq {x y : PackedFloat e s} - (hx : x.isNormOrNonzeroSubnorm) (hy : y.isNormOrNonzeroSubnorm) - (hnormState : x.isNorm ↔ y.isNorm) - (heq : x.toNumberRat = y.toNumberRat) : - x.sign = y.sign ∧ x.sig = y.sig ∧ x.ex = y.ex := by - have hSignEq : x.sign = y.sign := by - apply PackedFloat.sign_eq_of_toNumberRat_eq hx hy heq - simp [PackedFloat.toNumberRat] at heq - rw [hSignEq] at heq - simp [hSignEq] - have : x.toNumberRatSig * 2 ^ x.toNumberRatExp = y.toNumberRatSig * 2 ^ y.toNumberRatExp := by - rw [← Rat.mul_cancel_left (x := x.sign.toSign)] - · grind - · simp - have xsigNeZero : x.toNumberRatSig ≠ 0 := by grind only [toNumberRatSig_ne_zero_of_isNormOrNonzeroSubnorm] - have ySigNeZero : y.toNumberRatSig ≠ 0 := by grind only [toNumberRatSig_ne_zero_of_isNormOrNonzeroSubnorm] - by_cases hxnorm : x.isNorm - · have := mul_two_pow_inj - x.toNumberRatSig - y.toNumberRatSig - x.toNumberRatExp - y.toNumberRatExp - (by grind only) - (by grind only [one_le_toNumberRatSig_of_isNorm]) - (by grind only [one_le_toNumberRatSig_of_isNorm]) - (by grind only [toNumberRatSig_lt_two]) - (by grind only [toNumberRatSig_lt_two]) - -- now I need to know that 'toNumberRatSig', 'toNumberRatExp' are equal. - have hSigEq := sig_eq_of_toNumberRatSig_eq_toNumberRatSig - (x := x) (y := y) (by grind) (by grind) - simp [hSigEq] - have hExpEq := exp_eq_of_toNumberRatExp_eq - (x := x) (y := y) (by grind) (by grind) (by grind) (by grind) - simp [hExpEq] - · have xSubnorm : x.isNonzeroSubnorm := by grind only [isNormOrSubnorm_eq_isNorm_or_isSubnorm] - have ySubnorm : y.isNonzeroSubnorm := by grind only [isNormOrSubnorm_eq_isNorm_or_isSubnorm] - have expEq : x.toNumberRatExp = y.toNumberRatExp := by - simp [x.toNumberRatExp_eq_of_not_isNorm (by grind only)] - simp [y.toNumberRatExp_eq_of_not_isNorm (by grind only)] - have sigEq : x.toNumberRatSig = y.toNumberRatSig := by - rw [← Rat.mul_cancel_right (x := 2 ^ x.toNumberRatExp)] - · rw [expEq] - grind only - · grind only [Rat.two_pow_int_ne_zero] - have : x.sig = y.sig := by - rw [x.toRatNumberSig_eq_of_not_isNorm (by grind only)] at sigEq - rw [y.toRatNumberSig_eq_of_not_isNorm (by grind only)] at sigEq - have hTwoPowNeZero : (2 : Rat) ^ s ≠ 0 := by norm_cast; grind only [usr Nat.pow_pos] - rw [Rat.div_cancel hTwoPowNeZero] at sigEq - simp at sigEq - apply BitVec.eq_of_toNat_eq - assumption - simp [this] - rw [exp_eq_of_isNonzeroSubnorm (by grind only)] - rw [exp_eq_of_isNonzeroSubnorm (by grind only)] - - - -@[simp, grind =>] -theorem PackedFloat.le_getInfinity_true_iff_eq (hs : 0 < s) - (y : PackedFloat e s) : - y ≤ PackedFloat.getInfinity e s true ↔ y = .getInfinity e s true := by - constructor - · intros h - grind only [le_iff_eq_of_isNaN, le_iff_eq_of_isNaN', le_antisymm_of_ne_NaN, - => getInfinity_true_le_of_not_isNaN] - · intros h - subst h - grind only [le_refl] - -@[grind =] -theorem isNormOrNonzeroSubnorm_of_not_NaN_not_Infinite_not_Zero {pf : PackedFloat e s} (h : ¬ pf.isNaN) (h2 : ¬ pf.isInfinite) (h3 : ¬ pf.isZero) : - pf.isNormOrNonzeroSubnorm := by - simp [isNormOrNonzeroSubnorm] - simp [isNaN] at h - simp [isInfinite] at h2 - simp [isZero] at h3 - grind - -@[grind ., simp] -theorem Rat.two_pow_ne_zero (n : Int) : (2 : Rat) ^ n ≠ 0 := by - apply Rat.ne_zero_of_zero_lt - norm_cast - grind -theorem eq_of_toExtRat'_eq (x y : PackedFloat e s) - (hx : ¬ x.isNaN) (hy : ¬ y.isNaN) (hxzero : ¬ x.isZero) (hyzero : ¬ y.isZero) - (h : x.toExtRat' = y.toExtRat') : x = y := by - simp [toExtRat'] at h - have hExtRateq := h - simp [hx, hy] at h - by_cases hxinf : x.isInfinite - · simp [hxinf] at h - by_cases hyinf : y.isInfinite - · simp [hyinf] at h - grind only [PackedFloat.eq_getInfinity_iff_isInfinity, → eq_mkInfinity_of_isInfinite, - !isInfinite_getInfinity, #5505cb0d9cd21b53] - · by_cases hyinf : y.isInfinite - · simp [hyinf] at h - grind only - · simp [hyinf] at h - · simp [hxinf] at h - by_cases hyinf : y.isInfinite - · simp [hyinf] at h - · simp [hyinf] at h - have := - PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq (x := x) (y := y) - (by grind only [= isNormOrNonzeroSubnorm_of_not_NaN_not_Infinite_not_Zero]) - (by grind only [= isNormOrNonzeroSubnorm_of_not_NaN_not_Infinite_not_Zero]) - (by sorry) - (by grind) - apply PackedFloat.ext <;> grind only /- @[simp] @@ -2736,6 +2583,266 @@ does not overflow when its width is set to `exponentWidth 1 s` (where def exponentWidth (e s : Nat) : Nat := (2 ^ (e - 1) + s - 1).log2 + 2 + +theorem Rat.lt_mul_self_of_lt_one {y} {x : Rat} (hx0 : 0 ≤ x ∧ x < 1) (hy : 0 < y) + : x * y < y := by + suffices x * y < 1 * y by grind only + apply Rat.lt_of_le_of_ne + · apply Rat.mul_le_mul_of_le_of_le_of_nonneg_of_nonneg <;> grind only + · intros hcontra + rw [Rat.mul_cancel_right] at hcontra <;> grind only + + +namespace PackedFloat + +/-- +Subnormal numbers are smaller than '2^minNormalExp'. +-/ +@[simp, grind .] +theorem toNumberRatSig_times_toNumberRatExp_lt_two_pow_minNormalExp_of_isNonzeroSubnorm {x : PackedFloat e s} + (hx : x.isNonzeroSubnorm) : + x.toNumberRatSig * (2 : Rat) ^ x.toNumberRatExp < (2 : Rat) ^ minNormalExp e := by + rw [toNumberRatExp_eq_of_not_isNorm] + apply Rat.lt_mul_self_of_lt_one + · grind only [zero_le_twoNumberRatSig, !toNumberRatSig_lt_ite, → not_isSubnorm_of_isNorm, + Rat.natCast_eq_zero_iff, = Bool.toNat.eq_1] + · grind only [Fp.Rat.two_pow_pos] + · grind only [→ not_isNorm_of_isSubnorm] + + +theorem Rat.zpow_sub_eq_zpow_mul_zpow {b : Rat} (hb : b ≠ 0) + (x y: Int) : b ^ (x - y) = b ^ x * b ^ (-y) := by + rw [Int.sub_eq_add_neg] + rw [Rat.zpow_add hb] + +theorem Rat.mul_sub (b x y : Rat) : b * (x - y) = b * x - b * y := by + grind only + +@[simp, grind .] +theorem Rat.one_le_two_pow_nat {n : Nat} : 1 ≤ (2 : Rat) ^ n := by + induction n with + | zero => grind + | succ n ih => + rw [Rat.pow_succ] + grind + +theorem Rat.two_pow_le_two_pow_of_le {x y : Int} (h : x ≤ y) : (2 : Rat) ^ x ≤ (2 : Rat) ^ y := by + rw [Rat.le_iff_sub_nonneg] + rw [show (2 : Rat) ^ x = (2 : Rat) ^ x * 1 by grind only] + rw [show y = x + (y - x) by grind only] + rw [Rat.zpow_add (by grind only)] + rw [← Rat.mul_sub] + have : 1 ≤ (2 : Rat) ^ (y - x) := by + have : ∃ (k : Nat), y - x = k := by + exact Int.nonneg_def.mp h + obtain ⟨k, hk⟩ := this + rw [hk] + simp + grind only [Rat.mul_nonneg, Rat.le_of_lt, Fp.Rat.two_pow_pos] + + +theorem Rat.le_mul_of_one_le_of_le {x y y' : Rat} (hx1 : 1 ≤ x) (hy : 0 ≤ y) (hy' : y ≤ y') + : y ≤ x * y' := by + suffices 1 * y ≤ x * y' by grind only + apply Rat.mul_le_mul_of_le_of_le_of_nonneg_of_nonneg + · grind only + · grind only + · grind only [Rat.le_of_lt, Fp.Rat.two_pow_pos] + · grind only [Rat.le_of_lt, Fp.Rat.two_pow_pos] + + +theorem Rat.le_mul_self_of_le_one_of_nonneg {y} {x : Rat} (hx0 : 0 ≤ x ∧ x ≤ 1) (hy : 0 ≤ y) + : x * y ≤ y := by + suffices x * y ≤ 1 * y by grind only + apply Rat.mul_le_mul_of_le_of_le_of_nonneg_of_nonneg + · grind only + · grind only + · grind only [Rat.le_of_lt, Fp.Rat.two_pow_pos] + · grind only [Rat.le_of_lt, Fp.Rat.two_pow_pos] + +@[simp, grind .] +theorem toNumberRatSig_times_toNumberRatExp_le_of_isNorm + {x : PackedFloat e s} + (hx : x.isNorm) : + (2 : Rat) ^ minNormalExp e ≤ x.toNumberRatSig * (2 : Rat) ^ x.toNumberRatExp := by + rw [x.toNumberRatExp_eq_of_isNorm (by grind only)] + norm_cast + -- rw [Rat.zpow_sub_eq_zpow_mul_zpow] + have : 1 ≤ x.toNumberRatSig := by grind only [one_le_toNumberRatSig_of_isNorm] + rw [minNormalExp] + have : x.ex ≠ 0 := by grind only [ex_ne_zero_if_isNorm, = BitVec.ofNat_eq_ofNat, + = BitVec.zero_eq] + norm_cast + -- 2 ^ - (bias e - 1) ≤ 2 ^ (- bias e) + have : (2 : Rat) ^ ((- ((bias e - 1) : Nat)) : Int) ≤ (2 : Rat) ^ (((x.ex.toNat: Int) - (bias e : Int)) : Int) := by + norm_cast + apply Rat.two_pow_le_two_pow_of_le + grind + apply Rat.le_mul_of_one_le_of_le + · grind only + · grind only [Rat.le_of_lt, Fp.Rat.two_pow_pos] + · grind only + + +/-- +write being isNorm in terms of an arithmetic condition. +-/ +theorem isNorm_iff_toNumberRatSig_times_toNumberRatExp_ge + {x : PackedFloat e s} + (hx : x.isNormOrNonzeroSubnorm) : + x.isNorm ↔ decide ((2 : Rat) ^ minNormalExp e ≤ x.toNumberRatSig * (2 : Rat) ^ x.toNumberRatExp) := by + simp only [decide_eq_true_eq] + constructor + · apply toNumberRatSig_times_toNumberRatExp_le_of_isNorm + · apply Classical.byContradiction + intros h + simp at h + have := x.toNumberRatSig_times_toNumberRatExp_lt_two_pow_minNormalExp_of_isNonzeroSubnorm (by grind) + grind only + +/-- +This is true because we can separate out the +cases of normal and subnormal based on values, +and the largest subnormal is smaller than the smallest normal. +-/ +@[simp, grind .] +theorem isNorm_eq_of_toNumberRat_eq {x y : PackedFloat e s} + (hx : x.isNormOrNonzeroSubnorm) (hy : y.isNormOrNonzeroSubnorm) + (heq : x.toNumberRat = y.toNumberRat) : + (x.isNorm = y.isNorm) := by + have hsign : x.sign = y.sign := by + apply PackedFloat.sign_eq_of_toNumberRat_eq hx hy heq + have h' := heq + simp [toNumberRat] at h' + rw [hsign] at h' + have : x.toNumberRatSig * 2 ^ x.toNumberRatExp = y.toNumberRatSig * 2 ^ y.toNumberRatExp := by + rw [← Rat.mul_cancel_left (x := x.sign.toSign)] + · grind + · simp + have xval := x.isNorm_iff_toNumberRatSig_times_toNumberRatExp_ge (by grind) + have yval := y.isNorm_iff_toNumberRatSig_times_toNumberRatExp_ge (by grind) + rcases hxnorm : x.isNorm + · simp [hxnorm] at this xval + rcases hynorm : y.isNorm + · simp only + · simp only [hynorm, toNumberRatSig_times_toNumberRatExp_le_of_isNorm, decide_true, + Bool.false_eq_true] at this yval ⊢ + grind only [toNumberRatSig_times_toNumberRatExp_le_of_isNorm] + · simp [hxnorm] at this xval + rcases hynorm : y.isNorm + · simp [hynorm] at this yval ⊢ + grind only [toNumberRatSig_times_toNumberRatExp_le_of_isNorm] + · simp only + + +@[simp, grind .] +theorem sig_eq_and_ex_eq_of_toNumberRat_eq {x y : PackedFloat e s} + (hx : x.isNormOrNonzeroSubnorm) (hy : y.isNormOrNonzeroSubnorm) + (heq : x.toNumberRat = y.toNumberRat) : + x.sign = y.sign ∧ x.sig = y.sig ∧ x.ex = y.ex := by + have hNormState := PackedFloat.isNorm_eq_of_toNumberRat_eq hx hy heq + have hSignEq : x.sign = y.sign := by + apply PackedFloat.sign_eq_of_toNumberRat_eq hx hy heq + simp [PackedFloat.toNumberRat] at heq + rw [hSignEq] at heq + simp [hSignEq] + have : x.toNumberRatSig * 2 ^ x.toNumberRatExp = y.toNumberRatSig * 2 ^ y.toNumberRatExp := by + rw [← Rat.mul_cancel_left (x := x.sign.toSign)] + · grind + · simp + have xsigNeZero : x.toNumberRatSig ≠ 0 := by grind only [toNumberRatSig_ne_zero_of_isNormOrNonzeroSubnorm] + have ySigNeZero : y.toNumberRatSig ≠ 0 := by grind only [toNumberRatSig_ne_zero_of_isNormOrNonzeroSubnorm] + by_cases hxnorm : x.isNorm + · have := mul_two_pow_inj + x.toNumberRatSig + y.toNumberRatSig + x.toNumberRatExp + y.toNumberRatExp + (by grind only) + (by grind only [one_le_toNumberRatSig_of_isNorm]) + (by grind only [one_le_toNumberRatSig_of_isNorm]) + (by grind only [toNumberRatSig_lt_two]) + (by grind only [toNumberRatSig_lt_two]) + -- now I need to know that 'toNumberRatSig', 'toNumberRatExp' are equal. + have hSigEq := sig_eq_of_toNumberRatSig_eq_toNumberRatSig + (x := x) (y := y) (by grind) (by grind) + simp [hSigEq] + have hExpEq := exp_eq_of_toNumberRatExp_eq + (x := x) (y := y) (by grind) (by grind) (by grind) (by grind) + simp [hExpEq] + · have xSubnorm : x.isNonzeroSubnorm := by grind only [isNormOrSubnorm_eq_isNorm_or_isSubnorm] + have ySubnorm : y.isNonzeroSubnorm := by grind only [isNormOrSubnorm_eq_isNorm_or_isSubnorm] + have expEq : x.toNumberRatExp = y.toNumberRatExp := by + simp [x.toNumberRatExp_eq_of_not_isNorm (by grind only)] + simp [y.toNumberRatExp_eq_of_not_isNorm (by grind only)] + have sigEq : x.toNumberRatSig = y.toNumberRatSig := by + rw [← Rat.mul_cancel_right (x := 2 ^ x.toNumberRatExp)] + · rw [expEq] + grind only + · grind only [Rat.two_pow_int_ne_zero] + have : x.sig = y.sig := by + rw [x.toRatNumberSig_eq_of_not_isNorm (by grind only)] at sigEq + rw [y.toRatNumberSig_eq_of_not_isNorm (by grind only)] at sigEq + have hTwoPowNeZero : (2 : Rat) ^ s ≠ 0 := by norm_cast; grind only [usr Nat.pow_pos] + rw [Rat.div_cancel hTwoPowNeZero] at sigEq + simp at sigEq + apply BitVec.eq_of_toNat_eq + assumption + simp [this] + rw [exp_eq_of_isNonzeroSubnorm (by grind only)] + rw [exp_eq_of_isNonzeroSubnorm (by grind only)] + + +@[simp, grind =>] +theorem le_getInfinity_true_iff_eq (hs : 0 < s) + (y : PackedFloat e s) : + y ≤ PackedFloat.getInfinity e s true ↔ y = .getInfinity e s true := by + constructor + · intros h + grind only [PackedFloat.le_iff_eq_of_isNaN, PackedFloat.le_iff_eq_of_isNaN', le_antisymm_of_ne_NaN, + => PackedFloat.getInfinity_true_le_of_not_isNaN] + · intros h + subst h + grind only [PackedFloat.le_refl] + +@[grind =] +theorem isNormOrNonzeroSubnorm_of_not_NaN_not_Infinite_not_Zero {pf : PackedFloat e s} (h : ¬ pf.isNaN) (h2 : ¬ pf.isInfinite) (h3 : ¬ pf.isZero) : + pf.isNormOrNonzeroSubnorm := by + simp [PackedFloat.isNormOrNonzeroSubnorm] + simp [PackedFloat.isNaN] at h + simp [PackedFloat.isInfinite] at h2 + simp [PackedFloat.isZero] at h3 + grind + +theorem eq_of_toExtRat'_eq (x y : PackedFloat e s) + (hx : ¬ x.isNaN) (hy : ¬ y.isNaN) (hxzero : ¬ x.isZero) (hyzero : ¬ y.isZero) + (h : x.toExtRat' = y.toExtRat') : x = y := by + simp [PackedFloat.toExtRat'] at h + have hExtRateq := h + simp [hx, hy] at h + by_cases hxinf : x.isInfinite + · simp [hxinf] at h + by_cases hyinf : y.isInfinite + · simp [hyinf] at h + grind only [PackedFloat.eq_getInfinity_iff_isInfinity, → eq_mkInfinity_of_isInfinite, + !isInfinite_getInfinity, #5505cb0d9cd21b53] + · by_cases hyinf : y.isInfinite + · simp [hyinf] at h + grind only + · simp [hyinf] at h + · simp [hxinf] at h + by_cases hyinf : y.isInfinite + · simp [hyinf] at h + · simp [hyinf] at h + have := + PackedFloat.sig_eq_and_ex_eq_of_toNumberRat_eq (x := x) (y := y) + (by grind only [= isNormOrNonzeroSubnorm_of_not_NaN_not_Infinite_not_Zero]) + (by grind only [= isNormOrNonzeroSubnorm_of_not_NaN_not_Infinite_not_Zero]) + (by grind) + apply PackedFloat.ext <;> grind only + +end PackedFloat + -- Constants /-- E5M2 floating point representation of 1.0 -/ diff --git a/Fp/Grind.lean b/Fp/Grind.lean index 6539782..418e6b3 100644 --- a/Fp/Grind.lean +++ b/Fp/Grind.lean @@ -115,4 +115,6 @@ theorem Nat.two_pow_le_two_pow_of_le {n m : Nat} attribute [grind =, grind =_] Nat.shiftLeft_eq + + end Fp diff --git a/Fp/Theorems/Packing.lean b/Fp/Theorems/Packing.lean index d85994e..5d9b362 100644 --- a/Fp/Theorems/Packing.lean +++ b/Fp/Theorems/Packing.lean @@ -318,7 +318,6 @@ theorem toExtRat_eq_toExtRat' {pf : PackedFloat e s} grind · simp only [Rat.add_mul] ac_nf - simp norm_cast congr 1 · simp only [← Rat.zpow_add (q := 2) (hq := by decide)] diff --git a/Fp/Utils.lean b/Fp/Utils.lean index b16168d..10b9727 100644 --- a/Fp/Utils.lean +++ b/Fp/Utils.lean @@ -3,6 +3,7 @@ import Fp.Tactics import Fp.Grind + theorem Rat.mul_ne_zero_iff {x y : Rat} : (¬ (x * y = 0)) ↔ x ≠ 0 ∧ y ≠ 0 := by grind @@ -90,3 +91,90 @@ theorem toEFixed_hExOffset (e s : Nat) : 2 ^ (e - 1) + s - 2 < 2 ^ e + s := by have hexp0 : 0 < 2^e := Nat.two_pow_pos _ have hexp1 : 2^(e-1) ≤ 2^e := two_pow_sub_one_le_two_pow e omega + + + +@[simp, grind .] +theorem Rat.div_cancel {p q d : Rat} (hd : d ≠ 0) : + (p / d = q / d) <-> p = q := by + rw [Rat.div_def, Rat.div_def] + rw [Rat.mul_cancel_right] + · grind + +@[grind .] +theorem Rat.twoPowNeZero (n : Int) : (2 : Rat) ^ n ≠ 0 := by + apply Rat.ne_zero_of_zero_lt + norm_cast + grind only [Fp.Rat.two_pow_pos] + +attribute [simp] Rat.zpow_natCast + +theorem Rat.mul_le_mul_of_le_of_le_of_nonneg_of_nonneg + {a b c d : Rat} (hab : a ≤ b) (hcd : c ≤ d) (hac : 0 ≤ a) (hcc : 0 ≤ c) : + a * c ≤ b * d := by + apply (Rat.le_iff_sub_nonneg (a * c) (b * d)).mpr + rw [show b = a + (b - a) by grind only] + rw [Rat.add_mul] + have : (b - a) ≥ 0 := by grind + have : 0 ≤ a * d := by + apply Rat.mul_nonneg <;> grind only + rw [show a * d + (b - a) * d - a * c = (b - a) * d + a * (d - c) by grind only] + have : 0 ≤ a * (d - c) := by + apply Rat.mul_nonneg <;> grind only + have : 0 ≤ (b - a) * d := by + apply Rat.mul_nonneg <;> grind only + grind only + +theorem Rat.mul_lt_mul_of_lt_of_le_of_nonneg_of_nonneg + {a b c d : Rat} (hab : a < b) (hcd : c ≤ d) (hac : 0 ≤ a) (hcc : 0 ≤ c) : + a * c ≤ b * d := by + apply (Rat.le_iff_sub_nonneg (a * c) (b * d)).mpr + rw [show b = a + (b - a) by grind only] + rw [Rat.add_mul] + have : (b - a) ≥ 0 := by grind + have : 0 ≤ a * d := by + apply Rat.mul_nonneg <;> grind only + rw [show a * d + (b - a) * d - a * c = (b - a) * d + a * (d - c) by grind only] + have : 0 ≤ a * (d - c) := by + apply Rat.mul_nonneg <;> grind only + have : 0 ≤ (b - a) * d := by + apply Rat.mul_nonneg <;> grind only + grind only + +theorem Rat.mul_lt_mul_of_le_of_lt_of_nonneg_of_nonneg + {a b c d : Rat} (hab : a <= b) (hcd : c < d) (hac : 0 ≤ a) (hcc : 0 ≤ c) : + a * c ≤ b * d := by + apply (Rat.le_iff_sub_nonneg (a * c) (b * d)).mpr + rw [show b = a + (b - a) by grind only] + rw [Rat.add_mul] + have : (b - a) ≥ 0 := by grind + have : 0 ≤ a * d := by + apply Rat.mul_nonneg <;> grind only + rw [show a * d + (b - a) * d - a * c = (b - a) * d + a * (d - c) by grind only] + have : 0 ≤ a * (d - c) := by + apply Rat.mul_nonneg <;> grind only + have : 0 ≤ (b - a) * d := by + apply Rat.mul_nonneg <;> grind only + grind only + +attribute [grind .] Rat.pow_pos + +@[grind .] +theorem Rat.two_pow_int_ne_zero {n : Int} : (2 : Rat) ^ n ≠ 0 := by + apply Rat.ne_zero_of_zero_lt + norm_cast + apply Rat.zpow_pos + grind only + +@[grind .] +theorem Rat.two_pow_nat_ne_zero {n : Nat} : (2 : Rat) ^ n ≠ 0 := by + apply Rat.ne_zero_of_zero_lt + norm_cast + exact Nat.two_pow_pos n + + +@[grind ., simp] +theorem Rat.two_pow_ne_zero (n : Int) : (2 : Rat) ^ n ≠ 0 := by + apply Rat.ne_zero_of_zero_lt + norm_cast + grind From c32e0dbda1aa9cbacebd6f02f9cbd8ff51ab1831 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Thu, 19 Feb 2026 15:27:33 +0000 Subject: [PATCH 35/44] chore: add theory --- Fp/Basic.lean | 7 +++++++ Fp/Theorems/Packing.lean | 5 ++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 2d19fd8..2fde194 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -2332,6 +2332,7 @@ def toDyadic (uf : UnpackedFloat e s) : Dyadic := def toRat (uf : UnpackedFloat e s) : Rat := uf.toDyadic.toRat +-- TODO: add a toRat', and show that these are equivalent. end UnpackedFloat @@ -2841,6 +2842,12 @@ theorem eq_of_toExtRat'_eq (x y : PackedFloat e s) (by grind) apply PackedFloat.ext <;> grind only + +/-- +info: 'PackedFloat.eq_of_toExtRat'_eq' depends on axioms: [propext, Classical.choice, Quot.sound] +-/ +#guard_msgs in #print axioms eq_of_toExtRat'_eq + end PackedFloat -- Constants diff --git a/Fp/Theorems/Packing.lean b/Fp/Theorems/Packing.lean index 5d9b362..c10a783 100644 --- a/Fp/Theorems/Packing.lean +++ b/Fp/Theorems/Packing.lean @@ -436,10 +436,9 @@ theorem toExtRat_unpack_eq_toExtRat {pf : PackedFloat e s} · simp only [EUnpackedFloat.toExtRat, ↓reduceIte, cond_false, EUnpackedFloat.isNaN_mkNumber, EUnpackedFloat.isInfinite_mkNumber, - EUnpackedFloat.num_mkNumber, toExtRat', hNaN, hInf, hZero, hNorm, cond_true, + EUnpackedFloat.num_mkNumber, toExtRat', hNaN, hInf, ExtRat.Number.injEq] - simp only [PackedFloat.toNumberRat, PackedFloat.toNumberRatSig, PackedFloat.toNumberRatExp, - hZero, hNorm, if_true] + simp only [PackedFloat.toNumberRat, PackedFloat.toNumberRatSig, PackedFloat.toNumberRatExp, hNorm, if_true] simp only [UnpackedFloat.toRat_eq, Rat.mul_assoc] congr 1 simp only [BitVec.toNat_cons', Nat.shiftLeft_eq, Bool.toNat, cond_true] From 6ddfc8fc78bd24296845413cda5ecad50711557d Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Fri, 20 Feb 2026 11:22:00 +0000 Subject: [PATCH 36/44] chore: more normalize --- Fp/Basic.lean | 137 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 2fde194..ae3d172 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -2303,6 +2303,7 @@ theorem sig_mkZero (sign : Bool) : (mkZero sign : UnpackedFloat e s).sig = 0#s : def isZero (uf : UnpackedFloat e s) : Bool := uf.ex == BitVec.intMin e && uf.sig == 0#s +-- | Why does the 'ex' fit? @[bv_normalize] def normalize (uf : UnpackedFloat e s) (sign := uf.sign) : UnpackedFloat e s := bif uf.sig == 0#s then @@ -2320,18 +2321,66 @@ theorem sign_normalize (uf : UnpackedFloat e s) : (normalize uf zsign).sign = if uf.sig == 0#s then zsign else uf.sign := by grind [normalize, mkZero] +@[simp] +theorem sig_normalize (uf : UnpackedFloat e s) : (normalize uf zsign).sig = + if uf.sig == 0#s then 0#s else uf.sig <<< uf.sig.clz := by + grind [normalize, mkZero] + +@[simp] +theorem exp_normalize (uf : UnpackedFloat e s) : (normalize uf zsign).ex = + if uf.sig == 0#s then BitVec.intMin e else uf.ex - uf.sig.clz.setWidth _ := by + grind [normalize, mkZero] + + @[bv_normalize] def toEUnpackedFloat (uf : UnpackedFloat e s) : EUnpackedFloat e s := .mk .Number uf def toDyadic (uf : UnpackedFloat e s) : Dyadic := let sig : BitVec (s + 1) := uf.sig.setWidth' (Nat.le.step Nat.le.refl) - let sig := bif uf.sign then -sig else sig - .ofIntWithPrec sig.toInt ((s - 1 : Nat) - uf.ex.toInt) + -- | this can lead to overflow in the case where + -- sig = intMin. negating intMin causes overflow, so we need to be careful. + .ofIntWithPrec (uf.sign.toSign * sig.toInt) ((s - 1 : Nat) - uf.ex.toInt) def toRat (uf : UnpackedFloat e s) : Rat := uf.toDyadic.toRat + +def toSigNat (uf : UnpackedFloat e s) : Nat := + let sig : BitVec (s + 1) := uf.sig.setWidth' (Nat.le.step Nat.le.refl) + sig.toNat + +@[simp] +theorem toNat_toSigNat_eq (uf : UnpackedFloat e s) : + toSigNat uf = uf.sig.toNat := by + simp [toSigNat] + +@[simp] +theorem toSigNat_of_sig_eq_zero (uf : UnpackedFloat e s) (h : uf.sig = 0#s) : + uf.toSigNat = 0 := by + simp [toSigNat, h] + +def toExpInt (uf : UnpackedFloat e s) : Int := + - ((s - 1 : Nat) - uf.ex.toInt) + +def toRat' (uf : UnpackedFloat e s) : Rat := + uf.sign.toSign * uf.toSigNat * (2 : Rat) ^ uf.toExpInt + +theorem toInt_setWidth_succ_eq_toNat (x : BitVec w) : + (x.setWidth (w + 1)).toInt = x.toNat := by + rw [BitVec.toInt_eq_toNat_of_msb] + · simp + · grind only [= BitVec.msb_eq_getMsbD_zero, = BitVec.getMsbD_setWidth] + +theorem toRat_eq_toRat' (uf : UnpackedFloat e s) : uf.toRat = uf.toRat' := by + rw [toRat, toRat'] + rw [UnpackedFloat.toDyadic] + rw [Dyadic.toRat_ofIntWithPrec_eq_mul_two_pow] + simp only [BitVec.setWidth'_eq] + rw [toInt_setWidth_succ_eq_toNat (x := uf.sig)] + simp [toExpInt] + norm_cast + -- TODO: add a toRat', and show that these are equivalent. end UnpackedFloat @@ -2850,6 +2899,90 @@ info: 'PackedFloat.eq_of_toExtRat'_eq' depends on axioms: [propext, Classical.ch end PackedFloat +namespace UnpackedFloat + +@[simp] +theorem BitVec.clz_zero (w : Nat) : (0#w : BitVec w).clz = w := by + rw [BitVec.clz_eq_iff_eq_zero] + + +@[simp, grind =] +theorem toNat_clz_lt_iff_ne_zero (x : BitVec w) : x.clz.toNat < w ↔ x ≠ 0#w := by + have := BitVec.clz_lt_iff_ne_zero (x := x) + by_cases hx : x = 0#w + · simp [hx] + · simp [hx] + have := this.mpr (by grind only) + simp [BitVec.lt_def] at this + grind only + +-- | TODO: move this into a separate 'def', because it does sth important: +-- it moves the leading 1 of the significand to the front, +-- so we probably want to buidld theory about it? +theorem toNat_shiftLeft_clz_eq_toNat (uf : UnpackedFloat e s) : + (uf.sig <<< uf.sig.clz.toNat).toNat = uf.sig.toNat <<< uf.sig.clz.toNat := by + by_cases hs : s = 0 + · simp [hs] + grind only [= Nat.shiftLeft_eq, = BitVec.toNat_zero_length] + · by_cases hsig : uf.sig = 0#s + · simp [hsig] + · simp only [BitVec.toNat_shiftLeft] + apply Nat.mod_eq_of_lt + have : uf.sig.toNat < 2 ^ s := by grind + have := BitVec.two_pow_sub_clz_le_toNat_of_ne_zero (x := uf.sig) (by grind only) (by grind only) + have := BitVec.toNat_lt_two_pow_sub_clz (x := uf.sig) (w := s) + have : uf.sig.clz.toNat < s := by + grind only [#61b3] + rw [Nat.shiftLeft_eq] + apply Nat.lt_of_lt_of_le (m := 2 ^ (s - uf.sig.clz.toNat) * (2 ^ uf.sig.clz.toNat)) + · apply Nat.mul_lt_mul_of_lt_of_le + · grind only + · apply Nat.pow_le_pow_of_le + · grind only + · grind only + · grind only [usr Nat.pow_pos] + · rw [← Nat.pow_add] + apply Nat.pow_le_pow_of_le + · grind only + · grind only + + +-- TODO: find a more natural phrasing that +-- this does not overflow. +theorem UnpackedFloat.toRat_normalize_eq {uf : UnpackedFloat e s} + (hex : -(↑(2 ^ e) / 2) ≤ uf.ex.toInt - ↑uf.sig.clz.toNat): + uf.toRat = uf.normalize.toRat := by + simp only [UnpackedFloat.toRat_eq_toRat'] + simp only [toRat', sign_normalize, beq_iff_eq, ite_self] + rw [UnpackedFloat.normalize] + by_cases hsig : uf.sig = 0#s + · simp [hsig] + · simp only [show ¬uf.sig == 0#s by grind, BitVec.shiftLeft_eq', cond_false] + simp only [toNat_toSigNat_eq] + rw [toNat_shiftLeft_clz_eq_toNat] + simp only [toExpInt, BitVec.toInt_sub, BitVec.toInt_setWidth, + Int.sub_bmod_bmod] + + have : uf.ex.toInt.bmod (2^e) = uf.ex.toInt := by + rw [BitVec.toInt_eq_toNat_bmod] + simp + have hbmod : (uf.ex.toInt - uf.sig.clz.toNat).bmod (2^e) = uf.ex.toInt - uf.sig.clz.toNat := by + rw [Int.bmod_eq_of_le] + · grind + · grind + rw [hbmod] + have := BitVec.toNat_lt_two_pow_sub_clz (x := uf.sig) (w := s) + rw [Nat.shiftLeft_eq] + simp + push_cast + -- ⊢ ↑uf.sign.toSign * ↑uf.sig.toNat * 2 ^ (-(↑(s - 1) - uf.ex.toInt)) = + -- ↑uf.sign.toSign * (↑uf.sig.toNat * 2 ^ uf.sig.clz.toNat) * + -- 2 ^ (-(↑(s - 1) - (uf.ex.toInt - ↑uf.sig.clz.toNat))) + -- reassociate, we need to gather all the 2^(blah) and cancel the powers + sorry + +end UnpackedFloat + -- Constants /-- E5M2 floating point representation of 1.0 -/ From 51c4fd8a1cba0868889d79eb5722078d1723d711 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Fri, 20 Feb 2026 13:13:31 +0000 Subject: [PATCH 37/44] chore: prove that normalize preserves denotation if exponent does not overflow by negative wraparound --- Fp/Basic.lean | 80 +++++++++++++++------------------------------------ Fp/Utils.lean | 49 +++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 57 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index ae3d172..2744570 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -2660,55 +2660,6 @@ theorem toNumberRatSig_times_toNumberRatExp_lt_two_pow_minNormalExp_of_isNonzero · grind only [→ not_isNorm_of_isSubnorm] -theorem Rat.zpow_sub_eq_zpow_mul_zpow {b : Rat} (hb : b ≠ 0) - (x y: Int) : b ^ (x - y) = b ^ x * b ^ (-y) := by - rw [Int.sub_eq_add_neg] - rw [Rat.zpow_add hb] - -theorem Rat.mul_sub (b x y : Rat) : b * (x - y) = b * x - b * y := by - grind only - -@[simp, grind .] -theorem Rat.one_le_two_pow_nat {n : Nat} : 1 ≤ (2 : Rat) ^ n := by - induction n with - | zero => grind - | succ n ih => - rw [Rat.pow_succ] - grind - -theorem Rat.two_pow_le_two_pow_of_le {x y : Int} (h : x ≤ y) : (2 : Rat) ^ x ≤ (2 : Rat) ^ y := by - rw [Rat.le_iff_sub_nonneg] - rw [show (2 : Rat) ^ x = (2 : Rat) ^ x * 1 by grind only] - rw [show y = x + (y - x) by grind only] - rw [Rat.zpow_add (by grind only)] - rw [← Rat.mul_sub] - have : 1 ≤ (2 : Rat) ^ (y - x) := by - have : ∃ (k : Nat), y - x = k := by - exact Int.nonneg_def.mp h - obtain ⟨k, hk⟩ := this - rw [hk] - simp - grind only [Rat.mul_nonneg, Rat.le_of_lt, Fp.Rat.two_pow_pos] - - -theorem Rat.le_mul_of_one_le_of_le {x y y' : Rat} (hx1 : 1 ≤ x) (hy : 0 ≤ y) (hy' : y ≤ y') - : y ≤ x * y' := by - suffices 1 * y ≤ x * y' by grind only - apply Rat.mul_le_mul_of_le_of_le_of_nonneg_of_nonneg - · grind only - · grind only - · grind only [Rat.le_of_lt, Fp.Rat.two_pow_pos] - · grind only [Rat.le_of_lt, Fp.Rat.two_pow_pos] - - -theorem Rat.le_mul_self_of_le_one_of_nonneg {y} {x : Rat} (hx0 : 0 ≤ x ∧ x ≤ 1) (hy : 0 ≤ y) - : x * y ≤ y := by - suffices x * y ≤ 1 * y by grind only - apply Rat.mul_le_mul_of_le_of_le_of_nonneg_of_nonneg - · grind only - · grind only - · grind only [Rat.le_of_lt, Fp.Rat.two_pow_pos] - · grind only [Rat.le_of_lt, Fp.Rat.two_pow_pos] @[simp, grind .] theorem toNumberRatSig_times_toNumberRatExp_le_of_isNorm @@ -2733,7 +2684,6 @@ theorem toNumberRatSig_times_toNumberRatExp_le_of_isNorm · grind only [Rat.le_of_lt, Fp.Rat.two_pow_pos] · grind only - /-- write being isNorm in terms of an arithmetic condition. -/ @@ -2947,10 +2897,28 @@ theorem toNat_shiftLeft_clz_eq_toNat (uf : UnpackedFloat e s) : · grind only +theorem two_zpow_mul_two_zpow_neg_eq_one (z : Int) : + (2 : Rat) ^ z * (2 : Rat) ^ (-z) = 1 := by + rw [← Rat.zpow_add (by decide)] + rw [show z + (-z) = 0 by grind] + simp + +/- +Use this to simplify exponentiation in Q, +since grind knows the field axioms, +and can correctly deduce from this +that these are multiplicative inverses. +-/ +theorem two_pow_mul_two_pow_neg_intCast_eq_one (z : Nat) : + (2 : Rat) ^ z * (2 : Rat) ^ (-( z : Int)) = 1 := by + have := two_zpow_mul_two_zpow_neg_eq_one (z := z) + simp at this + grind + -- TODO: find a more natural phrasing that -- this does not overflow. theorem UnpackedFloat.toRat_normalize_eq {uf : UnpackedFloat e s} - (hex : -(↑(2 ^ e) / 2) ≤ uf.ex.toInt - ↑uf.sig.clz.toNat): + (hex : -(↑(2 ^ e) / 2) ≤ uf.ex.toInt - ↑uf.sig.clz.toNat) : uf.toRat = uf.normalize.toRat := by simp only [UnpackedFloat.toRat_eq_toRat'] simp only [toRat', sign_normalize, beq_iff_eq, ite_self] @@ -2969,17 +2937,15 @@ theorem UnpackedFloat.toRat_normalize_eq {uf : UnpackedFloat e s} have hbmod : (uf.ex.toInt - uf.sig.clz.toNat).bmod (2^e) = uf.ex.toInt - uf.sig.clz.toNat := by rw [Int.bmod_eq_of_le] · grind - · grind + · grind only [usr BitVec.two_mul_toInt_lt] rw [hbmod] have := BitVec.toNat_lt_two_pow_sub_clz (x := uf.sig) (w := s) rw [Nat.shiftLeft_eq] simp push_cast - -- ⊢ ↑uf.sign.toSign * ↑uf.sig.toNat * 2 ^ (-(↑(s - 1) - uf.ex.toInt)) = - -- ↑uf.sign.toSign * (↑uf.sig.toNat * 2 ^ uf.sig.clz.toNat) * - -- 2 ^ (-(↑(s - 1) - (uf.ex.toInt - ↑uf.sig.clz.toNat))) - -- reassociate, we need to gather all the 2^(blah) and cancel the powers - sorry + simp only [Int.neg_sub] + simp only [Rat.zpow_sub_eq_zpow_mul_zpow (b := 2) (hb := by decide)] + grind only [two_pow_mul_two_pow_neg_intCast_eq_one] end UnpackedFloat diff --git a/Fp/Utils.lean b/Fp/Utils.lean index 10b9727..56f877c 100644 --- a/Fp/Utils.lean +++ b/Fp/Utils.lean @@ -178,3 +178,52 @@ theorem Rat.two_pow_ne_zero (n : Int) : (2 : Rat) ^ n ≠ 0 := by apply Rat.ne_zero_of_zero_lt norm_cast grind + +theorem Rat.zpow_sub_eq_zpow_mul_zpow {b : Rat} (hb : b ≠ 0) + (x y: Int) : b ^ (x - y) = b ^ x * b ^ (-y) := by + rw [Int.sub_eq_add_neg] + rw [Rat.zpow_add hb] + +theorem Rat.mul_sub (b x y : Rat) : b * (x - y) = b * x - b * y := by + grind only + +@[simp, grind .] +theorem Rat.one_le_two_pow_nat {n : Nat} : 1 ≤ (2 : Rat) ^ n := by + induction n with + | zero => grind + | succ n ih => + rw [Rat.pow_succ] + grind + +theorem Rat.two_pow_le_two_pow_of_le {x y : Int} (h : x ≤ y) : (2 : Rat) ^ x ≤ (2 : Rat) ^ y := by + rw [Rat.le_iff_sub_nonneg] + rw [show (2 : Rat) ^ x = (2 : Rat) ^ x * 1 by grind only] + rw [show y = x + (y - x) by grind only] + rw [Rat.zpow_add (by grind only)] + rw [← Rat.mul_sub] + have : 1 ≤ (2 : Rat) ^ (y - x) := by + have : ∃ (k : Nat), y - x = k := by + exact Int.nonneg_def.mp h + obtain ⟨k, hk⟩ := this + rw [hk] + simp + grind only [Rat.mul_nonneg, Rat.le_of_lt, Fp.Rat.two_pow_pos] + +theorem Rat.le_mul_of_one_le_of_le {x y y' : Rat} (hx1 : 1 ≤ x) (hy : 0 ≤ y) (hy' : y ≤ y') + : y ≤ x * y' := by + suffices 1 * y ≤ x * y' by grind only + apply Rat.mul_le_mul_of_le_of_le_of_nonneg_of_nonneg + · grind only + · grind only + · grind only [Rat.le_of_lt, Fp.Rat.two_pow_pos] + · grind only [Rat.le_of_lt, Fp.Rat.two_pow_pos] + + +theorem Rat.le_mul_self_of_le_one_of_nonneg {y} {x : Rat} (hx0 : 0 ≤ x ∧ x ≤ 1) (hy : 0 ≤ y) + : x * y ≤ y := by + suffices x * y ≤ 1 * y by grind only + apply Rat.mul_le_mul_of_le_of_le_of_nonneg_of_nonneg + · grind only + · grind only + · grind only [Rat.le_of_lt, Fp.Rat.two_pow_pos] + · grind only [Rat.le_of_lt, Fp.Rat.two_pow_pos] From 536fb5243050005bc76e24f1228c4e3428794975 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Sat, 21 Feb 2026 14:05:24 +0000 Subject: [PATCH 38/44] chore: fp --- Fp/Basic.lean | 2 +- Fp/Theorems/Packing.lean | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 2744570..199a859 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -2919,7 +2919,7 @@ theorem two_pow_mul_two_pow_neg_intCast_eq_one (z : Nat) : -- this does not overflow. theorem UnpackedFloat.toRat_normalize_eq {uf : UnpackedFloat e s} (hex : -(↑(2 ^ e) / 2) ≤ uf.ex.toInt - ↑uf.sig.clz.toNat) : - uf.toRat = uf.normalize.toRat := by + uf.normalize.toRat = uf.toRat := by simp only [UnpackedFloat.toRat_eq_toRat'] simp only [toRat', sign_normalize, beq_iff_eq, ite_self] rw [UnpackedFloat.normalize] diff --git a/Fp/Theorems/Packing.lean b/Fp/Theorems/Packing.lean index c10a783..24aec06 100644 --- a/Fp/Theorems/Packing.lean +++ b/Fp/Theorems/Packing.lean @@ -230,7 +230,7 @@ theorem toRat_normalize_eq_toRat {uf : UnpackedFloat e s} have hSigClzNeZero' : uf.sig.clz.toNat < s := by rw [BitVec.lt_def] at hSigClzNeZero simp at hSigClzNeZero - apply hSigClzNeZero + sorry rw [BitVec.toInt_setWidth] have : (uf.sig.clz.toNat : Int).bmod (2 ^ e) = uf.sig.clz.toNat := by refine Int.bmod_eq_of_le hle ?_ From 757ffc616970bfd19dc6dbc16ee08ceb1bbc6b41 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Sat, 21 Feb 2026 14:14:39 +0000 Subject: [PATCH 39/44] chore: packing --- Fp/Theorems/Packing.lean | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Fp/Theorems/Packing.lean b/Fp/Theorems/Packing.lean index 24aec06..c350167 100644 --- a/Fp/Theorems/Packing.lean +++ b/Fp/Theorems/Packing.lean @@ -100,15 +100,19 @@ theorem toEUnpackedFloat_not_isInfinite {uf : UnpackedFloat e s} theorem toRat_eq {uf : UnpackedFloat e s} : uf.toRat = uf.sign.toSign * uf.sig.toNat * 2 ^ (uf.ex.toInt - (s - 1 : Nat)) := by have hmsb : (uf.sig.setWidth' _).msb = false := BitVec.msb_setWidth'_of_lt (Nat.lt_succ_self s) - simp only [toRat, toDyadic, cond_eq_ite, Dyadic.toRat_ofIntWithPrec_eq_mul_two_pow, + simp only [toRat, toDyadic, Dyadic.toRat_ofIntWithPrec_eq_mul_two_pow, Int.neg_sub, Bool.toSign] congr split · simp only [Rat.intCast_neg, Rat.intCast_ofNat, ← Rat.intCast_natCast, Rat.neg_mul, Rat.one_mul] rewrite [← Rat.intCast_neg] congr - rewrite [← Int.neg_inj, BitVec.neg_toInt_neg hmsb] + simp only [Int.reduceNeg, BitVec.setWidth'_eq, Int.neg_one_mul, + Int.neg_inj] + rw [BitVec.toInt_eq_msb_cond] simp + intros hcontra + simp [BitVec.msb_eq_getLsbD_last] at hcontra · rewrite [BitVec.toInt_eq_toNat_of_msb hmsb] simp [Rat.intCast_natCast] @@ -230,7 +234,7 @@ theorem toRat_normalize_eq_toRat {uf : UnpackedFloat e s} have hSigClzNeZero' : uf.sig.clz.toNat < s := by rw [BitVec.lt_def] at hSigClzNeZero simp at hSigClzNeZero - sorry + apply (toNat_clz_lt_iff_ne_zero ..) |>.mpr h rw [BitVec.toInt_setWidth] have : (uf.sig.clz.toNat : Int).bmod (2 ^ e) = uf.sig.clz.toNat := by refine Int.bmod_eq_of_le hle ?_ @@ -372,7 +376,7 @@ theorem exponentWidth_gt_zero : exponentWidth e s > 0 := by -- | TODO: refactor by pulling out lemmas that talk about the 'toNat' of the various -- significand, and so on. theorem toExtRat_unpack_eq_toExtRat {pf : PackedFloat e s} - : pf.unpack.toExtRat = pf.toExtRat := by + : pf.unpack.toExtRat = pf.toExtRat := by simp only [unpack, unpackNormOrNonzeroSubnorm, BitVec.truncate_eq_setWidth, toExtRat_eq_toExtRat'] cases hNaN : pf.isNaN · cases hInf : pf.isInfinite @@ -480,7 +484,7 @@ theorem toExtRat_unpack_eq_toExtRat {pf : PackedFloat e s} Nat.add_one_sub_one, @Int.sub_eq_add_neg _ s, @Rat.zpow_add 2 (by decide)] rw [Rat.mul_comm _ (2 ^ (-s : Int)), ← Rat.mul_assoc, Rat.add_mul, ← Rat.zpow_natCast, ← Rat.zpow_add (by decide) s (-s), Int.add_neg_eq_sub, Int.sub_self, Rat.zpow_zero, Rat.zpow_neg, ← Rat.div_def] · simp only [EUnpackedFloat.toExtRat, cond_true, cond_false, EUnpackedFloat.mkZero_not_isNaN, - EUnpackedFloat.mkZero_not_isInfinite, toExtRat', hNaN, hInf, hZero] + EUnpackedFloat.mkZero_not_isInfinite, toExtRat', hNaN, hInf] simp [EUnpackedFloat.mkZero] simp [hZero] · simp only [EUnpackedFloat.toExtRat, cond_true, cond_false, From 457a2584ec351c31b86d4133dd4c3f6316985522 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Sat, 21 Feb 2026 14:16:54 +0000 Subject: [PATCH 40/44] chore: try to unify abdal's theory with my theory --- Fp/Basic.lean | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 199a859..5b6882c 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -1579,7 +1579,6 @@ theorem plus_zero_not_le_minus_zero : ¬ (PackedFloat.getZero e s false ≤ PackedFloat.getZero e s true) := by simp [getZero, ← PackedFloat.le_def, PackedFloat.le, PackedFloat.isNaN] - instance {x y : PackedFloat e s} : Decidable (x ≤ y) := by simp only [← PackedFloat.le_def] infer_instance @@ -2360,7 +2359,7 @@ theorem toSigNat_of_sig_eq_zero (uf : UnpackedFloat e s) (h : uf.sig = 0#s) : uf.toSigNat = 0 := by simp [toSigNat, h] -def toExpInt (uf : UnpackedFloat e s) : Int := +def toExpInt {e s} (uf : UnpackedFloat e s) : Int := - ((s - 1 : Nat) - uf.ex.toInt) def toRat' (uf : UnpackedFloat e s) : Rat := @@ -2382,7 +2381,6 @@ theorem toRat_eq_toRat' (uf : UnpackedFloat e s) : uf.toRat = uf.toRat' := by norm_cast -- TODO: add a toRat', and show that these are equivalent. - end UnpackedFloat namespace EUnpackedFloat From 983c71fb669ef86bf8886cfd64931767c76b452a Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 23 Feb 2026 10:55:46 +0000 Subject: [PATCH 41/44] chore: write lots of sorries about lower behaviour the next thing we need to know is that the packed float ordering is discrete with endpoints, which means that we need a separation like theorem that says that there exists a 'successor' and 'predecessor' packed float from every packed float, and that 'lower = upper \/ successor lower = upper'. I will probably first prove 'successor' exists, and then defining the rest of the theory based on this. --- Fp/Basic.lean | 21 +++- Fp/SmtLibSemantics.lean | 258 ++++++++++++++++++++++++++++++++------- Fp/Theorems/Packing.lean | 3 +- 3 files changed, 229 insertions(+), 53 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 5b6882c..9dd10dc 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -644,7 +644,7 @@ theorem isZeroOrSubnorm_of_isNonzeroSubnorm {pf : PackedFloat e s} : grind [isNonzeroSubnorm, isZeroOrSubnorm] - +-- TODO: delete 'isNZero', 'isPZero'. @[grind →] theorem isZero_of_isNZero {pf : PackedFloat e s} : pf.isNZero → pf.isZero := by @@ -1768,7 +1768,7 @@ def toExtRat' (pf : PackedFloat e s) : ExtRat := else .Number pf.toNumberRat -@[simp] +@[simp, grind =] theorem toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm {pf : PackedFloat e s} (hp : pf.isNormOrNonzeroSubnorm := by grind) : pf.toExtRat' = .Number pf.toNumberRat := by @@ -1781,7 +1781,7 @@ theorem toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm {pf : PackedFloat e s} (hp simp [toExtRat', hnan, hinf, toNumberRat] -@[simp] +@[simp, grind =] theorem toExtRat'_eq_zero_of_isZero (pf : PackedFloat e s) (hp : pf.isZero) : pf.toExtRat' = .Number 0 := by have hnan : pf.isNaN = false := by @@ -1791,19 +1791,19 @@ theorem toExtRat'_eq_zero_of_isZero (pf : PackedFloat e s) (hp : pf.isZero) : simp only [toExtRat', hnan, hinf, cond_false, ExtRat.Number.injEq] grind -@[simp] +@[simp, grind =] theorem toExtRat'_eq_NaN_of_isNaN (pf : PackedFloat e s) (hp : pf.isNaN) : pf.toExtRat' = .NaN := by simp [toExtRat', hp] -@[simp] +@[simp, grind =] theorem toExtRat'_eq_Infinity_of_isInfinite (pf : PackedFloat e s) (hp : pf.isInfinite) : pf.toExtRat' = .Infinity pf.sign := by rw [toExtRat', hp] grind [not_isNaN_of_isInfinite] -@[simp, grind! .] +@[simp, grind! .] -- aggressive? theorem toExtRat'_getInfinity {sign : Bool} (hs : 0 < s := by grind) : (PackedFloat.getInfinity e s sign).toExtRat' = .Infinity sign := by have : (PackedFloat.getInfinity e s sign).isInfinite = true := by @@ -2845,6 +2845,15 @@ info: 'PackedFloat.eq_of_toExtRat'_eq' depends on axioms: [propext, Classical.ch -/ #guard_msgs in #print axioms eq_of_toExtRat'_eq + +@[grind ., grind =] +theorem isNaN_iff_toExtRat'_eq_NaN (x : PackedFloat e s) (hs : 0 < s) : + x.isNaN ↔ x.toExtRat' = .NaN := by + constructor + · intros h + simp [h] + · intros hx + induction x using PackedFloat.classification <;> grind end PackedFloat namespace UnpackedFloat diff --git a/Fp/SmtLibSemantics.lean b/Fp/SmtLibSemantics.lean index 4e0fb18..819324d 100644 --- a/Fp/SmtLibSemantics.lean +++ b/Fp/SmtLibSemantics.lean @@ -264,7 +264,7 @@ def IsLawfulLower [ExtendedNumber R] [RE : RoundableEmbed X R] (r : R) (lower : RE.embed lower ≤ r ∧ (∀ (lower' : X), RE.embed lower' ≤ r → RE.embed lower' ≤ RE.embed lower) @[grind →] -theorem IsLawfulLower.functional [ExtendedNumber R] [RE : RoundableEmbed X R] [Std.IsPartialOrder R] (r : R) (lower1 lower2 : X) : +theorem IsLawfulLower.embed_eq_embed [ExtendedNumber R] [RE : RoundableEmbed X R] [Std.IsPartialOrder R] (r : R) (lower1 lower2 : X) : IsLawfulLower r lower1 → IsLawfulLower r lower2 → RE.embed lower1 = RE.embed lower2 := by intro hl1 hl2 cases hl1 with @@ -275,38 +275,34 @@ theorem IsLawfulLower.functional [ExtendedNumber R] [RE : RoundableEmbed X R] [S have hle21 : RE.embed lower2 ≤ RE.embed lower1 := hglb1 lower2 hle2 grind --- theorem IsLawfulLower_always_exists {X R} [Inhabited X] [ExtendedNumber R] [RE : RoundableEmbed X R] (r : R) : --- ∃ (x : X), IsLawfulLower r x := by --- apply Classical.byContradiction --- intros h --- simp at h --- simp [IsLawfulLower] at h +/-- lower is the larger than other lower bounds. -/ +@[grind →] +theorem le_lower_of_le_of_IsLawfulLower [ExtendedNumber R] [RE : RoundableEmbed X R] (r : R) (lower lower' : X) + (hlawful : IsLawfulLower r lower) (hle : RE.embed lower' ≤ r) : + RE.embed lower' ≤ RE.embed lower := by + grind [IsLawfulLower] + +/-- lower is an lower bound -/ +@[grind →] +theorem lower_le_of_IsLawfulLower [ExtendedNumber R] [RE : RoundableEmbed X R] (r : R) (lower : X) + (hlawful : IsLawfulLower r lower) : + RE.embed lower ≤ r := by grind [IsLawfulLower] +@[grind →] +theorem IsLawfulLower.embed_eq_of_IfLawfulLower [ExtendedNumber R] [RE : RoundableEmbed X R] [Std.IsPartialOrder R] (r : R) (upper1 upper2 : X) : + IsLawfulLower r upper1 → IsLawfulLower r upper2 → RE.embed upper1 = RE.embed upper2 := by + intro hu1 hu2 + cases hu1 with + | intro hle1 lub1 => + cases hu2 with + | intro hle2 lub2 => + grind only [#4bb8, #5630] open Classical in noncomputable def smtLibLower [Inhabited X] [ExtendedNumber R] [RoundableEmbed X R] : RoundableLower X R where lower (r : R) : X := - if hp : ∃ (x : X), IsLawfulLower r x then - Classical.choose hp - else - default - -@[grind .] -theorem embed_smtLibLower_eq_of_IsLawfulLower [Inhabited X] [ExtendedNumber R] [instEmbed : RoundableEmbed X R] [Std.IsPartialOrder R] (r : R) (lower : X) : - IsLawfulLower r lower → instEmbed.embed (smtLibLower.lower r) = instEmbed.embed lower := by - intro hl - simp [smtLibLower] - split - case isTrue h => - obtain ⟨x, hlx⟩ := h - have : instEmbed.embed x = instEmbed.embed lower := by - apply IsLawfulLower.functional r x lower hlx hl - grind - case isFalse h => - simp at h - grind - + epsilon (IsLawfulLower r) -- TODO: need to know that IsLawfulLower is functional. @@ -315,8 +311,10 @@ theorem embed_smtLibLower_eq_of_IsLawfulLower [Inhabited X] [ExtendedNumber R] [ def IsLawfulUpper [ExtendedNumber R] [RE : RoundableEmbed X R] (r : R) (upper : X) : Prop := r ≤ RE.embed upper ∧ (∀ (upper' : X), r ≤ RE.embed upper' → RE.embed upper ≤ RE.embed upper') + + @[grind →] -theorem IsLawfulUpper.functional [ExtendedNumber R] [RE : RoundableEmbed X R] [Std.IsPartialOrder R] (r : R) (upper1 upper2 : X) : +theorem IsLawfulUpper.embed_eq_of_IsLawfulUpper [ExtendedNumber R] [RE : RoundableEmbed X R] [Std.IsPartialOrder R] (r : R) (upper1 upper2 : X) : IsLawfulUpper r upper1 → IsLawfulUpper r upper2 → RE.embed upper1 = RE.embed upper2 := by intro hu1 hu2 cases hu1 with @@ -330,11 +328,7 @@ theorem IsLawfulUpper.functional [ExtendedNumber R] [RE : RoundableEmbed X R] [S open Classical in noncomputable def smtLibUpper {X R} [Inhabited X] [ExtendedNumber R] [RoundableEmbed X R] : RoundableUpper X R where upper (r : R) : X := - if hp : ∃ (x : X), IsLawfulUpper r x then - /- Use hilbert epsilon to pick -/ - Classical.choose hp - else - default + epsilon (IsLawfulUpper r) /-- The default SMT-Lib adjunction of packed floats into rationals, written `v_ε,σ(f)`, @@ -367,19 +361,38 @@ theorem smtLiV.upper_toRoundableEmbed_eq [Inhabited X] [ExtendedNumber R] (embed theorem RoundableEmbed_embedPackedFloatExtRat_eq_smtLibV_embed: RoundableEmbed.embed (self := embedPackedFloatExtRat e s) = PackedFloat.toExtRat := rfl +open Classical in +/-- +If a property 'P' is a functional property, then 'epsilon P' +will pick the element 'p' which obeys that property. +-/ +theorem epsilon_eq_of_eq [Nonempty α] (P : α → Prop) + (hP : ∀ (a b : α), P a → P b → a = b) + {p : α} + (hp : P p) : + epsilon P = p := by + have := epsilon_spec (p := P) ⟨p, hp⟩ + apply hP <;> assumption + +-- open Classical in +-- theorem epsilon_eq_of_eq_of [Nonempty α] (P : α → Prop) (Q : α → Prop) +-- (hP : ∀ (a b : α), Q a → Q b → a = b) +-- {p : α} +-- (hp : P p) +-- (hq : Q p) +-- (hpq : ∀ (a : α), Q a → P a) : +-- epsilon P = p := by +-- have := epsilon_spec (p := P) ⟨p, hp⟩ +-- have := epsilon_spec (p := Q) ⟨p, hq⟩ + + -@[simp, grind .] -theorem toExtRat'_smtLibLower_eq_toExtRat'_of_IsLawfulLower (r : ExtRat) (lower : PackedFloat e s) : - IsLawfulLower r lower → (smtLibLower.lower r : PackedFloat e s).toExtRat' = PackedFloat.toExtRat' lower := by - intros h - have := embed_smtLibLower_eq_of_IsLawfulLower r lower h - simp at this - assumption -- theorem isNaN_lower_eq @[simp, grind .] -theorem IsLawfulLower_mkInfinity (hs : 0 < s) {sign : Bool} : IsLawfulLower (ExtRat.Infinity sign) (PackedFloat.getInfinity e s sign) := by +theorem IsLawfulLower_mkInfinity (e s : Nat) (hs : 0 < s) (sign : Bool): + IsLawfulLower (ExtRat.Infinity sign) (PackedFloat.getInfinity e s sign) := by constructor · simp -- grind @@ -391,8 +404,31 @@ theorem IsLawfulLower_mkInfinity (hs : 0 < s) {sign : Bool} : IsLawfulLower (Ext rw [PackedFloat.toExtRat'_getInfinity] grind +theorem IsLawfulLower_infinity_iff (e s : Nat) (hs : 0 < s) {x : PackedFloat e s} : + IsLawfulLower (ExtRat.Infinity sign) x ↔ x.isInfinite := by + constructor + · simp [IsLawfulLower] + intros hx hx' + specialize hx' (PackedFloat.getInfinity e s sign) + simp [hs] at hx' + specialize hx' (by grind only) + have : x.toExtRat' = ExtRat.Infinity sign := by grind only + grind only [= PackedFloat.toExtRat'_eq_NaN_of_isNaN, = PackedFloat.toExtRat'_eq_zero_of_isZero, + = PackedFloat.toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm, + = PackedFloat.isNormOrNonzeroSubnorm_of_not_NaN_not_Infinite_not_Zero] + . intros hx + simp [IsLawfulLower] + simp [hx, hs] + induction x using PackedFloat.classification <;> try grind + case zeroCase sign => + have : (PackedFloat.getZero e s sign).isZero = true := by + refine PackedFloat.isZero_of_isNZero ?_ + + + @[simp, grind .] -theorem IsLawfulUpper_mkInfinity (hs : 0 < s) {sign : Bool} : IsLawfulUpper (ExtRat.Infinity sign) (PackedFloat.getInfinity e s sign) := by +theorem IsLawfulUpper_mkInfinity (hs : 0 < s) (sign : Bool) : + IsLawfulUpper (ExtRat.Infinity sign) (PackedFloat.getInfinity e s sign) := by constructor · simp -- grind @@ -412,6 +448,22 @@ theorem IsLawfulLower_mkNaN : IsLawfulLower ExtRat.NaN (PackedFloat.getNaN e s) simp at hLtNaN simp [hLtNaN] +@[simp, grind .] +theorem IsLawfulLower_NaN_iff {x : PackedFloat e s} : IsLawfulLower ExtRat.NaN x ↔ x.isNaN := by + constructor + · intros hx + simp [IsLawfulLower] at hx + obtain ⟨hx, hx'⟩ := hx + have : x.isNaN := by grind only [= PackedFloat.toExtRat'_eq_Infinity_of_isInfinite, + = PackedFloat.toExtRat'_eq_zero_of_isZero, + = PackedFloat.toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm, + = PackedFloat.isNormOrNonzeroSubnorm_of_not_NaN_not_Infinite_not_Zero] + grind + · intros hnan + simp [IsLawfulLower] + simp [hnan] + + @[simp, grind .] theorem IsLawfulUpper_mkNaN : IsLawfulUpper ExtRat.NaN (PackedFloat.getNaN e s) := by constructor @@ -421,7 +473,7 @@ theorem IsLawfulUpper_mkNaN : IsLawfulUpper ExtRat.NaN (PackedFloat.getNaN e s) simp [hNaNLe] @[simp, grind .] -theorem IslawfulUpper_mkNumber (pf : PackedFloat e s): IsLawfulUpper pf.toExtRat' pf := by +theorem IslawfulUpper_mkNumber (pf : PackedFloat e s) : IsLawfulUpper pf.toExtRat' pf := by constructor · simp; grind · simp @@ -432,6 +484,74 @@ theorem IsLawfulLower_mkNumber (pf : PackedFloat e s) : IsLawfulLower pf.toExtRa · simp; grind · simp + +theorem lower_eq_of_IsLawfulLower_of_not_isNaN_not_isZero {e s} {r : ExtRat} + {x : PackedFloat e s} + (hx : IsLawfulLower r x) + (hnum : ¬ x.isNaN ∧ ¬ x.isZero): + smtLibLower.lower r = x := by + simp [smtLibLower] + -- generalize hu : Classical.epsilon (fun (x : PackedFloat e s) => IsLawfulLower r x) = u + -- have := Classical.epsilon_spec (p := fun (x : PackedFloat e s) => IsLawfulLower r x) + induction r + case NaN => + simp at hx + grind only + case Infinity => + simp at hx + grind only + + + + · apply IsLawfulLower.functional + + have : (embedPackedFloatExtRat e s).embed x = (embedPackedFloatExtRat e s).embed x' := by grind only [→ + lower_le_of_IsLawfulLower, + → le_lower_of_le_of_IsLawfulLower] + simp at this + have : x = x' := by + apply PackedFloat.eq_of_toExtRat'_eq + · grind + · -- by contradiction, if true, then x'.toExtRat' will be NaN, + -- which will fuck up the toExtRat'. + intros hx + simp [hx] at this + grind only [= PackedFloat.toExtRat'_eq_Infinity_of_isInfinite, + = PackedFloat.toExtRat'_eq_Number_of_isNormOrNonzeroSubnorm, + = PackedFloat.isNormOrNonzeroSubnorm_of_not_NaN_not_Infinite_not_Zero] + · grind + · -- by contradiction. if true, then x'.toExtRat' will be 0, + -- whch will fuck up the toExtRat'. + sorry + sorry + subst this + + + + +@[grind .] +theorem embed_smtLibLower_eq_of_IsLawfulLower [Inhabited X] [ExtendedNumber R] [instEmbed : RoundableEmbed X R] [Std.IsPartialOrder R] (r : R) (lower : X) : + IsLawfulLower r lower → instEmbed.embed (smtLibLower.lower r) = instEmbed.embed lower := by + intro hl + simp [smtLibLower] + split + case isTrue h => + obtain ⟨x, hlx⟩ := h + have : instEmbed.embed x = instEmbed.embed lower := by + apply IsLawfulLower.functional r x lower hlx hl + grind + case isFalse h => + simp at h + grind + +@[simp, grind .] +theorem toExtRat'_smtLibLower_eq_toExtRat'_of_IsLawfulLower (r : ExtRat) (lower : PackedFloat e s) : + IsLawfulLower r lower → (smtLibLower.lower r : PackedFloat e s).toExtRat' = PackedFloat.toExtRat' lower := by + intros h + have := embed_smtLibLower_eq_of_IsLawfulLower r lower h + simp at this + assumption + @[simp] theorem isNaN_lower_NaN : (smtLibLower.lower ExtRat.NaN : PackedFloat e s).isNaN = true := sorry @@ -439,9 +559,22 @@ theorem isNaN_lower_NaN : (smtLibLower.lower ExtRat.NaN : PackedFloat e s).isNaN /-- Lower returns the input at all points except zero. -/ theorem lower_eq_of_ne_zero {r : ExtRat} {lower : PackedFloat e s} - (h : r = lower.toExtRat) (hr : r ≠ 0) : - (smtLibLower.lower r : PackedFloat e s) = lower := by sorry - + (hs : 0 < s) + (hpfNaN : ¬ lower.isNaN) + (h : r = lower.toExtRat) (hr : r ≠ 0) : + (smtLibLower.lower r : PackedFloat e s) = lower := by + induction lower using PackedFloat.classification + case nanCase pf hpf => + grind only -- contradiction + case infCase sign => + simp [h] + simp [PackedFloat.toExtRat'_getInfinity hs] + have := IsLawfulLower_mkInfinity e s hs sign + sorry + case zeroCase pf => + sorry + case numCase pf hpf => + sorry theorem lower_eq_plus_zero : (smtLibLower.lower (0 : ExtRat) : PackedFloat e s) = PackedFloat.getZero e s false := sorry @@ -563,6 +696,30 @@ theorem smtLibRoundMethod.lowerHalf_eq {R : Type} (e s : Nat) [ExtendedNumber R] : (smtLibRoundMethod e s v ves).lowerHalf = (fun r => ExtendedNumber.smtLibEq (v.embed (v.lower r)) (ves.embed (ves.lower r))) := rfl +/-- +two packed floats are at least this far away, or are equal. +-/ +theorem PackedFloat.toExtRat_sub_toExtRat_ge_of_le (a b : PackedFloat e s) (hab : a ≤ b) + (ha : ¬ a.isNaN ∧ ¬ a.isInfinite) + (hb : ¬ b.isNaN ∧ ¬ b.isInfinite): + ((2 : Rat) ^ (- (s : Int)) ≤ b.toNumberRat - a.toNumberRat) ∨ (a.toNumberRat = b.toNumberRat) := by + by_cases hab' : a.toNumberRat = b.toNumberRat + · simp [hab'] + · -- one of them must be nonzero. + -- if both are zero, then toNumberRat will be equal. + sorry + +/-- +The lowerHalf is true if the rational is closer to the lower approximant than the upper approximant. +-/ +theorem smtLibRoundMethod.lowerHalf_eq_iff (e s : Nat) + (v : RoundableAdjunction (PackedFloat e s) ExtRat) + (ves : RoundableAdjunction (PackedFloat e (s + 1)) ExtRat) + (r : ExtRat) : + (smtLibRoundMethod e s v ves).lowerHalf r ↔ ((r - (v.lower r).toExtRat) ≤ ((v.upper r).toExtRat - r)) := by + sorry + + theorem smtLibRoundMethod.tieBreak_eq {R : Type} (e s : Nat) (v : RoundableAdjunction (PackedFloat e s) R) (ves : RoundableAdjunction (PackedFloat e (s + 1)) R) @@ -571,6 +728,15 @@ theorem smtLibRoundMethod.tieBreak_eq {R : Type} (e s : Nat) (v.embed (v.lower r) < ves.embed (ves.lower r)) = (ves.embed (ves.upper r) < (v.embed (v.upper r)))) := rfl +/-- +The tieBreak is true iff the rational is closer to the upper approximant than the lower approximant. +-/ +theorem smtLibRoundMethod.tieBreak_eq_iff (e s : Nat) + (v : RoundableAdjunction (PackedFloat e s) ExtRat) + (ves : RoundableAdjunction (PackedFloat e (s + 1)) ExtRat) + (r : ExtRat) : + (smtLibRoundMethod e s v ves).tieBreak r ↔ ((r - (v.lower r).toExtRat) = ((v.upper r).toExtRat - r)) := by sorry + instance [hExtended : ExtendedNumber R] [DecidableRel hExtended.smtLibEq] {v : RoundableAdjunction (PackedFloat e s) R} diff --git a/Fp/Theorems/Packing.lean b/Fp/Theorems/Packing.lean index c350167..0ba8f7e 100644 --- a/Fp/Theorems/Packing.lean +++ b/Fp/Theorems/Packing.lean @@ -188,7 +188,8 @@ theorem sigWidth_add_bias_le_exponentWidth_sub_one : s + bias e ≤ 2 ^ (exponen simp only [bias, exponentWidth] grind only [!Nat.two_pow_pos, !Nat.log2_eq_iff, #569066451790c837, #ccfcc644d1be4e5b] --- theorem expWidth_lt_exponentWidth : e < exponentWidth e s := by +-- theorem expWidth_lt_exponentWidth : +-- e < exponentWidth e s := by -- unfold exponentWidth -- induction e with -- | zero => simp From 43804dd3306db60d4dfe008e94e8f12d03b82cc2 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 23 Feb 2026 11:04:58 +0000 Subject: [PATCH 42/44] chore: write about packed float successor, which tells us that the ordering is discrete --- Fp/SmtLibSemantics.lean | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Fp/SmtLibSemantics.lean b/Fp/SmtLibSemantics.lean index 819324d..4874bc0 100644 --- a/Fp/SmtLibSemantics.lean +++ b/Fp/SmtLibSemantics.lean @@ -312,7 +312,6 @@ def IsLawfulUpper [ExtendedNumber R] [RE : RoundableEmbed X R] (r : R) (upper : r ≤ RE.embed upper ∧ (∀ (upper' : X), r ≤ RE.embed upper' → RE.embed upper ≤ RE.embed upper') - @[grind →] theorem IsLawfulUpper.embed_eq_of_IsLawfulUpper [ExtendedNumber R] [RE : RoundableEmbed X R] [Std.IsPartialOrder R] (r : R) (upper1 upper2 : X) : IsLawfulUpper r upper1 → IsLawfulUpper r upper2 → RE.embed upper1 = RE.embed upper2 := by @@ -592,6 +591,7 @@ theorem upper_eq_of_ne_zero {r : ExtRat} {upper : PackedFloat e s} (h : r = uppe theorem le_upper (r : ExtRat) : r ≤ ((smtLibUpper.upper r) : PackedFloat e s).toExtRat := sorry + instance (he : 0 < e) (hs : 0 < s) : LawfulRoundableAdjunction (smtLibV (embedPackedFloatExtRat e s)) where adjunctionLower := by intros r p @@ -753,6 +753,19 @@ instance [hExtended : ExtendedNumber R] rw [smtLibRoundMethod] infer_instance +/-- +A successor is the least *strict* upper bound. So, successor gives the next +packed float. +Note that this sucks, because this will mean that +the successor of -0 is not necessarily +0, +which is probably not what we want. +Instead, we should build this as PackedFloat theory, +and show that the ordering on PackedFloat has a 'successor'. +-/ +def IsLawfulSuccessor [ExtendedNumber R] [RE : RoundableEmbed X R] (r : R) (upper : X) : Prop := + r < RE.embed upper ∧ (∀ (upper' : X), r < RE.embed upper' → RE.embed upper ≤ RE.embed upper') + + -- end SmtLibRoundMethod namespace SmtLibFunctions From a146fd29a9cdeaab5e044e59ad27e57ebe896458 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 23 Feb 2026 11:15:27 +0000 Subject: [PATCH 43/44] chore: write down successor defn --- Fp/Basic.lean | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Fp/Basic.lean b/Fp/Basic.lean index 9dd10dc..db1c4c7 100644 --- a/Fp/Basic.lean +++ b/Fp/Basic.lean @@ -644,7 +644,7 @@ theorem isZeroOrSubnorm_of_isNonzeroSubnorm {pf : PackedFloat e s} : grind [isNonzeroSubnorm, isZeroOrSubnorm] --- TODO: delete 'isNZero', 'isPZero'. +-- TODO: delete 'isNZero', @[grind →] theorem isZero_of_isNZero {pf : PackedFloat e s} : pf.isNZero → pf.isZero := by @@ -1465,7 +1465,6 @@ instance : Std.IsPartialOrder ExtRat where le_trans := by grind [le_trans] le_antisymm := by grind [le_antisymm] - def isNaN (r : ExtRat) : Bool := r = .NaN @@ -1568,6 +1567,16 @@ instance : LE (PackedFloat exWidth sigWidth) where theorem le_def (x y : PackedFloat e s) : x.le y = (x ≤ y) := rfl +def lt (x y : PackedFloat e s) : Prop := + x ≤ y ∧ x ≠ y + +instance : LT (PackedFloat e s) where + lt x y := x.lt y + +@[simp] +theorem lt_def (x y : PackedFloat e s) : + x.lt y = (x < y) := rfl + @[simp, grind =] theorem minus_zero_le_plus_zero {e s} : (PackedFloat.getZero e s true ≤ PackedFloat.getZero e s false) = @@ -1583,6 +1592,18 @@ instance {x y : PackedFloat e s} : Decidable (x ≤ y) := by simp only [← PackedFloat.le_def] infer_instance +/-- +The successor is the least *strict* upper bound. +This is used to show that the ordering on 'PackedFloat' is a discrete ordering, +with adjacent elements having a gap of at least '2^-s' +-/ +def IsSuccessor (p q : PackedFloat e s) : Prop := + p < q ∧ (∀ (r : PackedFloat e s), p < r → q ≤ r) + +instance {x y : PackedFloat e s} : Decidable (x < y) := by + simp only [← PackedFloat.lt_def, PackedFloat.lt] + infer_instance + def toNumberRatSig {e s} (pf : PackedFloat e s) : Rat := if pf.isNorm then 1 + pf.sig.toNat / 2 ^ s From f3442bb14b469bf3fe04b597c0a6ea4bef949671 Mon Sep 17 00:00:00 2001 From: Siddharth Bhat Date: Mon, 23 Feb 2026 22:17:42 +0000 Subject: [PATCH 44/44] chore: start adding FP proof --- Fp/SmtLibSemantics.lean | 1 - Fp/UnpackedRound.lean | 170 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 1 deletion(-) diff --git a/Fp/SmtLibSemantics.lean b/Fp/SmtLibSemantics.lean index 4874bc0..39f7ca7 100644 --- a/Fp/SmtLibSemantics.lean +++ b/Fp/SmtLibSemantics.lean @@ -501,7 +501,6 @@ theorem lower_eq_of_IsLawfulLower_of_not_isNaN_not_isZero {e s} {r : ExtRat} grind only - · apply IsLawfulLower.functional have : (embedPackedFloatExtRat e s).embed x = (embedPackedFloatExtRat e s).embed x' := by grind only [→ diff --git a/Fp/UnpackedRound.lean b/Fp/UnpackedRound.lean index 57fdd74..bad1c01 100644 --- a/Fp/UnpackedRound.lean +++ b/Fp/UnpackedRound.lean @@ -4,6 +4,7 @@ import Fp.MulInv import Fp.Grind import Fp.ForLean.Rat import Fp.Rounding +import Fp.Theorems.Packing @[bv_normalize] def BitVec.leadingOne (w : Nat) : BitVec w := @@ -759,6 +760,175 @@ def UnpackedFloat.round {expWidth sigWidth : Nat} {targetExponentWidth targetSig (isZero := inUf.isZero) result +/-- +The core rounding function, +that rounds an `UnpackedFloat` to the target exponent and significand widths. +-/ +@[bv_normalize] +def UnpackedFloat.proofRound {expWidth sigWidth : Nat} + {targetExponentWidth targetSignificandWidth : Nat} + (inUf : UnpackedFloat expWidth sigWidth) + (hTargetExponentWidth : targetExponentWidth ≤ expWidth) + (hSigWidth : targetSignificandWidth + 2 ≤ sigWidth) + (mode : RoundingMode) : + EUnpackedFloat (exponentWidth targetExponentWidth targetSignificandWidth) (targetSignificandWidth + 1) := + -- round a normalized, normal float. + let exp : BitVec expWidth := inUf.ex + + let targetMinNormalExp : BitVec expWidth := + BitVec.ofInt expWidth (minNormalExp targetExponentWidth) + + let hTargetMinNormalExp : targetMinNormalExp.toInt = minNormalExp targetExponentWidth := by + simp [targetMinNormalExp] + -- use 'fits' theorems from Fp/Theorems/Packing. + sorry + let earlyOverflow : Bool := exp.sgt (BitVec.ofInt expWidth (maxNormalExp targetExponentWidth)) + let hEarlyOverflow : earlyOverflow = + decide (inUf.ex.toInt ≥ maxNormalExp targetExponentWidth) := sorry + + -- early underflow: + let earlyUnderflow : Bool := exp.slt (BitVec.ofInt expWidth (minSubnormalExp targetExponentWidth targetSignificandWidth - 1)) + let hEarlyUnderflow : earlyUnderflow = + decide (exp < minSubnormalExp targetExponentWidth targetSignificandWidth - 1) := sorry + + -- force exponent to be at least min normal exponent. + let expGeMin := + if exp.slt targetMinNormalExp then + targetMinNormalExp + else + exp + let hExpGeMin : expGeMin.toInt = max targetMinNormalExp.toInt exp.toInt := sorry + + -- how much to shift 'sig' by. + let shiftAmtPositive := expGeMin - exp + + let hShiftAmtPositive1 : shiftAmtPositive.toInt = expGeMin.toInt - exp.toInt := by sorry + let hShiftAmtPositive2 : shiftAmtPositive.toNat = expGeMin.toInt - exp.toInt := by sorry + -- have : shiftAmtPositive.toInt ≥ 0 := AxRoundNormal + -- have : shiftAmtPositive.toNat = shiftAmtPositive.toInt := AxRoundNormal + -- have : exp.toInt + shiftAmtPositive.toInt = expGeMin.toInt := AxRoundNormal + + let sigWithHidden : BitVec sigWidth := inUf.sig + + -- in inf precision, we want to take: + -- ↓ guard + -- 1 . a b c d e f g h ... * 2^exp + -- ============↑ (6 bits of precision) + -- suppose we exceed by 3 bits, so shiftAmt = 3. + + -- and convert to target precision: + -- ↓ guard + -- 0 . 0 0 0 1 a b c d e f g h * 2^exp + 2^shiftAmt | to make 'exp + shiftAmt >= minNormalExp'. + -- ==============↑ (6 bits of precision) + -- (sigWidth - 1) - (targetSignificandWidth - 1) - + -- let targetSigWithHidden : BitVec (targetSignificandWidth + 1) := + -- sigWithHidden.extractMsb' 0 (targetSignificandWidth + 1) + -- to grab the bit *after* w bits, it's the bit x[w]. + -- we want the bit *after* targetSignificandWidth + 1, i.e., bit at index targetSignificandWidth + 1. + let guardBitIndexFromLsb : BitVec sigWidth := + BitVec.ofNat sigWidth ((sigWidth - 1) - (targetSignificandWidth + 1)) + -- | See that when we call 'shiftAmtPositive.zeroExtend sigWidth', there is + -- a potential that shiftAmtPositive is wider than sigWidth. + -- However, when we compute early underflow, + let guardBitIndexFromLsbAdjusted : BitVec sigWidth := + guardBitIndexFromLsb + shiftAmtPositive.zeroExtend sigWidth + -- TODO: figure out what to say about 'guardBitIndexFromLsb'? + let guardBitMask : BitVec sigWidth := BitVec.oneHotBV guardBitIndexFromLsbAdjusted + let guardBit : Bool := (sigWithHidden &&& guardBitMask) ≠ 0#sigWidth + -- TODO: guardBit ↔ ¬ isLower? + let stickyBitsMask : BitVec sigWidth := (BitVec.orderEncode guardBitIndexFromLsbAdjusted) + let stickyBits : BitVec sigWidth := (sigWithHidden &&& stickyBitsMask) + let stickyBit : Bool := stickyBits ≠ 0#sigWidth + -- TODO: guardbit => (stikyBit ↔ tieBreak)? + let sigwithHiddenCleared : BitVec sigWidth := + sigWithHidden &&& (~~~(guardBitMask ||| stickyBitsMask)) + + let lsbMask : BitVec sigWidth := + BitVec.oneHotBV (guardBitIndexFromLsbAdjusted + 1#sigWidth) + + let isEven : Bool := sigWithHidden &&& lsbMask = 0#sigWidth + -- TODO: isEven ↔ isEven? + let shouldRoundUp := roundingDecision + (mode := mode) + (sign := inUf.sign) + (significandEven := isEven) + (guardBit := guardBit) + (stickyBit := stickyBit) + (_exact := false) + -- TODO: shouldRoundUp ↔ SMT-LIB shouldRoundUp? + + let sigDidOverflow_RoundedTargetSigWithHidden : BitVec (sigWidth + 1) := + if shouldRoundUp then + if sigwithHiddenCleared = 0#sigWidth && lsbMask = 0#sigWidth then + BitVec.oneHotBV (w := sigWidth + 1) (sigWidth) + else + sigwithHiddenCleared.zeroExtend (sigWidth + 1) + lsbMask.zeroExtend (sigWidth + 1) + else + sigwithHiddenCleared.zeroExtend (sigWidth + 1) + + let sigDidOverflow : Bool := + sigDidOverflow_RoundedTargetSigWithHidden.msb + + let roundedTargetSigWithHidden : BitVec sigWidth := + sigDidOverflow_RoundedTargetSigWithHidden.setWidth sigWidth + + + let roundedTargetSigWithHiddenOverflowAdjusted : BitVec sigWidth := + if sigDidOverflow then + BitVec.leadingOne sigWidth + else + roundedTargetSigWithHidden + -- lower = upper + 1. + let roundedExpExtended : BitVec (expWidth + 1) := + if sigDidOverflow then + exp.signExtend (expWidth + 1) + 1#(expWidth + 1) + else + exp.signExtend (expWidth + 1) + + -- TODO: upper = lower + 1 + -- I find this width stuff confusing, which width should we use? + -- have : expWidth ≥ exponentWidth targetExponentWidth targetSignificandWidth := by grind + let maxNormalExpBV : BitVec (expWidth + 1) := + BitVec.ofInt (expWidth + 1) (maxNormalExp targetExponentWidth) + let lateOverflow : Bool := + maxNormalExpBV.slt roundedExpExtended + -- let subnormalExpBV : BitVec (expWidth) := BitVec.ofInt (expWidth) (subnormalExp targetExponentWidth) + -- let minSubnormalExpMinusOneBV : BitVec (expWidth + 1) := + -- BitVec.ofInt (expWidth + 1) (minSubnormalExp targetExponentWidth targetSignificandWidth - 1) + let minSubnormalExpBV : BitVec (expWidth + 1) := + BitVec.ofInt (expWidth + 1) (minSubnormalExp targetExponentWidth targetSignificandWidth) + let lateUnderflow : Bool := + roundedExpExtended.slt minSubnormalExpBV + -- (roundedExpExtended = minSubnormalExpMinusOneBV) && !shouldRoundUp + -- let out := out ++ s!"\nlateUnderflow: {lateUnderflow} = (roundedExpExtended({roundedExpExtended.toBitsStr}=int:{roundedExpExtended.toInt}) = minSubnormalExpMinusOneBV({minSubnormalExpMinusOneBV.toBitsStr}=int:{minSubnormalExpMinusOneBV.toInt}) - 1) && !shouldRoundUp({shouldRoundUp})" + -- let out := out ++ s!"\nlate underflow: {lateUnderflow} = roundedExp({roundedExp.toBitsStr}=int:{roundedExp.toInt}) < subnormalExpBV({subnormalExpBV.toBitsStr}=int:{subnormalExpBV.toInt})" + let underflow : Bool := lateUnderflow || earlyUnderflow + let overflow : Bool := lateOverflow || earlyOverflow + + let roundedClampedExpExtended : BitVec (expWidth + 1) := + if lateOverflow then + maxNormalExpBV + else if lateUnderflow then + minSubnormalExpBV + else + roundedExpExtended + let finalExp := roundedClampedExpExtended.truncate (exponentWidth targetExponentWidth targetSignificandWidth) + let finalSigTruncated := roundedTargetSigWithHiddenOverflowAdjusted.extractMsb' 0 (targetSignificandWidth + 1) + let finalNumber : UnpackedFloat (exponentWidth targetExponentWidth targetSignificandWidth) (targetSignificandWidth + 1) := + { sign := inUf.sign, + ex := finalExp, + sig := finalSigTruncated + } + -- | TODO: I don't fully understand the special cases + let result := rounderSpecialCases + (roundingMode := mode) + (roundedResult := finalNumber) + (overflow := overflow) + (underflow := underflow) + (isZero := inUf.isZero) + result + + /-- Round an EUnpacked float, by ignoring NaN and infinity. -/ def EUnpackedFloat.round {expWidth sigWidth : Nat} {targetExponentWidth targetSignificandWidth : Nat} (inEuf : EUnpackedFloat expWidth sigWidth)