Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - feat(data/nat/digits): add norm_digits tactic #4452

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 110 additions & 1 deletion src/data/nat/digits.lean
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/-
Copyright (c) 2020 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison, Shing Tak Lam
Authors: Scott Morrison, Shing Tak Lam, Mario Carneiro
-/
import data.int.modeq
import tactic.interval_cases
Expand All @@ -15,6 +15,9 @@ and reconstructing numbers from their digits.

We also prove some divisibility tests based on digits, in particular completing
Theorem #85 from https://www.cs.ru.nl/~freek/100/.

A basic `norm_digits` tactic is also provided for proving goals of the form
`nat.digits a b = l` where `a` and `b` are numerals.
-/

namespace nat
Expand Down Expand Up @@ -74,13 +77,23 @@ end

@[simp] lemma digits_zero_succ (n : ℕ) : digits 0 (n.succ) = [n+1] := rfl

theorem digits_zero_succ' : ∀ {n : ℕ} (w : 0 < n), digits 0 n = [n]
| 0 h := absurd h dec_trivial
| (n+1) _ := rfl

@[simp] lemma digits_one (n : ℕ) : digits 1 n = list.repeat 1 n := rfl

@[simp] lemma digits_one_succ (n : ℕ) : digits 1 (n + 1) = 1 :: digits 1 n := rfl

@[simp] lemma digits_add_two_add_one (b n : ℕ) :
digits (b+2) (n+1) = (((n+1) % (b+2)) :: digits (b+2) ((n+1) / (b+2))) := rfl

theorem digits_def' : ∀ {b : ℕ} (h : 2 ≤ b) {n : ℕ} (w : 0 < n),
digits b n = n % b :: digits b (n/b)
| 0 h := absurd h dec_trivial
| 1 h := absurd h dec_trivial
| (b+2) h := digits_aux_def _ _

@[simp]
lemma digits_of_lt (b x : ℕ) (w₁ : 0 < x) (w₂ : x < b) : digits b x = [x] :=
begin
Expand Down Expand Up @@ -544,4 +557,100 @@ begin
exact t,
end

/-! ### `norm_digits` tactic -/

namespace norm_digits

theorem digits_succ
(b n m r l)
(e : r + b * m = n)
(hr : r < b)
(h : nat.digits b m = l ∧ 2 ≤ b ∧ 0 < m) :
nat.digits b n = r :: l ∧ 2 ≤ b ∧ 0 < n :=
begin
rcases h with ⟨h, b2, m0⟩,
have b0 : 0 < b := by linarith,
have n0 : 0 < n := by linarith [mul_pos b0 m0],
refine ⟨_, b2, n0⟩,
obtain ⟨rfl, rfl⟩ := (nat.div_mod_unique b0).2 ⟨e, hr⟩,
subst h, exact nat.digits_def' b2 n0,
end

theorem digits_one
(b n) (n0 : 0 < n) (nb : n < b) :
nat.digits b n = [n] ∧ 2 ≤ b ∧ 0 < n :=
begin
have b2 : 2 ≤ b := by linarith,
refine ⟨_, b2, n0⟩,
rw [nat.digits_def' b2 n0, nat.mod_eq_of_lt nb,
(nat.div_eq_zero_iff (by linarith : 0 < b)).2 nb, nat.digits_zero],
end

open tactic

/-- Helper function for the `norm_digits` tactic. -/
meta def eval_aux (eb : expr) (b : ℕ) :
digama0 marked this conversation as resolved.
Show resolved Hide resolved
expr → ℕ → instance_cache → tactic (instance_cache × expr × expr)
| en n ic := do
let m := n / b,
let r := n % b,
(ic, er) ← ic.of_nat r,
(ic, pr) ← norm_num.prove_lt_nat ic er eb,
if m = 0 then do
(_, pn0) ← norm_num.prove_pos ic en,
return (ic, `([%%en] : list nat), `(digits_one %%eb %%en %%pn0 %%pr))
else do
em ← expr.of_nat `(ℕ) m,
(_, pe) ← norm_num.derive `(%%er + %%eb * %%em : ℕ),
(ic, el, p) ← eval_aux em m ic,
return (ic, `(@list.cons ℕ %%er %%el),
`(digits_succ %%eb %%en %%em %%er %%el %%pe %%pr %%p))

/-- Helper function for the `norm_digits` tactic. -/
meta def eval : expr → ℕ → expr → ℕ → tactic (expr × expr)
digama0 marked this conversation as resolved.
Show resolved Hide resolved
| eb b en 0 := return (`([] : list ℕ), `(nat.digits_zero %%eb))
| eb 0 en n := do
ic ← mk_instance_cache `(ℕ),
(_, pn0) ← norm_num.prove_pos ic en,
return (`([%%en] : list ℕ), `(@nat.digits_zero_succ' %%en %%pn0))
| eb 1 en n := do
ic ← mk_instance_cache `(ℕ),
(_, pn0) ← norm_num.prove_pos ic en,
s ← simp_lemmas.add_simp simp_lemmas.mk `list.repeat,
(rhs, p2) ← simplify s [] `(list.repeat 1 %%en),
p ← mk_eq_trans `(nat.digits_one %%en) p2,
return (rhs, p)
| eb b en n := do
ic ← mk_instance_cache `(ℕ),
(_, l, p) ← eval_aux eb b en n ic,
p ← mk_app ``and.left [p],
return (l, p)

end norm_digits

open tactic

/--
A tactic for normalizing expressions of the form `nat.digits a b = l` where
`a` and `b` are numerals.

```
example : nat.digits 10 123 = [3,2,1] := by norm_digits
```
-/
meta def norm_digits : tactic unit :=
do `(nat.digits %%eb %%en = %%el') ← target,
b ← expr.to_nat eb,
n ← expr.to_nat en,
(el, p) ← nat.norm_digits.eval eb b en n,
unify el el',
exact p

digama0 marked this conversation as resolved.
Show resolved Hide resolved
run_cmd add_interactive [``norm_digits]

add_tactic_doc
{ name := "norm_digits",
category := doc_category.tactic,
decl_names := [`tactic.interactive.norm_digits],
tags := ["arithmetic", "decision procedure"] }
end nat
12 changes: 12 additions & 0 deletions test/norm_digits.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import data.nat.digits

example : nat.digits 0 0 = [] := by norm_digits
example : nat.digits 1 0 = [] := by norm_digits
example : nat.digits 2 0 = [] := by norm_digits
example : nat.digits 10 0 = [] := by norm_digits
example : nat.digits 0 1 = [1] := by norm_digits
example : nat.digits 0 1000 = [1000] := by norm_digits
example : nat.digits 1 10 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] := by norm_digits
example : nat.digits 2 65536 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] := by norm_digits
example : nat.digits 3 30000000 = [0, 1, 0, 1, 2, 0, 1, 1, 0, 0, 1, 1, 2, 0, 0, 2] := by norm_digits
example : nat.digits 10 1234567 = [7, 6, 5, 4, 3, 2, 1] := by norm_digits