1#[allow(unused_imports)]
6use vstd::arithmetic::div_mod::*;
7#[allow(unused_imports)]
8use vstd::prelude::*;
9
10verus! {
11
12pub 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
23pub 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
34pub 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
39pub open spec fn tc_radix(r: u32) -> bool {
41 r == 2 || r == 8 || r == 16
42}
43
44pub 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
93proof 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
123proof 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
157proof 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
185pub 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
271fn 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
291pub 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 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 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
373pub 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
435pub 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} #[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 #[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}