Repository: leanprover/lean4
Pull request #7699: feat: `BitVec.toInt_srem`
Author: login: Rob23oba
Labels: name: toolchain-available
description: A toolchain is available for this PR, at leanprover/lean4-pr-releases:pr-release-NNNN
name: changelog-library
description: Library
Created: 2025-03-27T16:30:55Z | Merged: 2025-03-29T07:35:55Z

This PR adds the `BitVec.toInt_srem` lemma, relating `BitVec.srem` with `Int.tmod`.



Diff:
diff --git a/src/Init/Data/BitVec/Bitblast.lean b/src/Init/Data/BitVec/Bitblast.lean
index b06e5fa17192..5018d5a5699c 100644
--- a/src/Init/Data/BitVec/Bitblast.lean
+++ b/src/Init/Data/BitVec/Bitblast.lean
@@ -1648,7 +1648,50 @@ theorem toInt_sdiv (a b : BitVec w) : (a.sdiv b).toInt = (a.toInt.tdiv b.toInt).
     conv => lhs; rw [(by omega: w = (w - 1) + 1)]
     simp [Nat.pow_succ, Int.natCast_pow, Int.mul_comm]
   · rw [← toInt_bmod_cancel]
-    rw [BitVec.toInt_sdiv_of_ne_or_ne _ _ (by simpa only [Classical.not_and_iff_not_or_not] using h)]
+    rw [BitVec.toInt_sdiv_of_ne_or_ne _ _ (by simpa only [Decidable.not_and_iff_not_or_not] using h)]
+
+theorem msb_umod_eq_false_of_left {x : BitVec w} (hx : x.msb = false) (y : BitVec w) : (x % y).msb = false := by
+  rw [msb_eq_false_iff_two_mul_lt] at hx ⊢
+  rw [toNat_umod]
+  refine Nat.lt_of_le_of_lt ?_ hx
+  rw [Nat.mul_le_mul_left_iff (by decide)]
+  exact Nat.mod_le _ _
+
+theorem msb_umod_of_le_of_ne_zero_of_le {x y : BitVec w}
+    (hx : x ≤ intMin w) (hy : y ≠ 0#w) (hy' : y ≤ intMin w) : (x % y).msb = false := by
+  simp only [msb_umod, Bool.and_eq_false_imp, Bool.or_eq_false_iff, decide_eq_false_iff_not,
+    BitVec.not_lt, beq_eq_false_iff_ne, ne_eq, hy, not_false_eq_true, _root_.and_true]
+  intro h
+  rw [← intMin_le_iff_msb_eq_true (length_pos_of_ne hy)] at h
+  rwa [BitVec.le_antisymm hx h]
+
+@[simp]
+theorem toInt_srem (x y : BitVec w) : (x.srem y).toInt = x.toInt.tmod y.toInt := by
+  rw [srem_eq]
+  by_cases hyz : y = 0#w
+  · simp only [hyz, ofNat_eq_ofNat, msb_zero, umod_zero, neg_zero, neg_neg, toInt_zero, Int.tmod_zero]
+    cases x.msb <;> rfl
+  cases h : x.msb
+  · cases h' : y.msb
+    · dsimp only
+      rw [toInt_eq_toNat_of_msb (msb_umod_eq_false_of_left h y), toNat_umod]
+      rw [toInt_eq_toNat_of_msb h, toInt_eq_toNat_of_msb h', ← Int.ofNat_tmod]
+    · dsimp only
+      rw [toInt_eq_toNat_of_msb (msb_umod_eq_false_of_left h _), toNat_umod]
+      rw [toInt_eq_toNat_of_msb h, toInt_eq_neg_toNat_neg_of_msb_true h']
+      rw [Int.tmod_neg, ← Int.ofNat_tmod]
+  · cases h' : y.msb
+    · dsimp only
+      rw [toInt_eq_neg_toNat_neg_of_msb_true h, toInt_eq_toNat_of_msb h', Int.neg_tmod]
+      rw [← Int.ofNat_tmod, ← toNat_umod, toInt_neg_eq_of_msb ?msb, toInt_eq_toNat_of_msb ?msb]
+      rw [BitVec.msb_umod_of_le_of_ne_zero_of_le (neg_le_intMin_of_msb_eq_true h) hyz]
+      exact le_intMin_of_msb_eq_false h'
+    · dsimp only
+      rw [toInt_eq_neg_toNat_neg_of_msb_true h, toInt_eq_neg_toNat_neg_of_msb_true h', Int.neg_tmod, Int.tmod_neg]
+      rw [← Int.ofNat_tmod, ← toNat_umod, toInt_neg_eq_of_msb ?msb', toInt_eq_toNat_of_msb ?msb']
+      rw [BitVec.msb_umod_of_le_of_ne_zero_of_le (neg_le_intMin_of_msb_eq_true h)
+        ((not_congr neg_eq_zero_iff).mpr hyz)]
+      exact neg_le_intMin_of_msb_eq_true h'
 
 /-! ### Lemmas that use Bitblasting circuits -/
 
diff --git a/src/Init/Data/BitVec/Lemmas.lean b/src/Init/Data/BitVec/Lemmas.lean
index b2dc42f326a4..d80fc889377c 100644
--- a/src/Init/Data/BitVec/Lemmas.lean
+++ b/src/Init/Data/BitVec/Lemmas.lean
@@ -263,6 +263,11 @@ theorem getMsbD_of_zero_length (h : w = 0) (x : BitVec w) : x.getMsbD i = false
   subst h; simp [getMsbD_zero_length]
 theorem msb_of_zero_length (h : w = 0) (x : BitVec w) : x.msb = false := by
   subst h; simp [msb_zero_length]
+theorem eq_of_zero_length (h : w = 0) {x y : BitVec w} : x = y := by
+  subst h; rw [eq_nil x, eq_nil y]
+
+theorem length_pos_of_ne {x y : BitVec w} (h : x ≠ y) : 0 < w :=
+  Nat.zero_lt_of_ne_zero (mt (fun h => eq_of_zero_length h) h)
 
 theorem ofFin_ofNat (n : Nat) :
     ofFin (no_index (OfNat.ofNat n : Fin (2^w))) = OfNat.ofNat n := by
@@ -3699,6 +3704,11 @@ theorem not_lt_iff_le {x y : BitVec w} : (¬ x < y) ↔ y ≤ x := by
   constructor <;>
     (intro h; simp only [lt_def, Nat.not_lt, le_def] at h ⊢; omega)
 
+protected theorem le_of_lt {x y : BitVec w} (h : x < y) : x ≤ y := Nat.le_of_lt h
+
+protected theorem le_of_eq {x y : BitVec w} (h : x = y) : x ≤ y :=
+  Nat.le_of_eq (toNat_eq.mp h)
+
 @[simp]
 theorem not_lt_zero {x : BitVec w} : ¬x < 0#w := of_decide_eq_false rfl
 
@@ -3735,9 +3745,18 @@ theorem allOnes_le_iff {x : BitVec w} : allOnes w ≤ x ↔ x = allOnes w := by
 
 @[simp]
 theorem lt_allOnes_iff {x : BitVec w} : x < allOnes w ↔ x ≠ allOnes w := by
- have := not_congr (@allOnes_le_iff w x)
- rw [BitVec.not_le] at this
- exact this
+  have := not_congr (@allOnes_le_iff w x)
+  rw [BitVec.not_le] at this
+  exact this
+
+theorem le_of_zero_length (h : w = 0) {x y : BitVec w} : x ≤ y := by
+  exact BitVec.le_of_eq (eq_of_zero_length h)
+
+theorem pos_of_msb {x : BitVec w} (hx : x.msb = true) : 0#w < x := by
+  apply Decidable.by_contra
+  intro h
+  rw [BitVec.not_lt, le_zero_iff] at h
+  simp [h] at hx
 
 /-! ### udiv -/
 
@@ -4706,6 +4725,9 @@ The RHS is zero in case `w = 0` which is modeled by wrapping the expression in `
 theorem toNat_intMin : (intMin w).toNat = 2 ^ (w - 1) % 2 ^ w := by
   simp [intMin]
 
+theorem toNat_intMin_of_pos (hw : 0 < w) : (intMin w).toNat = 2 ^ (w - 1) := by
+  rw [toNat_intMin, Nat.mod_eq_of_lt (Nat.pow_lt_pow_of_lt (by decide) (Nat.sub_one_lt_of_lt hw))]
+
 @[simp]
 theorem intMin_eq_zero_iff {w : Nat} : intMin w = 0#w ↔ w = 0 := by
   by_cases h : w = 0
@@ -4802,6 +4824,37 @@ theorem ne_intMin_of_msb_eq_false (h : 0 < w) {n : BitVec w} (hn : n.msb = false
   simp only [msb_intMin, decide_eq_false_iff_not, Nat.not_lt, Nat.le_zero_eq] at hn
   omega
 
+theorem toInt_neg_eq_of_msb {x : BitVec w} (h : x.msb = false) : (-x).toInt = -x.toInt := by
+  match w with
+  | 0 => rw [of_length_zero (x := x), neg_zero, toInt_zero, Int.neg_zero]
+  | w' + 1 => exact toInt_neg_of_ne_intMin (ne_intMin_of_msb_eq_false (Nat.zero_lt_succ _) h)
+
+theorem lt_intMin_iff_msb_eq_false {x : BitVec w} (hw : 0 < w) :
+    x < intMin w ↔ x.msb = false := by
+  simp only [msb_eq_false_iff_two_mul_lt, toNat_intMin_of_pos hw, lt_def]
+  rw [← Nat.mul_lt_mul_left (by decide : 0 < 2), ← Nat.pow_add_one', Nat.sub_one_add_one_eq_of_pos hw]
+
+theorem intMin_le_iff_msb_eq_true {x : BitVec w} (hw : 0 < w) :
+    intMin w ≤ x ↔ x.msb = true := by
+  rw [← Decidable.not_iff_not, BitVec.not_le, Bool.not_eq_true]
+  exact lt_intMin_iff_msb_eq_false hw
+
+theorem le_intMin_of_msb_eq_false {x : BitVec w} (hx : x.msb = false) : x ≤ intMin w := by
+  match w with
+  | 0 => exact le_of_zero_length rfl
+  | w' + 1 =>
+    apply BitVec.le_of_lt
+    exact (lt_intMin_iff_msb_eq_false (Nat.zero_lt_succ _)).mpr hx
+
+theorem neg_le_intMin_of_msb_eq_true {x : BitVec w} (hx : x.msb = true) : -x ≤ intMin w := by
+  match w with
+  | 0 => exact le_of_zero_length rfl
+  | w' + 1 =>
+    rw [le_def, toNat_neg_of_pos (pos_of_msb hx), toNat_intMin_of_pos (Nat.zero_lt_succ _)]
+    simp only [Nat.succ_eq_add_one, Nat.add_one_sub_one, Nat.pow_add_one]
+    rw [msb_eq_true_iff_two_mul_ge, Nat.pow_add_one] at hx
+    omega
+
 /-! ### intMax -/
 
 /-- The bitvector of width `w` that has the largest value when interpreted as an integer. -/


Reviews:
user: login: TwoFX
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T07:16:02Z
user: login: Rob23oba
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T14:53:24Z
user: login: Rob23oba
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T14:53:51Z
user: login: tobiasgrosser
body: LGTM. Thank you!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T16:20:43Z
user: login: TwoFX
body: Thanks!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-29T07:14:32Z

Review comments:
diff_hunk: @@ -1648,7 +1648,95 @@ theorem toInt_sdiv (a b : BitVec w) : (a.sdiv b).toInt = (a.toInt.tdiv b.toInt).
     conv => lhs; rw [(by omega: w = (w - 1) + 1)]
     simp [Nat.pow_succ, Int.natCast_pow, Int.mul_comm]
   · rw [← toInt_bmod_cancel]
-    rw [BitVec.toInt_sdiv_of_ne_or_ne _ _ (by simpa only [Classical.not_and_iff_not_or_not] using h)]
+    rw [BitVec.toInt_sdiv_of_ne_or_ne _ _ (by simpa only [Decidable.not_and_iff_not_or_not] using h)]
+
+theorem BitVec.toNat_intMin_of_pos (hw : 0 < w) : (intMin w).toNat = 2 ^ (w - 1) := by
path: src/Init/Data/BitVec/Bitblast.lean
user: login: TwoFX
body: All of these are currently named `BitVec.BitVec.stuff`.
created_at: 2025-03-28T07:12:41Z
author_association: MEMBER
diff_hunk: @@ -1648,7 +1648,95 @@ theorem toInt_sdiv (a b : BitVec w) : (a.sdiv b).toInt = (a.toInt.tdiv b.toInt).
     conv => lhs; rw [(by omega: w = (w - 1) + 1)]
     simp [Nat.pow_succ, Int.natCast_pow, Int.mul_comm]
   · rw [← toInt_bmod_cancel]
-    rw [BitVec.toInt_sdiv_of_ne_or_ne _ _ (by simpa only [Classical.not_and_iff_not_or_not] using h)]
+    rw [BitVec.toInt_sdiv_of_ne_or_ne _ _ (by simpa only [Decidable.not_and_iff_not_or_not] using h)]
+
+theorem BitVec.toNat_intMin_of_pos (hw : 0 < w) : (intMin w).toNat = 2 ^ (w - 1) := by
path: src/Init/Data/BitVec/Bitblast.lean
user: login: TwoFX
body: Most of these lemmas probably have a better home further up in this file or somewhere in `Lemmas.lean`.
created_at: 2025-03-28T07:13:10Z
author_association: MEMBER
diff_hunk: @@ -1648,7 +1648,95 @@ theorem toInt_sdiv (a b : BitVec w) : (a.sdiv b).toInt = (a.toInt.tdiv b.toInt).
     conv => lhs; rw [(by omega: w = (w - 1) + 1)]
     simp [Nat.pow_succ, Int.natCast_pow, Int.mul_comm]
   · rw [← toInt_bmod_cancel]
-    rw [BitVec.toInt_sdiv_of_ne_or_ne _ _ (by simpa only [Classical.not_and_iff_not_or_not] using h)]
+    rw [BitVec.toInt_sdiv_of_ne_or_ne _ _ (by simpa only [Decidable.not_and_iff_not_or_not] using h)]
+
+theorem BitVec.toNat_intMin_of_pos (hw : 0 < w) : (intMin w).toNat = 2 ^ (w - 1) := by
+  rw [toNat_intMin, Nat.mod_eq_of_lt (Nat.pow_lt_pow_of_lt (by decide) (Nat.sub_one_lt_of_lt hw))]
+
+protected theorem BitVec.le_of_lt {x y : BitVec w} (h : x < y) : x ≤ y := Nat.le_of_lt h
+
+theorem BitVec.lt_intMin_iff {x : BitVec w} (hw : 0 < w) : x < intMin w ↔ x.msb = false := by
+  simp only [msb_eq_false_iff_two_mul_lt, BitVec.toNat_intMin_of_pos hw, lt_def]
+  rw [← Nat.mul_lt_mul_left (by decide : 0 < 2), ← Nat.pow_add_one', Nat.sub_one_add_one_eq_of_pos hw]
+
+theorem BitVec.intMin_le_iff {x : BitVec w} (hw : 0 < w) : intMin w ≤ x ↔ x.msb = true := by
+  rw [← Decidable.not_iff_not, BitVec.not_le, Bool.not_eq_true]
+  exact BitVec.lt_intMin_iff hw
+
+theorem BitVec.le_intMin_of_msb {x : BitVec w} (hx : x.msb = false) : x ≤ intMin w := by
+  match w with
+  | 0 => decide +revert
+  | w' + 1 =>
+    apply BitVec.le_of_lt
+    exact (BitVec.lt_intMin_iff (Nat.zero_lt_succ _)).mpr hx
+
+theorem BitVec.pos_of_msb {x : BitVec w} (hx : x.msb = true) : 0#w < x := by
+  apply Decidable.by_contra
+  intro h
+  simp only [BitVec.not_lt, le_zero_iff] at h
+  cases h
+  simp at hx
+
+theorem BitVec.neg_le_intMin_of_msb {x : BitVec w} (hx : x.msb = true) : -x ≤ intMin w := by
+  match w with
+  | 0 => decide +revert
+  | w' + 1 =>
+    rw [BitVec.le_def, BitVec.toNat_neg_of_pos (BitVec.pos_of_msb hx), toNat_intMin_of_pos (Nat.zero_lt_succ _)]
+    simp only [Nat.succ_eq_add_one, Nat.add_one_sub_one, Nat.pow_add_one]
+    rw [msb_eq_true_iff_two_mul_ge, Nat.pow_add_one] at hx
+    omega
+
+theorem BitVec.msb_umod_eq_false_of_left {x : BitVec w} (hx : x.msb = false) (y : BitVec w) : (x % y).msb = false := by
+  rw [msb_eq_false_iff_two_mul_lt] at hx ⊢
+  rw [toNat_umod]
+  refine Nat.lt_of_le_of_lt ?_ hx
+  rw [Nat.mul_le_mul_left_iff (by decide)]
+  exact Nat.mod_le _ _
+
+theorem BitVec.width_pos_of_ne {x y : BitVec w} (h : x ≠ y) : 0 < w := by
path: src/Init/Data/BitVec/Bitblast.lean
user: login: TwoFX
body: I think the `w` parameter is currently usually referred to as `length` in identifiers?
created_at: 2025-03-28T07:15:09Z
author_association: MEMBER
diff_hunk: @@ -1648,7 +1648,95 @@ theorem toInt_sdiv (a b : BitVec w) : (a.sdiv b).toInt = (a.toInt.tdiv b.toInt).
     conv => lhs; rw [(by omega: w = (w - 1) + 1)]
     simp [Nat.pow_succ, Int.natCast_pow, Int.mul_comm]
   · rw [← toInt_bmod_cancel]
-    rw [BitVec.toInt_sdiv_of_ne_or_ne _ _ (by simpa only [Classical.not_and_iff_not_or_not] using h)]
+    rw [BitVec.toInt_sdiv_of_ne_or_ne _ _ (by simpa only [Decidable.not_and_iff_not_or_not] using h)]
+
+theorem BitVec.toNat_intMin_of_pos (hw : 0 < w) : (intMin w).toNat = 2 ^ (w - 1) := by
path: src/Init/Data/BitVec/Bitblast.lean
user: login: Rob23oba
body: oops, yeah, fixed
created_at: 2025-03-28T14:53:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1648,7 +1648,95 @@ theorem toInt_sdiv (a b : BitVec w) : (a.sdiv b).toInt = (a.toInt.tdiv b.toInt).
     conv => lhs; rw [(by omega: w = (w - 1) + 1)]
     simp [Nat.pow_succ, Int.natCast_pow, Int.mul_comm]
   · rw [← toInt_bmod_cancel]
-    rw [BitVec.toInt_sdiv_of_ne_or_ne _ _ (by simpa only [Classical.not_and_iff_not_or_not] using h)]
+    rw [BitVec.toInt_sdiv_of_ne_or_ne _ _ (by simpa only [Decidable.not_and_iff_not_or_not] using h)]
+
+theorem BitVec.toNat_intMin_of_pos (hw : 0 < w) : (intMin w).toNat = 2 ^ (w - 1) := by
+  rw [toNat_intMin, Nat.mod_eq_of_lt (Nat.pow_lt_pow_of_lt (by decide) (Nat.sub_one_lt_of_lt hw))]
+
+protected theorem BitVec.le_of_lt {x y : BitVec w} (h : x < y) : x ≤ y := Nat.le_of_lt h
+
+theorem BitVec.lt_intMin_iff {x : BitVec w} (hw : 0 < w) : x < intMin w ↔ x.msb = false := by
+  simp only [msb_eq_false_iff_two_mul_lt, BitVec.toNat_intMin_of_pos hw, lt_def]
+  rw [← Nat.mul_lt_mul_left (by decide : 0 < 2), ← Nat.pow_add_one', Nat.sub_one_add_one_eq_of_pos hw]
+
+theorem BitVec.intMin_le_iff {x : BitVec w} (hw : 0 < w) : intMin w ≤ x ↔ x.msb = true := by
+  rw [← Decidable.not_iff_not, BitVec.not_le, Bool.not_eq_true]
+  exact BitVec.lt_intMin_iff hw
+
+theorem BitVec.le_intMin_of_msb {x : BitVec w} (hx : x.msb = false) : x ≤ intMin w := by
+  match w with
+  | 0 => decide +revert
+  | w' + 1 =>
+    apply BitVec.le_of_lt
+    exact (BitVec.lt_intMin_iff (Nat.zero_lt_succ _)).mpr hx
+
+theorem BitVec.pos_of_msb {x : BitVec w} (hx : x.msb = true) : 0#w < x := by
+  apply Decidable.by_contra
+  intro h
+  simp only [BitVec.not_lt, le_zero_iff] at h
+  cases h
+  simp at hx
+
+theorem BitVec.neg_le_intMin_of_msb {x : BitVec w} (hx : x.msb = true) : -x ≤ intMin w := by
+  match w with
+  | 0 => decide +revert
+  | w' + 1 =>
+    rw [BitVec.le_def, BitVec.toNat_neg_of_pos (BitVec.pos_of_msb hx), toNat_intMin_of_pos (Nat.zero_lt_succ _)]
+    simp only [Nat.succ_eq_add_one, Nat.add_one_sub_one, Nat.pow_add_one]
+    rw [msb_eq_true_iff_two_mul_ge, Nat.pow_add_one] at hx
+    omega
+
+theorem BitVec.msb_umod_eq_false_of_left {x : BitVec w} (hx : x.msb = false) (y : BitVec w) : (x % y).msb = false := by
+  rw [msb_eq_false_iff_two_mul_lt] at hx ⊢
+  rw [toNat_umod]
+  refine Nat.lt_of_le_of_lt ?_ hx
+  rw [Nat.mul_le_mul_left_iff (by decide)]
+  exact Nat.mod_le _ _
+
+theorem BitVec.width_pos_of_ne {x y : BitVec w} (h : x ≠ y) : 0 < w := by
path: src/Init/Data/BitVec/Bitblast.lean
user: login: Rob23oba
body: Oh, thanks, didn't notice
created_at: 2025-03-28T14:53:51Z
author_association: CONTRIBUTOR

PR comments:
user: login: ghost
created_at: 2025-03-27T16:54:08Z
body: Mathlib CI status ([docs](https://leanprover-community.github.io/contribute/tags_and_branches.html)):
- ❗ Batteries/Mathlib CI will not be attempted unless your PR branches off the `nightly-with-mathlib` branch. Try `git rebase 7bd937580494311ecdb9502a7b5ab0ccf473bd44 --onto 1465c23e12f6f8b608ef944be4559b1b5e38f40a`. You can force Mathlib CI using the `force-mathlib-ci` label. (2025-03-27 16:54:08)
- ❗ Batteries/Mathlib CI will not be attempted unless your PR branches off the `nightly-with-mathlib` branch. Try `git rebase 7bd937580494311ecdb9502a7b5ab0ccf473bd44 --onto 060b2fe46fe728934744ee52271b92ab74cbd5b4`. You can force Mathlib CI using the `force-mathlib-ci` label. (2025-03-28 15:23:10)
author_association: NONE
user: login: tobiasgrosser
created_at: 2025-03-28T07:43:10Z
body: Nice. Thank you, @Rob23oba. These are elegant proofs. As @TwoFX said, please move the theorems into the relevant files. I am happy to help.
author_association: CONTRIBUTOR
user: login: Rob23oba
created_at: 2025-03-28T15:53:39Z
body: I moved most of the theorems to `Lemmas.lean` now and added some related lemmas.
author_association: CONTRIBUTOR