-
Notifications
You must be signed in to change notification settings - Fork 55
Add loop invariants and harnesses for DecimalSeq functions #439
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
base: main
Are you sure you want to change the base?
Changes from all commits
7e52e12
1402d50
7f2fd77
874421e
b5ee3b2
13af0ca
d2eb714
c8d98b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,8 @@ | |
//! algorithm can be found in "ParseNumberF64 by Simple Decimal Conversion", | ||
//! available online: <https://nigeltao.github.io/blog/2020/parse-number-f64-simple.html>. | ||
|
||
#[cfg(kani)] | ||
use crate::kani; | ||
use crate::num::dec2flt::common::{ByteSlice, is_8digits}; | ||
|
||
/// A decimal floating-point number, represented as a sequence of decimal digits. | ||
|
@@ -83,6 +85,7 @@ impl DecimalSeq { | |
// | ||
// Trim is only called in `right_shift` and `left_shift`. | ||
debug_assert!(self.num_digits <= Self::MAX_DIGITS); | ||
#[kani::loop_invariant(self.num_digits <= Self::MAX_DIGITS)] | ||
while self.num_digits != 0 && self.digits[self.num_digits - 1] == 0 { | ||
self.num_digits -= 1; | ||
} | ||
|
@@ -98,6 +101,7 @@ impl DecimalSeq { | |
let dp = self.decimal_point as usize; | ||
let mut n = 0_u64; | ||
|
||
#[kani::loop_invariant(n < 10u64.pow(kaniindex as u32))] | ||
for i in 0..dp { | ||
n *= 10; | ||
if i < self.num_digits { | ||
|
@@ -130,6 +134,13 @@ impl DecimalSeq { | |
let mut write_index = self.num_digits + num_new_digits; | ||
let mut n = 0_u64; | ||
|
||
#[kani::loop_invariant(read_index <= Self::MAX_DIGITS && | ||
write_index == read_index + num_new_digits && | ||
n < 10u64 << (shift - 1) && | ||
self.num_digits <= Self::MAX_DIGITS && | ||
self.decimal_point <= self.num_digits as i32 && | ||
kani::forall!(|i in (0,DecimalSeq::MAX_DIGITS)| self.digits[i] <= 9) | ||
)] | ||
while read_index != 0 { | ||
read_index -= 1; | ||
write_index -= 1; | ||
|
@@ -144,6 +155,7 @@ impl DecimalSeq { | |
n = quotient; | ||
} | ||
|
||
#[kani::loop_invariant(self.num_digits <= Self::MAX_DIGITS && self.decimal_point <= self.num_digits as i32)] | ||
while n > 0 { | ||
write_index -= 1; | ||
let quotient = n / 10; | ||
|
@@ -171,13 +183,15 @@ impl DecimalSeq { | |
let mut read_index = 0; | ||
let mut write_index = 0; | ||
let mut n = 0_u64; | ||
#[kani::loop_invariant( n == 0 || (read_index > 0 && read_index <= self.num_digits + 64 - n.leading_zeros() as usize))] | ||
while (n >> shift) == 0 { | ||
if read_index < self.num_digits { | ||
n = (10 * n) + self.digits[read_index] as u64; | ||
read_index += 1; | ||
} else if n == 0 { | ||
return; | ||
} else { | ||
#[kani::loop_invariant(n > 0 && read_index <= self.num_digits + 64 - n.leading_zeros() as usize && read_index > 0)] | ||
while (n >> shift) == 0 { | ||
n *= 10; | ||
read_index += 1; | ||
|
@@ -194,13 +208,18 @@ impl DecimalSeq { | |
return; | ||
} | ||
let mask = (1_u64 << shift) - 1; | ||
#[kani::loop_invariant(self.num_digits <= Self::MAX_DIGITS && | ||
write_index < read_index && | ||
write_index < Self::MAX_DIGITS - self.num_digits.saturating_sub(read_index) | ||
)] | ||
while read_index < self.num_digits { | ||
let new_digit = (n >> shift) as u8; | ||
n = (10 * (n & mask)) + self.digits[read_index] as u64; | ||
read_index += 1; | ||
self.digits[write_index] = new_digit; | ||
write_index += 1; | ||
} | ||
#[kani::loop_invariant(write_index <= Self::MAX_DIGITS)] | ||
while n > 0 { | ||
let new_digit = (n >> shift) as u8; | ||
n = 10 * (n & mask); | ||
|
@@ -363,6 +382,7 @@ fn number_of_digits_decimal_left_shift(d: &DecimalSeq, mut shift: usize) -> usiz | |
let pow5_b = (0x7FF & x_b) as usize; | ||
let pow5 = &TABLE_POW5[pow5_a..]; | ||
|
||
#[kani::loop_invariant(true)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This currently fails with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I mentioned it in the PR description. |
||
for (i, &p5) in pow5.iter().enumerate().take(pow5_b - pow5_a) { | ||
if i >= d.num_digits { | ||
return num_new_digits - 1; | ||
|
@@ -377,3 +397,55 @@ fn number_of_digits_decimal_left_shift(d: &DecimalSeq, mut shift: usize) -> usiz | |
|
||
num_new_digits | ||
} | ||
|
||
#[cfg(kani)] | ||
#[unstable(feature = "kani", issue = "none")] | ||
pub mod decimal_seq_verify { | ||
use super::*; | ||
|
||
impl kani::Arbitrary for DecimalSeq { | ||
fn any() -> DecimalSeq { | ||
let mut ret = DecimalSeq { | ||
num_digits: kani::any_where(|x| *x <= DecimalSeq::MAX_DIGITS), | ||
decimal_point: kani::any_where(|x| *x >= 0), | ||
truncated: kani::any(), | ||
digits: kani::any(), | ||
}; | ||
kani::assume(ret.decimal_point <= ret.num_digits as i32); | ||
kani::assume(kani::forall!(|i in (0,DecimalSeq::MAX_DIGITS)| ret.digits[i] <= 9)); | ||
ret | ||
} | ||
} | ||
|
||
#[kani::proof] | ||
fn check_round() { | ||
let mut a: DecimalSeq = kani::any(); | ||
a.round(); | ||
} | ||
|
||
#[kani::proof] | ||
fn check_number_of_digits_decimal_left_shift() { | ||
let mut a: DecimalSeq = kani::any(); | ||
let shift: usize = kani::any_where(|x| *x > 0 && *x <= 60); | ||
let n = number_of_digits_decimal_left_shift(&a, shift); | ||
// 19 is the greatest number x such that 10u64^x does not overflow | ||
// It is also TABLE.max << 11 | ||
assert!(n <= 19); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain the magic number "19" (and include a comment doing so)? |
||
assert!(n == 19 || 1u64 << shift < 10u64.pow(n as u32 + 1)) | ||
} | ||
|
||
#[kani::proof] | ||
fn check_right_shift() { | ||
let mut a: DecimalSeq = kani::any(); | ||
//This function is called in parse_long_mantissa function (slow.rs), in which the maximum of shift is 60 | ||
let shift: usize = kani::any_where(|x| *x > 0 && *x <= 60); | ||
a.right_shift(shift); | ||
} | ||
|
||
#[kani::proof] | ||
fn check_left_shift() { | ||
let mut a: DecimalSeq = kani::any(); | ||
let shift: usize = kani::any_where(|x| *x > 0 && *x <= 60); | ||
a.left_shift(shift); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.