以下の内容はhttps://lpha-z.hatenablog.com/entry/2025/07/27/231500より取得しました。


Lean 4 で LZA の証明(その1)

先週の記事(Lean 4に入門する - よーる)でLean 4に入門しました。 私がLean 4を使ってやってみたいことは、浮動小数点数アルゴリズムの性質を示すことと、浮動小数点数演算器の正しさを証明することです。

後者で最近特に気になっているのは、LZA(leading zeros anticipation, 先行ゼロの予測)のアルゴリズムの正しさの検証です。 LZAは高速な浮動小数点数積和演算器の実装に使われるハードウェアアルゴリズムですが(絶対値のLeading Zero Anticipatory (LZA)の勉強 - よーる)、その証明は自明と言えるものではありません。 正しく動作することを上記の記事では「どこかは分からないが、ある桁より上の桁に特定の性質を満たすものがあるはず」のような形で説明しましたが、ビット列の両端のコーナーケースや、何ビット余分に拡張すればいいのかなど、気になる点が複数あります。 そこで、そのような点も含めて、計算機に確認してもらいたいのです。

以下本記事では、まずはLZC(leading zeros count)を定義します。

BitVec

まず、LZCは入力となるビット列の長さに依存する点に注意します。 つまりC++の記法で書けば、leading_zeros_count<uint32_t>(0)は32ですが、leading_zeros_count<uint64_t>(0)は64であるということです。 そのため、符号なし整数(Nat)をそのまま使うのはかなり不便で、「nビットの符号なし整数」のようなものを扱う必要があります。 この目的のため、BitVecを使います。

基本的な使い方

使い方は以下のような感じです。

import Mathlib.Data.BitVec
import Mathlib.Tactic
def a : BitVec 8 := 0x3A
def b : BitVec 8 := 0xA9

example : a &&& b = 0x28 := by decide

よさそうですね。

練習1

ビット演算に関する性質を証明することで、BitVecの使い方に慣れていきます。

example (n : Nat) (a : BitVec n) (b : BitVec n) : a &&& b <= b := by
  induction n
  · cases a <;> rename_i da
    cases b <;> rename_i db
    cases da <;> rename_i qa
    cases db <;> rename_i qb
    cases qa <;> cases qb <;> simp [BitVec.and_self]
  · cases a <;> rename_i da
    cases b <;> rename_i db
    cases da <;> rename_i qa ha
    cases db <;> rename_i qb hb
    simp_all only [le_ofFin, toFin_and]
    cases qa <;> cases qb
    simp

BitVecFinを使っているようなのですが、はがすのが大変な上、何を使って書き換えればいいのか探すのが本当に大変です。 40分くらいかけてここまでたどりつきましたが、詰まりました。

ChatGPTに聞いたところ、Nat.land_le_rightが使えると教えてくれました。 上で手に入れた書き換えルールを使って書き直したところ、次の正しい証明をすぐに書くことができました。

example (n : Nat) (a : BitVec n) (b : BitVec n) : a &&& b <= b := by
  cases' a with qa ha
  cases' b with qb hb
  simp [le_ofFin, toFin_and, Fin.le_def]
  exact Nat.and_le_right

そもそも、証明の最初の二行は特に意味がないので取り除けます。

example (n : Nat) (a : BitVec n) (b : BitVec n) : a &&& b <= b := by
  simp [le_ofFin, toFin_and, Fin.le_def]
  exact Nat.and_le_right

練習2

足し算に関する性質の証明も、LZAには必須でしょう。 nビット整数とnビット整数を足し合わせると、2n - 2以下になる、という命題を証明することでBitVecの使い方に慣れていきます。

実際の所、最初に書いた命題は<を使っていて正しくなく、30分ほど溶かしました。 なんでBitVec<は符号付き比較なのでしょう? それを直した後、30分ほどで、以下の証明を作ることができました。

