Skip to main content

improv_verified/
radix.rs

1//! Base conversion between integers and digit vectors (most significant
2//! first): `BASE`, and the 10-digit two's complement of `DEC2BIN` / `BIN2DEC`
3//! and the rest of the radix 2 / 8 / 16 family.
4
5#[allow(unused_imports)]
6use vstd::arithmetic::div_mod::*;
7#[allow(unused_imports)]
8use vstd::prelude::*;
9
10verus! {
11
12/// `r` to the `e`.
13pub open spec fn pw(r: nat, e: nat) -> nat
14    decreases e,
15{
16    if e == 0 {
17        1
18    } else {
19        r * pw(r, (e - 1) as nat)
20    }
21}
22
23/// The value of digits `ds` (most significant first) in radix `r` (Horner).
24pub open spec fn value(ds: Seq<u8>, r: nat) -> nat
25    decreases ds.len(),
26{
27    if ds.len() == 0 {
28        0
29    } else {
30        value(ds.drop_last(), r) * r + ds.last() as nat
31    }
32}
33
34/// Every digit is below `r`.
35pub open spec fn digits_below(ds: Seq<u8>, r: nat) -> bool {
36    forall|i: int| 0 <= i < ds.len() ==> (#[trigger] ds[i] as nat) < r
37}
38
39/// The radixes of the two's-complement functions.
40pub open spec fn tc_radix(r: u32) -> bool {
41    r == 2 || r == 8 || r == 16
42}
43
44/// What 10-digit two's-complement digits in radix `r` mean: nothing if there
45/// are more than 10 or a digit is not below `r`; ten digits led by one of at
46/// least `r / 2` are negative (the value less `r¹⁰`); otherwise the value.
47pub open spec fn decode_spec(ds: Seq<u8>, r: nat) -> Option<int> {
48    if ds.len() > 10 || !digits_below(ds, r) {
49        None
50    } else if ds.len() == 10 && ds[0] as nat >= r / 2 {
51        Some(value(ds, r) - pw(r, 10))
52    } else {
53        Some(value(ds, r) as int)
54    }
55}
56
57proof fn lemma_pw_pos(r: nat, e: nat)
58    requires
59        r >= 1,
60    ensures
61        pw(r, e) >= 1,
62    decreases e,
63{
64    if e > 0 {
65        lemma_pw_pos(r, (e - 1) as nat);
66        let p = pw(r, (e - 1) as nat);
67        assert(r * p >= 1) by (nonlinear_arith)
68            requires
69                r >= 1,
70                p >= 1,
71        ;
72    }
73}
74
75proof fn lemma_pw_mono(r: nat, a: nat, b: nat)
76    requires
77        r >= 1,
78        a <= b,
79    ensures
80        pw(r, a) <= pw(r, b),
81    decreases b,
82{
83    if a < b {
84        lemma_pw_mono(r, a, (b - 1) as nat);
85        let p = pw(r, (b - 1) as nat);
86        assert(p <= r * p) by (nonlinear_arith)
87            requires
88                r >= 1,
89        ;
90    }
91}
92
93/// Digits below `r` are worth less than `r` to their count.
94proof fn lemma_value_bound(ds: Seq<u8>, r: nat)
95    requires
96        digits_below(ds, r),
97    ensures
98        value(ds, r) < pw(r, ds.len()),
99    decreases ds.len(),
100{
101    if ds.len() > 0 {
102        let p = ds.drop_last();
103        assert(digits_below(p, r)) by {
104            assert forall|i: int| 0 <= i < p.len() implies (#[trigger] p[i] as nat) < r by {
105                assert(p[i] == ds[i]);
106            }
107        }
108        lemma_value_bound(p, r);
109        let v = value(p, r);
110        let d = ds.last() as nat;
111        assert(d < r);
112        let q = pw(r, p.len());
113        assert(pw(r, ds.len()) == r * q);
114        assert(value(ds, r) == v * r + d);
115        assert(v * r + d < r * q) by (nonlinear_arith)
116            requires
117                v < q,
118                d < r,
119        ;
120    }
121}
122
123/// A leading digit is worth `d · r^len(ds)`.
124proof fn lemma_value_prepend(d: u8, ds: Seq<u8>, r: nat)
125    ensures
126        value(seq![d] + ds, r) == d as nat * pw(r, ds.len()) + value(ds, r),
127    decreases ds.len(),
128{
129    let s = seq![d] + ds;
130    let dn = d as nat;
131    if ds.len() == 0 {
132        assert(s.drop_last() =~= Seq::<u8>::empty());
133        assert(s.last() == d);
134        let z = value(s.drop_last(), r);
135        assert(z == 0);
136        assert(value(s, r) == z * r + dn);
137        assert(pw(r, 0) == 1);
138        assert(z * r + dn == dn * 1 + 0) by (nonlinear_arith)
139            requires
140                z == 0,
141        ;
142    } else {
143        let t = ds.drop_last();
144        assert(s.drop_last() =~= seq![d] + t);
145        assert(s.last() == ds.last());
146        lemma_value_prepend(d, t, r);
147        let p = pw(r, t.len());
148        let w = value(t, r);
149        let l = ds.last() as nat;
150        assert(pw(r, ds.len()) == r * p);
151        assert(value(ds, r) == w * r + l);
152        assert(value(s, r) == value(seq![d] + t, r) * r + l);
153        assert((dn * p + w) * r + l == dn * (r * p) + (w * r + l)) by (nonlinear_arith);
154    }
155}
156
157/// The leading digit pins the value between `d · r^(n−1)` and
158/// `(d + 1) · r^(n−1)`.
159proof fn lemma_value_first(ds: Seq<u8>, r: nat)
160    requires
161        ds.len() >= 1,
162        digits_below(ds, r),
163    ensures
164        ds[0] as nat * pw(r, (ds.len() - 1) as nat) <= value(ds, r),
165        value(ds, r) < (ds[0] as nat + 1) * pw(r, (ds.len() - 1) as nat),
166{
167    let rest = ds.drop_first();
168    assert(ds =~= seq![ds[0]] + rest);
169    lemma_value_prepend(ds[0], rest, r);
170    assert(digits_below(rest, r)) by {
171        assert forall|i: int| 0 <= i < rest.len() implies (#[trigger] rest[i] as nat) < r by {
172            assert(rest[i] == ds[i + 1]);
173        }
174    }
175    lemma_value_bound(rest, r);
176    let p = pw(r, rest.len());
177    let d = ds[0] as nat;
178    let w = value(rest, r);
179    assert(d * p + w < (d + 1) * p) by (nonlinear_arith)
180        requires
181            w < p,
182    ;
183}
184
185/// `u` in radix `r` (2…36): its digits, most significant first, without
186/// leading zeros (`0` is one digit). Each digit is below `r` and they are
187/// worth exactly `u`.
188pub fn to_digits(u: u64, r: u32) -> (ds: Vec<u8>)
189    requires
190        2 <= r <= 36,
191    ensures
192        value(ds@, r as nat) == u,
193        digits_below(ds@, r as nat),
194        ds.len() >= 1,
195        ds.len() == 1 || ds@[0] != 0,
196{
197    let rr = r as u64;
198    let mut ds: Vec<u8> = Vec::new();
199    let mut x = u;
200    proof {
201        let w = value(ds@, r as nat);
202        assert(w == 0);
203        assert(pw(r as nat, 0) == 1);
204        assert(u as nat == x as nat * 1 + w);
205    }
206    loop
207        invariant_except_break
208            ds.len() == 0 || x != 0,
209        invariant
210            2 <= r <= 36,
211            rr == r,
212            u as nat == x as nat * pw(r as nat, ds.len() as nat) + value(ds@, r as nat),
213            digits_below(ds@, r as nat),
214        ensures
215            value(ds@, r as nat) == u,
216            ds.len() >= 1,
217            ds.len() == 1 || ds@[0] != 0,
218        decreases x,
219    {
220        let ghost x0 = x;
221        let ghost ds0 = ds@;
222        let d = (x % rr) as u8;
223        x /= rr;
224        ds.insert(0, d);
225        proof {
226            let rn = r as nat;
227            let p = pw(rn, ds0.len());
228            let v0 = value(ds0, rn);
229            lemma_fundamental_div_mod(x0 as int, rr as int);
230            lemma_mod_bound(x0 as int, rr as int);
231            assert(d as int == x0 as int % rr as int);
232            assert(x as int == x0 as int / rr as int);
233            assert(ds@ =~= seq![d] + ds0);
234            lemma_value_prepend(d, ds0, rn);
235            assert(pw(rn, ds@.len()) == rn * p);
236            assert(u as nat == x as nat * (rn * p) + (d as nat * p + v0)) by (nonlinear_arith)
237                requires
238                    x0 as int == rn as int * x as int + d as int,
239                    u as nat == x0 as nat * p + v0,
240            ;
241            assert forall|i: int| 0 <= i < ds@.len() implies (#[trigger] ds@[i] as nat) < rn by {
242                if i > 0 {
243                    assert(ds@[i] == ds0[i - 1]);
244                }
245            }
246            if x0 > 0 {
247                lemma_div_decreases(x0 as int, rr as int);
248            }
249            if x == 0 {
250                assert(x0 as int == d as int) by (nonlinear_arith)
251                    requires
252                        x0 as int == rn as int * x as int + d as int,
253                        x == 0,
254                ;
255                let w = value(ds@, rn);
256                let q = pw(rn, ds@.len());
257                assert(u as nat == w) by (nonlinear_arith)
258                    requires
259                        u as nat == x as nat * q + w,
260                        x == 0,
261                ;
262            }
263        }
264        if x == 0 {
265            break;
266        }
267    }
268    ds
269}
270
271/// `r¹⁰` for a two's-complement radix.
272fn full10(r: u32) -> (f: i64)
273    requires
274        tc_radix(r),
275    ensures
276        f == pw(r as nat, 10),
277        f <= 0x100_0000_0000,
278{
279    if r == 2 {
280        assert(pw(2, 10) == 1024) by (compute);
281        1024
282    } else if r == 8 {
283        assert(pw(8, 10) == 0x4000_0000) by (compute);
284        0x4000_0000
285    } else {
286        assert(pw(16, 10) == 0x100_0000_0000) by (compute);
287        0x100_0000_0000
288    }
289}
290
291/// `v` as 10-digit two's complement in radix 2, 8 or 16: `None` exactly when
292/// `v` is outside `[−r¹⁰/2, r¹⁰/2)`. The digits are those of `v` (`v + r¹⁰`
293/// if negative) without leading zeros: at most 10, each below `r`, exactly 10
294/// for a negative `v`, and [`decode`] maps them back to `v`.
295pub fn encode(v: i64, r: u32) -> (res: Option<Vec<u8>>)
296    requires
297        tc_radix(r),
298    ensures
299        res is None <==> !(-(pw(r as nat, 10) as int / 2) <= v < pw(r as nat, 10) as int / 2),
300        res matches Some(ds) ==> {
301            &&& digits_below(ds@, r as nat)
302            &&& 1 <= ds.len() <= 10
303            &&& (v < 0 ==> ds.len() == 10)
304            &&& decode_spec(ds@, r as nat) == Some(v as int)
305        },
306{
307    let full = full10(r);
308    let half = full / 2;
309    if v < -half || v >= half {
310        return None;
311    }
312    let u: u64 = if v < 0 {
313        (v + full) as u64
314    } else {
315        v as u64
316    };
317    let ds = to_digits(u, r);
318    proof {
319        let rn = r as nat;
320        let f = pw(rn, 10);
321        let p9 = pw(rn, 9);
322        let h = rn / 2;
323        let n = ds.len();
324        let w = value(ds@, rn);
325        let d0 = ds@[0] as nat;
326        assert(w == u);
327        lemma_value_first(ds@, rn);
328        lemma_value_bound(ds@, rn);
329        lemma_pw_pos(rn, 9);
330        assert(f == rn * p9);
331        assert(rn == 2 * h && h >= 1);
332        assert(f / 2 == h * p9) by (nonlinear_arith)
333            requires
334                f == rn * p9,
335                rn == 2 * h,
336        ;
337        assert(h * p9 >= p9) by (nonlinear_arith)
338            requires
339                h >= 1,
340        ;
341        // At most 10 digits: a leading digit ≥ 1 makes u ≥ r^(n−1) ≥ r¹⁰.
342        if n > 10 {
343            lemma_pw_mono(rn, 10, (n - 1) as nat);
344            let p_top = pw(rn, (n - 1) as nat);
345            assert(d0 * p_top >= p_top) by (nonlinear_arith)
346                requires
347                    d0 >= 1,
348            ;
349        }
350        if v >= 0 {
351            if n == 10 && d0 >= h {
352                assert(d0 * p9 >= h * p9) by (nonlinear_arith)
353                    requires
354                        d0 >= h,
355                ;
356            }
357        } else {
358            // u ≥ r¹⁰/2 ≥ r⁹: 10 digits, led by one ≥ r/2.
359            if n <= 9 {
360                lemma_pw_mono(rn, n as nat, 9);
361            }
362            if n == 10 && d0 + 1 <= h {
363                assert((d0 + 1) * p9 <= h * p9) by (nonlinear_arith)
364                    requires
365                        d0 + 1 <= h,
366                ;
367            }
368        }
369    }
370    Some(ds)
371}
372
373/// The integer 10-digit two's-complement digits `ds` in radix 2, 8 or 16
374/// stand for (`decode_spec`): `None` for more than 10 digits or a digit not
375/// below `r`.
376pub fn decode(ds: &[u8], r: u32) -> (res: Option<i64>)
377    requires
378        tc_radix(r),
379    ensures
380        res is None <==> decode_spec(ds@, r as nat) is None,
381        res matches Some(v) ==> decode_spec(ds@, r as nat) == Some(v as int),
382        ds.len() > 10 ==> res is None,
383        (exists|i: int| 0 <= i < ds.len() && ds[i] as nat >= r) ==> res is None,
384{
385    if ds.len() > 10 {
386        return None;
387    }
388    let full = full10(r);
389    let mut v: i64 = 0;
390    let mut i = 0;
391    while i < ds.len()
392        invariant
393            tc_radix(r),
394            ds.len() <= 10,
395            0 <= i <= ds.len(),
396            full == pw(r as nat, 10),
397            full <= 0x100_0000_0000,
398            digits_below(ds@.subrange(0, i as int), r as nat),
399            v == value(ds@.subrange(0, i as int), r as nat),
400        decreases ds.len() - i,
401    {
402        let d = ds[i];
403        if d as u32 >= r {
404            assert(!digits_below(ds@, r as nat)) by {
405                assert(ds@[i as int] as nat >= r);
406            }
407            return None;
408        }
409        proof {
410            let p = ds@.subrange(0, i + 1);
411            let p0 = ds@.subrange(0, i as int);
412            assert(p.drop_last() =~= p0);
413            assert(p.last() == d);
414            assert(digits_below(p, r as nat)) by {
415                assert forall|k: int| 0 <= k < p.len() implies (#[trigger] p[k] as nat) < r by {
416                    if k < i {
417                        assert(p[k] == p0[k]);
418                    }
419                }
420            }
421            lemma_value_bound(p, r as nat);
422            lemma_pw_mono(r as nat, (i + 1) as nat, 10);
423            assert(v * r + d == value(p, r as nat));
424        }
425        v = v * (r as i64) + d as i64;
426        i += 1;
427    }
428    assert(ds@.subrange(0, ds.len() as int) =~= ds@);
429    if ds.len() == 10 && ds[0] as u32 >= r / 2 {
430        v -= full;
431    }
432    Some(v)
433}
434
435/// The round trip, as one statement: for every `v` in range, [`encode`]
436/// succeeds and [`decode`] of its digits gives `v` back.
437pub fn round_trip(v: i64, r: u32) -> (ok: bool)
438    requires
439        tc_radix(r),
440        -(pw(r as nat, 10) as int / 2) <= v < pw(r as nat, 10) as int / 2,
441    ensures
442        ok,
443{
444    match encode(v, r) {
445        Some(ds) => decode(&ds, r) == Some(v),
446        None => false,
447    }
448}
449
450} // verus!
451
452#[cfg(test)]
453#[cfg_attr(coverage_nightly, coverage(off))]
454mod tests {
455    use super::*;
456
457    #[test]
458    fn digits_of_zero_and_more() {
459        assert_eq!(to_digits(0, 2), vec![0]);
460        assert_eq!(to_digits(10, 2), vec![1, 0, 1, 0]);
461        assert_eq!(to_digits(255, 16), vec![15, 15]);
462        assert_eq!(to_digits(35, 36), vec![35]);
463    }
464
465    #[test]
466    fn twos_complement_round_trips() {
467        for r in [2u32, 8, 16] {
468            let half = i64::from(r).pow(10) / 2;
469            for v in [-half, -1, 0, 1, half - 1] {
470                let ds = encode(v, r).unwrap();
471                assert_eq!(decode(&ds, r), Some(v), "{v} in radix {r}");
472                assert!(round_trip(v, r));
473            }
474            assert_eq!(encode(half, r), None);
475            assert_eq!(encode(-half - 1, r), None);
476        }
477        assert_eq!(encode(-1, 2), Some(vec![1; 10]));
478        assert_eq!(decode(&[1; 11], 2), None);
479        assert_eq!(decode(&[2], 2), None);
480        assert_eq!(decode(&[], 16), Some(0));
481    }
482
483    fn text(ds: &[u8]) -> String {
484        ds.iter()
485            .map(|&d| char::from_digit(u32::from(d), 16).unwrap())
486            .collect()
487    }
488
489    /// Every 10-digit binary value, and a stride through octal and hex,
490    /// against `format!` on `v` (`v + r¹⁰` when negative).
491    #[test]
492    fn encode_matches_format() {
493        for (r, step) in [(2u32, 1i64), (8, 9_973), (16, 7_919_993)] {
494            let full = i64::from(r).pow(10);
495            let mut v = -full / 2;
496            while v < full / 2 {
497                let u = if v < 0 { v + full } else { v };
498                let want = match r {
499                    2 => format!("{u:b}"),
500                    8 => format!("{u:o}"),
501                    _ => format!("{u:x}"),
502                };
503                let ds = encode(v, r).unwrap();
504                assert_eq!(text(&ds), want, "{v} in radix {r}");
505                assert_eq!(decode(&ds, r), Some(v));
506                v += step;
507            }
508        }
509        for u in (0..1_000_000u64).chain([u64::MAX, (1 << 53) - 1]) {
510            assert_eq!(text(&to_digits(u, 16)), format!("{u:x}"));
511        }
512    }
513}