Skip to content

Commit 7e52e12

Browse files
add loop invariants & harnesses for DecimalSeq functions
1 parent 4c08921 commit 7e52e12

1 file changed

Lines changed: 71 additions & 1 deletion

File tree

library/core/src/num/dec2flt/decimal_seq.rs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//! available online: <https://nigeltao.github.io/blog/2020/parse-number-f64-simple.html>.
1111
1212
use crate::num::dec2flt::common::{ByteSlice, is_8digits};
13+
use crate::kani;
1314

1415
/// A decimal floating-point number, represented as a sequence of decimal digits.
1516
#[derive(Clone, Debug, PartialEq)]
@@ -83,6 +84,7 @@ impl DecimalSeq {
8384
//
8485
// Trim is only called in `right_shift` and `left_shift`.
8586
debug_assert!(self.num_digits <= Self::MAX_DIGITS);
87+
#[kani::loop_invariant(self.num_digits <= Self::MAX_DIGITS)]
8688
while self.num_digits != 0 && self.digits[self.num_digits - 1] == 0 {
8789
self.num_digits -= 1;
8890
}
@@ -98,6 +100,7 @@ impl DecimalSeq {
98100
let dp = self.decimal_point as usize;
99101
let mut n = 0_u64;
100102

103+
#[kani::loop_invariant(n < 10u64.pow(kaniindex as u32))]
101104
for i in 0..dp {
102105
n *= 10;
103106
if i < self.num_digits {
@@ -129,7 +132,14 @@ impl DecimalSeq {
129132
let mut read_index = self.num_digits;
130133
let mut write_index = self.num_digits + num_new_digits;
131134
let mut n = 0_u64;
132-
135+
136+
#[kani::loop_invariant(read_index <= Self::MAX_DIGITS &&
137+
write_index == read_index + num_new_digits &&
138+
n < 10u64 << (shift - 1) &&
139+
self.num_digits <= Self::MAX_DIGITS &&
140+
self.decimal_point <= self.num_digits as i32 &&
141+
kani::forall!(|i in (0,DecimalSeq::MAX_DIGITS)| self.digits[i] <= 9)
142+
)]
133143
while read_index != 0 {
134144
read_index -= 1;
135145
write_index -= 1;
@@ -144,6 +154,7 @@ impl DecimalSeq {
144154
n = quotient;
145155
}
146156

157+
#[kani::loop_invariant(self.num_digits <= Self::MAX_DIGITS && self.decimal_point <= self.num_digits as i32)]
147158
while n > 0 {
148159
write_index -= 1;
149160
let quotient = n / 10;
@@ -171,13 +182,16 @@ impl DecimalSeq {
171182
let mut read_index = 0;
172183
let mut write_index = 0;
173184
let mut n = 0_u64;
185+
#[kani::loop_invariant( n == 0 || (read_index > 0 && read_index <= self.num_digits + 64 - n.leading_zeros() as usize))]
174186
while (n >> shift) == 0 {
175187
if read_index < self.num_digits {
176188
n = (10 * n) + self.digits[read_index] as u64;
177189
read_index += 1;
178190
} else if n == 0 {
179191
return;
180192
} else {
193+
kani::assume(read_index == self.num_digits);
194+
#[kani::loop_invariant(n > 0 && read_index <= self.num_digits + 64 - n.leading_zeros() as usize && read_index > 0)]
181195
while (n >> shift) == 0 {
182196
n *= 10;
183197
read_index += 1;
@@ -194,13 +208,18 @@ impl DecimalSeq {
194208
return;
195209
}
196210
let mask = (1_u64 << shift) - 1;
211+
#[kani::loop_invariant(self.num_digits <= Self::MAX_DIGITS &&
212+
write_index < read_index &&
213+
write_index < Self::MAX_DIGITS - self.num_digits.saturating_sub(read_index)
214+
)]
197215
while read_index < self.num_digits {
198216
let new_digit = (n >> shift) as u8;
199217
n = (10 * (n & mask)) + self.digits[read_index] as u64;
200218
read_index += 1;
201219
self.digits[write_index] = new_digit;
202220
write_index += 1;
203221
}
222+
#[kani::loop_invariant(write_index <= Self::MAX_DIGITS)]
204223
while n > 0 {
205224
let new_digit = (n >> shift) as u8;
206225
n = 10 * (n & mask);
@@ -363,6 +382,7 @@ fn number_of_digits_decimal_left_shift(d: &DecimalSeq, mut shift: usize) -> usiz
363382
let pow5_b = (0x7FF & x_b) as usize;
364383
let pow5 = &TABLE_POW5[pow5_a..];
365384

385+
#[kani::loop_invariant(true)]
366386
for (i, &p5) in pow5.iter().enumerate().take(pow5_b - pow5_a) {
367387
if i >= d.num_digits {
368388
return num_new_digits - 1;
@@ -377,3 +397,53 @@ fn number_of_digits_decimal_left_shift(d: &DecimalSeq, mut shift: usize) -> usiz
377397

378398
num_new_digits
379399
}
400+
401+
#[cfg(kani)]
402+
#[unstable(feature = "kani", issue = "none")]
403+
pub mod decimal_seq_verify {
404+
use super::*;
405+
406+
impl kani::Arbitrary for DecimalSeq {
407+
fn any() -> DecimalSeq {
408+
let mut ret = DecimalSeq {
409+
num_digits: kani::any(),
410+
decimal_point: kani::any(),
411+
truncated: kani::any(),
412+
digits: kani::any() };
413+
kani::assume(a.num_digits <= DecimalSeq::MAX_DIGITS);
414+
kani::assume(a.decimal_point>=0);
415+
kani::assume(a.decimal_point <= a.num_digits as i32);
416+
kani::assume(kani::forall!(|i in (0,DecimalSeq::MAX_DIGITS)| a.digits[i] <= 9));
417+
ret
418+
}
419+
}
420+
421+
#[kani::proof]
422+
fn check_round() {
423+
let mut a: DecimalSeq = kani::any();
424+
a.round();
425+
}
426+
427+
#[kani::proof]
428+
fn check_number_of_digits_decimal_left_shift() {
429+
let mut a: DecimalSeq = kani::any();
430+
let shift: usize = kani::any_where(|x| *x > 0 && *x <= 60);
431+
let n = number_of_digits_decimal_left_shift(&a, shift);
432+
assert!(n <= 19);
433+
assert!(n == 19 || 1u64 << shift < 10u64.pow(n as u32 + 1))
434+
}
435+
436+
#[kani::proof]
437+
fn check_right_shift() {
438+
let mut a: DecimalSeq = kani::any();
439+
let shift: usize = kani::any_where(|x| *x > 0 && *x <= 60);
440+
a.right_shift(shift);
441+
}
442+
443+
#[kani::proof]
444+
fn check_left_shift() {
445+
let mut a: DecimalSeq = kani::any();
446+
let shift: usize = kani::any_where(|x| *x > 0 && *x <= 60);
447+
a.left_shift(shift);
448+
}
449+
}

0 commit comments

Comments
 (0)