example (n : Nat) (a : BitVec n) (b : BitVec n) : ((a.cons false) + (b.cons false)).ult (BitVec.fill (n+1) true) := by
  simp [BitVec.cons, BitVec.allOnes]
  rw [BitVec.add_ofFin]
  simp
  cases' a with qa
  cases' b with qb
  simp
  simp [Fin.add_def]
  simp [BitVec.ult]
  cases' qa with da ha
  cases' qb with db hb
  simp

  have h1 : da + db < 2 ^ (n + 1) - 1 := by
    omega
  have h2 : (da + db) % 2 ^ (n + 1) = da + db := by
    exact Nat.mod_eq_of_lt (by omega)

  rw [h2]
  exact h1

これを簡略化すると、以下のようになりました。

example (n : Nat) (a : BitVec n) (b : BitVec n) : ((a.cons false) + (b.cons false)).ult (BitVec.fill (n+1) true) := by
  simp [BitVec.cons, BitVec.allOnes]
  rw [BitVec.add_ofFin]
  cases' a with qa
  cases' b with qb
  simp [Fin.add_def]
  simp [BitVec.ult]
  cases' qa with da ha
  cases' qb with db hb

  have h : (da + db) % 2 ^ (n + 1) = da + db := by
    exact Nat.mod_eq_of_lt (by omega)

  rw [h]
  omega

補題hを作って即使っているのは無意味そうです。 ChatGPTに聞いたら、Nat.mod_eq_of_ltは等式を返すのでrw[...]の中に直接かけるよ、と言われました。 これでdaとかdbを使わなくなったので、さらにシンプルになりました。

example (n : Nat) (a : BitVec n) (b : BitVec n) : ((a.cons false) + (b.cons false)).ult (BitVec.fill (n+1) true) := by
  simp [BitVec.cons, BitVec.allOnes]
  rw [BitVec.add_ofFin, Fin.add_def]
  simp [BitVec.ult]
  rw [Nat.mod_eq_of_lt (by omega)]
  omega

依然として、simprwの使い方がよくわかりませんし、_defのつくバージョンとつかないバージョンの違いもよくわかりません。

LZC

定義

LZCは次のように定義すればよいでしょう。 BitVecのスライスを作る関数の仕様を調べるのを含めて15分くらいで作ることができました。

