Skip to content

Commit c5ff3bc

Browse files
MavenRainalexanderlhicksclaude
authored
Prove uniqueness of getBit binary representation (#181)
* Prove uniqueness of getBit binary representation Add getBit_injective (pointwise bit equality implies number equality) and getBit_repr_unique (any binary coefficient sequence summing to j must agree with getBit at each position). Remove resolved TODO. * style(Data/Nat/Bitwise): fix style guide violations in getBit uniqueness proofs * chore(lint): allow Bitwise.lean length in style-exceptions Bitwise.lean grew past the line-count threshold after adding the getBit uniqueness proofs. Per reviewer guidance, add it to the style exceptions list rather than splitting the file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Alexander Hicks <25369263+alexanderlhicks@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent f06b836 commit c5ff3bc

3 files changed

Lines changed: 75 additions & 3 deletions

File tree

CompPoly/Data/Nat/Bitwise.lean

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,6 @@ lemma getBit_eq_pred_getBit_of_div_two {n k : ℕ} (h_k: k > 0) :
923923
conv_lhs => rw [←Nat.sub_add_cancel (n:=k) (m:=1) (h:=by omega)]
924924
exact Eq.symm (getBit_of_shiftRight (k - 1))
925925

926-
-- TODO: uniqueness of this representation?
927926
theorem getBit_repr {ℓ : Nat} :
928927
∀ j, j < 2^ℓ → j = ∑ k ∈ Finset.Icc 0 (ℓ-1), (getBit k j) * 2^k := by
929928
induction ℓ with
@@ -1075,6 +1074,78 @@ theorem getBit_repr_univ {ℓ : Nat} :
10751074
have h_a_lt_ℓ: a < ℓ := by exact a.isLt
10761075
omega
10771076

1077+
/-- Two natural numbers with identical bits at every position are equal. -/
1078+
theorem getBit_injective (a b : ℕ) (h : ∀ k, getBit k a = getBit k b) : a = b := by
1079+
apply Nat.eq_of_testBit_eq
1080+
intro n
1081+
unfold Nat.testBit
1082+
unfold getBit at h
1083+
simp only [Nat.one_and_eq_mod_two, Nat.and_one_is_mod] at h ⊢
1084+
rw [h n]
1085+
1086+
/-- The binary representation of a number via `getBit` is unique: any sequence of
1087+
0/1 coefficients that sums to `j` must agree with `getBit` at each position. -/
1088+
theorem getBit_repr_unique {ℓ : ℕ} {j : ℕ} (h_j : j < 2 ^ ℓ)
1089+
(c : ℕ → ℕ) (h_bin : ∀ k, k < ℓ → c k = 0 ∨ c k = 1)
1090+
(h_sum : j = ∑ k ∈ Finset.Icc 0 (ℓ - 1), c k * 2 ^ k) :
1091+
∀ k, k < ℓ → c k = getBit k j := by
1092+
-- Proof by induction on ℓ, extracting the lowest bit at each step.
1093+
induction ℓ generalizing j c with
1094+
| zero => intro k hk; omega
1095+
| succ ℓ ih =>
1096+
intro k hk
1097+
rw [show ℓ + 1 - 1 = ℓ from by omega] at h_sum
1098+
by_cases hℓ0 : ℓ = 0
1099+
· subst hℓ0
1100+
simp only [Finset.Icc_self, Finset.sum_singleton, pow_zero, mul_one] at h_sum
1101+
interval_cases k
1102+
rw [h_sum]
1103+
simp only [getBit, Nat.shiftRight_zero, Nat.and_one_is_mod]
1104+
rcases h_bin 0 (by omega) with h₁ | h₂ <;> [rw [h₁]; rw [h₂]]
1105+
· -- Split: j = c 0 + (even tail). Extract c 0 via mod 2, then recurse on j/2.
1106+
have h_split := sum_Icc_split (fun k ↦ c k * 2 ^ k) 0 0 ℓ (by omega) (by omega)
1107+
rw [h_split, Finset.Icc_self, Finset.sum_singleton, pow_zero, mul_one] at h_sum
1108+
-- The tail sum is even
1109+
set S := ∑ n ∈ Finset.Icc 1 ℓ, c n * 2 ^ n with hS_def
1110+
have h_even : 2 ∣ S :=
1111+
Finset.dvd_sum (fun n hn ↦
1112+
dvd_mul_of_dvd_right (dvd_pow_self 2 (by simp [Finset.mem_Icc] at hn; omega)) _)
1113+
-- c 0 = getBit 0 j (parity argument)
1114+
have h_c0 : c 0 = getBit 0 j := by
1115+
simp only [getBit, Nat.shiftRight_zero, Nat.and_one_is_mod]
1116+
obtain ⟨m, hm⟩ := h_even
1117+
rw [h_sum, hm]
1118+
rcases h_bin 0 (by omega) with h₁ | h₂ <;> [rw [h₁]; rw [h₂]] <;> omega
1119+
by_cases hk0 : k = 0
1120+
· exact hk0 ▸ h_c0
1121+
· -- k > 0: recurse on j/2 with shifted coefficients
1122+
-- We need: j / 2 = ∑ i ∈ Icc 0 (ℓ-1), c(i+1) * 2^i
1123+
-- Reindex the tail: S = 2 * ∑ c(i+1) * 2^i
1124+
have h_reindex : S = 2 * ∑ n ∈ Finset.Icc 0 (ℓ - 1), c (n + 1) * 2 ^ n := by
1125+
rw [hS_def, Finset.mul_sum]
1126+
apply Finset.sum_nbij' (fun n ↦ n - 1) (fun n ↦ n + 1)
1127+
· intro n hn; simp only [Finset.mem_Icc] at hn ⊢; omega
1128+
· intro n hn; simp only [Finset.mem_Icc] at hn ⊢; omega
1129+
· intro n hn; simp only [Finset.mem_Icc] at hn; omega
1130+
· intro n hn; simp only [Finset.mem_Icc] at hn; omega
1131+
· intro n hn
1132+
have hn' := (Finset.mem_Icc.mp hn).1
1133+
rw [show n - 1 + 1 = n from by omega]
1134+
rw [show 2 ^ n = 2 ^ (n - 1) * 2 from by
1135+
rw [← pow_succ, show n - 1 + 1 = n from by omega]]
1136+
ring
1137+
have h_div : j / 2 = ∑ n ∈ Finset.Icc 0 (ℓ - 1), c (n + 1) * 2 ^ n := by
1138+
rw [h_sum, h_reindex]
1139+
rcases h_bin 0 (by omega) with h₁ | h₂ <;> [rw [h₁]; rw [h₂]] <;> omega
1140+
-- Apply IH to j / 2 with shifted coefficients
1141+
have h_res := ih (by omega : j / 2 < 2 ^ ℓ) (fun n ↦ c (n + 1))
1142+
(fun n hn ↦ h_bin (n + 1) (by omega)) h_div (k - 1) (by omega)
1143+
-- h_res : c ((k-1) + 1) = getBit (k-1) (j/2)
1144+
-- Rewrite (k-1)+1 to k in h_res
1145+
have hk_sub : k - 1 + 1 = k := by omega
1146+
simp only [hk_sub] at h_res
1147+
rw [h_res, getBit_eq_pred_getBit_of_div_two (by omega : k > 0)]
1148+
10781149
lemma getLowBits_succ {n: ℕ} (numLowBits: ℕ) :
10791150
getLowBits (numLowBits + 1) n = getLowBits numLowBits n
10801151
+ (getBit numLowBits n) <<< numLowBits := by

CompPoly/Fields/Binary/Tower/Abstract/Split.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ The power basis for `BTField (k+1)` over `BTField k` is {1, Z (k+1)}
8080
def powerBasisSucc (k : ℕ) :
8181
PowerBasis (BTField k) (BTField (k+1)) := by
8282
let pb : PowerBasis (BTField k) (AdjoinRoot (poly k)) :=
83-
AdjoinRoot.powerBasis (hf:=by exact poly_ne_zero k)
84-
-- ⊢ algebra_adjacent_tower k = AdjoinRoot.instAlgebra (poly k) => TODO : make a lemma for this
83+
AdjoinRoot.powerBasis (hf := by exact poly_ne_zero k)
8584
-- NOTE : pb.gen is definitionally equal to AdjoinRoot.root (poly k)
85+
-- See `algebra_adjacent_tower_eq_AdjoinRoot_algebra` for the algebra instance equality.
8686
have h_eq : AdjoinRoot (poly k) = BTField (k+1) := BTField_succ_eq_adjoinRoot k
8787
-- ⊢ PowerBasis (BTField k) (BTField (k + 1))
8888
apply pb.map (e:=BTField_succ_alg_equiv_adjoinRoot k)

scripts/style-exceptions.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ CompPoly/Fields/Binary/Tower/Concrete/Core.lean : line 1 : ERR_NUM_LIN : 1700 fi
1111
CompPoly/Fields/Binary/AdditiveNTT/AdditiveNTT.lean : line 1 : ERR_NUM_LIN : 3000 file contains 2826 lines, try to split it up : ERR_NUM_LIN
1212
CompPoly/Fields/Binary/AdditiveNTT/AdditiveNTT.lean : line 8 : ERR_TAC : Files in CompPoly should not import the whole Mathlib.Tactic folder : ERR_TAC
1313
CompPoly/Fields/Binary/AdditiveNTT/NovelPolynomialBasis.lean : line 1 : ERR_NUM_LIN : 1800 file contains 1636 lines, try to split it up : ERR_NUM_LIN
14+
CompPoly/Data/Nat/Bitwise.lean : line 1 : ERR_NUM_LIN : 1700 file contains 1537 lines, try to split it up : ERR_NUM_LIN
1415
CompPoly/Bivariate/GuruswamiSudan/Root/Alekhnovich/Correctness.lean : line 1 : ERR_NUM_LIN : 2600 file contains 2421 lines, try to split it up : ERR_NUM_LIN
1516
CompPoly/Data/ExtTreeMap/DTreeMap.lean : line 1 : ERR_LIN : Line has more than 100 characters : ERR_LIN

0 commit comments

Comments
 (0)