def LZC {n : Nat} (x : BitVec n) : Nat :=
  if x.msb then 0 else
    match n with
    | 0 => 0
    | Nat.succ m => 1 + LZC (BitVec.extractLsb' 0 m x)

ところで、Nat.logで見かけたdecreasing_byはいらなかったんでしょうか? 原始帰納関数の定義方法に則っているので大丈夫だったのかもしれません(Nat.logの定義は普通ではない帰納法に基づく定義です)。

いきなり高度な定理を証明するのは無理

さて、ここから加算結果のLZCに関する以下の命題を証明しようと思いましたが、30分くらいかけて証明を進めるうちに正しくないことがわかりました。

theorem LZC_of_add_eq_min_LZC {n : Nat} (a b : BitVec n) :
  1 + LZC ((a.cons false) + (b.cons false)) = Nat.min (LZC a) (LZC b) ∨ LZC ((a.cons false) + (b.cons false)) = Nat.min (LZC a) (LZC b)

正しくはこうでしょうか?

theorem LZC_of_add_eq_min_LZC {n : Nat} (a b : BitVec n) :
  1 + LZC ((a.cons false) + (b.cons false)) = Nat.min (LZC (a.cons false)) (LZC (b.cons false))
  ∨ LZC ((a.cons false) + (b.cons false)) = Nat.min (LZC (a.cons false)) (LZC (b.cons false))

これを証明するためにはまず次の補題が必要そうだったので、80分ほど頑張りましたが、向きを証明したあたりで力尽きました。

theorem lt_LZC_iff_ge {n : Nat} (x : BitVec n) (q : Nat) (hq : q ≤ n)
  : lzc x < q ↔ x.toNat ≥ 2 ^ (n-q) := by

その途中では、使いそうだった以下の定理も証明しましたが、結局使いませんでした。

theorem LZC_le_BitWidth {n : Nat} (x : BitVec n) : LZC x ≤ n := by
  induction n with
  | zero => exact Nat.le_zero.mpr rfl
  | succ m hn => simp [LZC]
                 split
                 · exact Nat.le_add_left 0 _
                 · have h (q : BitVec m): LZC q ≤ m := by bound
                   have h1 : LZC (BitVec.extractLsb' 0 m x) ≤ m := by simp [h]
                   linarith [h1]

boundhintタクティックで提示されたタクティックで、その内容はよくわかっていません。 BitVec関連の補題を証明するときに、hintで出てくることが多い気がします。

計算的定義と性質に関する定理

少し前に、ライブラリにdecide版とそうでないものが含まれていることを不思議に思ってChatGPTに聞いたことがありました。 その回答には、以下のような部分が含まれており、これに納得しました。

ChatGPTの出力。『証明用:「nが素数である」を命題として扱う:def Prime (n : Nat) : Prop := ...; 計算したいときは:def isPrime (n : Nat) : Bool := ... -- 実装を用意;両者をつなぐ定理を用意しておく theorem isPrime_correct {n} : isPrime n = true ↔ Prime n := ...;この分離によって、証明側は軽く、計算側は効率よく、というすみわけができます。』
ChatGPTの出力

このことを考えると、現状は計算的定義しか存在せず、それが不便を生み出している気がします。 そこで次の、性質に関する命題を証明しましょう。

theorem lzc_eq_iff_msb_prefix {n : Nat} (x : BitVec n) (t : Nat)
  : lzc x = t ↔ x.getMsbD t ∧ (∀s: Nat, s < t → x.getMsbD (s) = false)

この定理の名前はChatGPTに考えてもらいました。

ChatGPTの出力。『この定理にお勧めの候補 候補名   説明 lzc_eq_iff_msb_prefix    “LZC が t である ↔ MSB が t で true になる直前まで false” という意味を端的に表す lzc_eq_iff_first_one “最初の 1 (first one) の位置” で決まることを強調 lzc_spec    “LZC の仕様” として 1 本にまとめたい場合のシンプル版 lzc_characterization  数学的な “特徴づけ” を意識したややフォーマル寄り lzc_eq_iff_get_msbD  実装名 getMsbD を明示的に出すならこの形  個人的には lzc_eq_iff_msb_prefix が      lzc が主体であること      eq_iff パターンであること      何を条件にしているか(“msb prefix”)が読んでわかる  という点でバランスが良く、好まれやすい命名だと思います。』
ChatGPTの出力

ChatGPTが「個人的には」とか言うので驚きました。

なお、Lean 4では基本はsnake_caseだと言われたので、関数名をlzcに書き換えました。

deltasimp

simp [lzc]とすると定義が展開された上で簡略化が働きます。 この簡略化を嫌ってdelta [lzc]と書いたら、関数の浮動点表示に展開されて途方にくれました。 両者はともに定義の展開を行いますが、再帰関数の場合はそういう違いがあるようです。

LZC 0 のややこしさ

一瞬tに関する帰納法で進めようとしてしまいましたが、lzcの定義的にnに関する帰納法の方が扱いやすそうなのでその方針で進めていきます(ChatGPTもその方針を提示していました)。 というわけでベースケース(n = 0)を証明しようとしましたが、明らかに証明できないことがわかりました。 つまり、先ほど示した命題は間違いであり、正しくは以下のようになるはずです。

theorem lzc_eq_iff_msb_prefix {n : Nat} (x : BitVec n) (t : Nat)
  : lzc x = t ↔ (x = 0 ∧ t = n) ∨ (x.getMsbD t ∧ (∀s: Nat, s < t → x.getMsbD s = false))

LZC 0周りのコーナーケースは良く混乱するところなので、計算機で証明をチェックできることのありがたさを今一度体感しました。

generalizing tが必要

ChatGPTが提示したスケッチを元に証明を進めていたら、以下のような感じになりました。

theorem lzc_eq_iff_msb_prefix {n : Nat} (x : BitVec n) (t : Nat)
  : lzc x = t ↔ (x = 0 ∧ t = n) ∨ (x.getMsbD t ∧ (∀s: Nat, s < t → x.getMsbD s = false)) := by
  apply Iff.intro
  · intro h
    induction n with
    | zero => left
              simp [lzc] at h
              bound
    | succ m ih => right
                   cases hx : x.msb <;> simp [lzc, hx] at h ⊢
                   · have ih' := ih (BitVec.extractLsb' 0 m x) (t-1)

ここで型が合わないというエラーになって途方にくれました。

冷静に考えると、induction n generalizing t withで始めないといけませんでしたね(Natural Number Gameでちゃんと教わります。よくできたカリキュラムです)。 それを解決したらうまくいきました。 帰納法の仮定はこのように使うんですね。

あと、仮定も結論もsimpしたいときは、を使えるんですね。 この記号は見た目通り(?)\|-で入力できます。

帰納法の仮定の引数の型をちゃんと表示してほしいと思いましたが、後になって考えると、∀がついているのが引数なんですね。 ∀で導入された変数は、外側の変数を隠すので、同じ変数名でも外側のものを指しているわけではないので、さらに混乱します。

x ≠ 0 ↔ ∃t, x.getMsbD t

80分ほど頑張りましたが、かなり証明がつらいです。 つらさの原因は、帰納法で示したい命題に∨が出てくるところにあります。 x ≠ 0 ↔ ∃t, x.getMsbD t が成り立つので、∨の右側はx ≠ 0の時にしか成り立たず、つまりここに出てくる∨は実際には排他です。 排他な∨は、証明する分には不便ではありませんが、帰納法の仮定部分で出てくると不便です。 具体的には、x = 0を示したいので∨の左側を取り出したいのですが、このときに∨の右側が成り立たないことを示す必要が出てきます。

x ≠ 0 ↔ ∃t, x.getMsbD t補題として先に示したほうがよさそうです。 130分かけて以下のように証明しました。 力尽きたので簡略化は行っていません。

theorem forall_lt_testBit_imply_le_two_pow {x : Nat} (p: ∀i ≥ n, Nat.testBit x i = false) : x < 2^n := by
  contrapose! p
  simp_all only [Bool.not_eq_false]
  exact Nat.exists_ge_and_testBit_of_ge_two_pow p

theorem add_shiftRight (a b c : Nat) : a >>> (b + c) = a >>> b >>> c := by
  simp [Nat.shiftRight_add]

lemma lt_of_shiftRight_le_one (a b : Nat) (h : a ≤ b) : a >>> 1 ≤ b >>> 1 := by
  simp [Nat.shiftRight_eq_div_pow]
  exact Nat.div_le_div_right h

theorem lt_of_shiftRight_le (a b : Nat) (h : a ≤ b) : a >>> k ≤ b >>> k := by
  induction k
  case zero => exact h
  case succ m ih =>
    have (x n : Nat) : x >>> (n + 1) = x >>> n >>> 1 := by exact add_shiftRight x n 1
    rw [this] -- repeat rwだと、 x >>> 1 >>> 0 >>> ... >>> 0 >>> n に無限展開されるのでNG
    nth_rewrite 2 [this]
    exact lt_of_shiftRight_le_one (a >>> m) (b >>> m) ih

theorem non_zero_iff_exists_getMsbD {n : Nat} (x : BitVec n)
  : x ≠ 0 ↔ ∃t, x.getMsbD t := by
  by_cases hn : n = 0

  have hx : x = 0 := by bound
  apply Iff.intro
  · intro
    contradiction
  · intro ho
    contrapose! ho
    simp [BitVec.getMsbD]
    omega

  -- n ≠ 0
  apply Iff.intro
  · show x ≠ 0 → ∃t, x.getMsbD t
    intro hx
    have hxn : x.toNat ≠ 0 := by
      intro h0
      apply hx
      exact BitVec.eq_of_toNat_eq h0
    have hxpos : 0 < x.toNat := Nat.pos_of_ne_zero hxn
    have ht : x.toNat ≤ 2^n - 1 := by omega
    have : 2^0 ≤ x.toNat := by linarith
    rcases Nat.exists_ge_and_testBit_of_ge_two_pow this with ⟨k, hk₁, hk₂⟩
    have hk₂' : x.toNat >>> k ≥ 1 := by
      have : x.toNat >>> k % 2 = 1 := by
        simp [Nat.testBit] at hk₂
        exact hk₂
      contrapose! this
      have : x.toNat >>> k = 0 := by
        linarith -- omegaではだめ
      rw [this]
      simp
    have hk₃ : k < n := by
      contrapose! hk₂'
      have hx2 : x.toNat ≤ 2^n - 1 := by omega
      have hl : x.toNat >>> k ≤ (2^n - 1) >>> k := by
        exact lt_of_shiftRight_le x.toNat (2^n - 1) ht
      have : (2^n - 1) >>> k = 0 := by
        simp [Nat.shiftRight_eq_div_pow]
        refine Nat.sub_one_lt_of_le ?_ ?_
        exact Nat.two_pow_pos n
        refine Nat.pow_le_pow_of_le ?_ hk₂'
        decide
      linarith

-- testBit_toNatの定義↓
-- theorem testBit_toNat (x : BitVec w) : x.toNat.testBit i = x.getLsbD i := rfl


    use n - 1 - k
    simp [BitVec.getMsbD]
    have : n - 1 - k < n := by omega
    constructor
    · exact this
    · have : n - 1 - (n - 1 - k) = k := by omega
      rw [this]
      simp [BitVec.getLsbD]
      exact hk₂
  · show (∃ t, x.getMsbD t = true) → x ≠ 0
    intro ht
    aesop

x ≠ 0の扱い

結局この補題を証明しましたが、いちいち補題を適用するのすら面倒に感じました。 そこで、あまりきれいではありませんが、証明する定理を以下に切り替えました(x ≠ 0を追加しました)。 必要であれば、x ≠ 0を取り除けばいいでしょう。

theorem lzc_eq_iff_msb_prefix {n : Nat} (x : BitVec n) (t : Nat)
  : lzc x = t ↔ (x = 0 ∧ t = n) ∨ (x ≠ 0 ∧ x.getMsbD t ∧ (∀s: Nat, s < t → x.getMsbD s = false)) := by
  by_cases hxz : x = 0
  induction n generalizing t with
  | zero =>
      apply Iff.intro
      · intro h
        left
        constructor
        · exact hxz
        · simp_all [lzc]
      · intro h
        contrapose! h
        simp [lzc] at h
        tauto
  | succ m ih =>
      apply Iff.intro
      · intro h
        left
        simp [lzc] at h
        have : BitVec.msb x = false := by simp_all only [BitVec.ofNat_eq_ofNat, true_and, ne_eq, not_true_eq_false, BitVec.getMsbD_zero,
  Bool.false_eq_true, implies_true, and_true, and_self, or_false, BitVec.msb_zero, ↓reduceIte, BitVec.extractLsb'_zero]
        simp [this] at h
        set y := BitVec.extractLsb' 0 m x
        have yz : y = 0 := by bound
        have ih' := ih y m
        simp [yz] at ih'
        constructor
        exact hxz
        have : lzc y = m := by bound
        rw [this] at h
        linarith
      · intro h
        simp [hxz] at h
        rw [h]
        simp [lzc]
        have : BitVec.msb x = false := by simp_all only [BitVec.ofNat_eq_ofNat, true_and, ne_eq, not_true_eq_false, BitVec.getMsbD_zero,
  Bool.false_eq_true, implies_true, and_true, and_self, or_false, BitVec.msb_zero, ↓reduceIte, BitVec.extractLsb'_zero]
        simp [this]
        set y := BitVec.extractLsb' 0 m x
        have yz : y = 0 := by bound
        have ih' := ih y m
        simp [yz] at ih'
        have : lzc y = m := by bound
        rw [this]
        exact Nat.add_comm 1 m

  induction n generalizing t with
  | zero =>
      have : x = 0 := by bound
      contradiction
  | succ m ih =>
      apply Iff.intro
      · intro h
        right
        constructor; exact hxz
        simp [lzc] at h
        by_cases hxm : x.msb
        · simp [hxm] at h
          rw [← h]
          simp [BitVec.getMsbD]
          exact Lean.Grind.Bool.eq_true_of_and_eq_true_right hxm
        · set y := BitVec.extractLsb' 0 m x
          have hy : y.cons false = x := by
            simp [y]
            refine BitVec.eq_of_getElem_eq_iff.mpr ?_
            intro i
            by_cases hi : i >= m + 1
            omega
            by_cases hi : i = m
            simp [hi, BitVec.cons]
            simp [BitVec.msb, BitVec.getMsbD] at hxm
            simp [hxm]
            simp [BitVec.append_def]
            have hi : i < m := by omega
            simp [BitVec.cons, BitVec.extractLsb']
            simp [BitVec.append_def]
            simp [hi]
            simp [BitVec.getLsbD]
            exact fun hi => rfl
          simp [hxm] at h
          have ih' := ih y (t-1)
          have hy2 : y ≠ 0 := by
            by_contra!
            rw [this] at hy
            rw [← hy] at hxz
            contrapose! hxz
            simp [BitVec.cons]
          have ih'' := ih' hy2
          have hz : y.getMsbD (t - 1) = true ∧ ∀ s < t - 1, y.getMsbD s = false := by aesop
          constructor
          · rw [← hy]
            simp [BitVec.cons]
            constructor
            · linarith
            · simp [hz]
          · intro s
            intro hs1
            by_cases hs2: s = 0
            · rw [← hy, hs2]
              simp [BitVec.getMsbD, BitVec.cons, BitVec.append_def]
            · contrapose! hz
              intro hy3
              use s-1
              constructor
              · omega
              · rw [← hy] at hz
                simp [BitVec.getMsbD, BitVec.cons, BitVec.append_def] at hz hy3 ⊢
                constructor
                · have : t - 1 < m := by linarith
                  omega
                · have : m - 1 - (s - 1) = m - s := by omega
                  rw [this]
                  tauto
      · intro h
        contrapose! h
        constructor
        intro
        contradiction
        intro
        intro htop
        contrapose! h
        simp [lzc]
        by_cases xmsb: x.msb
        · simp [xmsb]
          have hq : x.getMsbD 0 = true := by
            simp [BitVec.msb] at xmsb
            exact xmsb
          contrapose! hq
          contrapose! h
          have : 0 < t := by omega
          use 0
          constructor
          · exact this
          · exact ne_false_of_eq_true xmsb
        · simp [xmsb]
          set y := BitVec.extractLsb' 0 m x
          have hy : y.cons false = x := by
            simp [y]
            refine BitVec.eq_of_getElem_eq_iff.mpr ?_
            intro i
            by_cases hi : i >= m + 1
            omega
            by_cases hi : i = m
            simp [hi, BitVec.cons]
            simp [BitVec.msb, BitVec.getMsbD] at xmsb
            simp [xmsb]
            simp [BitVec.append_def]
            have hi : i < m := by omega
            simp [BitVec.cons, BitVec.extractLsb']
            simp [BitVec.append_def]
            simp [hi]
            simp [BitVec.getLsbD]
            exact fun hi => rfl
          have ht : t ≠ 0 := by
            by_contra ht
            simp [ht, BitVec.getMsbD, BitVec.msb] at htop xmsb
            exact Std.Tactic.BVDecide.Reflect.Bool.false_of_eq_true_of_eq_false htop xmsb
          have ih' := ih y (t-1)
          have hy2 : y ≠ 0 := by
            by_contra!
            rw [this] at hy
            rw [← hy] at hxz
            contrapose! hxz
            simp [BitVec.cons]
          have ih'' : (lzc y = t - 1 ↔ y = 0 ∧ t - 1 = m ∨ y ≠ 0 ∧ y.getMsbD (t - 1) = true ∧ ∀ s < t - 1, y.getMsbD s = false) := by
            exact ih y (t - 1) hy2
          have ih''' : (y = 0 ∧ t - 1 = m ∨ y ≠ 0 ∧ y.getMsbD (t - 1) = true ∧ ∀ s < t - 1, y.getMsbD s = false) → lzc y = t - 1 := by tauto
          contrapose! ih'''
          constructor
          · right
            constructor
            exact hy2
            constructor
            simp [BitVec.getMsbD, BitVec.getLsbD] at htop ⊢
            constructor
            omega
            have : m-1 - (t-1) = m -t := by omega
            rw [this]
            contrapose! htop
            intro
            rw [← hy]
            simp [BitVec.cons]
            exact eq_false_of_ne_true htop
            contrapose! h
            rw [← hy]
            simp [BitVec.cons]
            contrapose! h
            intro s
            intro hs
            contrapose! h
            use s + 1
            simp
            constructor
            · omega
            · exact eq_true_of_ne_false h
          omega

この証明には230分ほどかかったようです。 同じ補題をコピペで導入しているところもあり、もっと構造化できそうです。

かなり無意味なことをやっている部分がいくつかあります。 仮定に含まれる∧や∨を分解する方法がわからないので、

  • ∧であれば、contrapose!してからleftrightでいらないほうを破棄して、contrapose!でもどす
  • ∨であれば、contrapose!してからconstructで自明に成り立たない方(否定形なので自明に成り立つ)を証明して消して、contrapose!でもどす

などとしています。 覚えていることだけでやろうとしている弊害です。(←調べろ)

そもそも、∃や∀が含まれる命題をcontraposeするとものすごく見にくくなるので、それも混乱します。 ちゃんと調べるべきですね。

一方で、∃s<t,とか∀s<tとかが含まれる命題を本当にcontraposeしたいときは、手でやると間違えるので、計算機が機械的に変形してくれるのはありがたいです。 この辺は、まともに数学の証明を(手で)たくさん書いていれば、息をするように書き換えられるのでしょうけれど……。

他、気づいたことを雑多にまとめます。

  • rw??というタクティックを使うと、GUI上で選択した部分を書き換える方法の候補が表示される
  • use 0とかやるとゴールにsorryが出てきて混乱するが、改行すると直る
  • exactには引数が必要だが、exactと入力した時点で(=引数がない状態で)赤線も引かれずに終わった感じになるので混乱しがち
  • Natの引き算は普通ではない(x < y で x - y = 0となる)のでlinarithでは証明できないことが多い。omegaでも証明できないことが多いが、「普通でない」ケースが発生しないことを補題で証明しておけば、omegaで証明できる。補題の証明自体もomegaでできるので全部omegaだけでできないの?と言う感じだけれど、おそらく何が必要かをこちらから示すのが大事なのだろう
  • linarithで証明できてomegaでは証明できないことがある。自然数に関してはlinarithの上位互換のような気がするのだけれど、なぜ?

感想です。

  • 証明に詰まったあたりで、どのような補題が必要か考えて、その補題を証明……という風にやっているけれど、成り立たない補題の証明(“ミスパス”)に迷い込んでしまうことがよくある。見通しが極端に悪いので、迷い込んだことが発覚するのもかなり先になる。深さ優先的に突き進むのではなく、やはり全体像を把握して証明のスケッチを作ってから進める方がよさそう
  • 今回の命題はx = 0か否かの分岐・inductioniffの三つが絡み合っているが、どの順番で解きほぐせばいいのかがよくわからなかった。x = 0か否かの分岐を先に片付けるのは良かった。inductionを先に使う今回の証明方針は、iff帰納法の仮定に置ける、つまり仮定が強まるので良いだろうと思ったが、結局逆向きは使わなかった気がするので、取り出すのが面倒になっただけであった。やはり証明のスケッチを書いてから証明に臨むべきである

まとめ

LZAの性質を証明しようと思いましたが、まずはLZC単体の性質を示すだけで時間切れとなりました。




以上の内容はhttps://lpha-z.hatenablog.com/entry/2025/07/27/231500より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14