Skip to content

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

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Changes from 5 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7e52e12
add loop invariants & harnesses for DecimalSeq functions
thanhnguyen-aws Aug 1, 2025
1402d50
remove unneccessary assume
thanhnguyen-aws Aug 1, 2025
7f2fd77
Merge branch 'main' into decseqinvariant
thanhnguyen-aws Aug 1, 2025
874421e
fix format + add comments
thanhnguyen-aws Aug 1, 2025
b5ee3b2
Merge branch 'decseqinvariant' of https://github.com/thanhnguyen-aws/…
thanhnguyen-aws Aug 1, 2025
13af0ca
Update library/core/src/num/dec2flt/decimal_seq.rs
thanhnguyen-aws Aug 4, 2025
d2eb714
Update library/core/src/num/dec2flt/decimal_seq.rs
thanhnguyen-aws Aug 4, 2025
c8d98b3
fix impl Arbitrary
thanhnguyen-aws Aug 4, 2025
5753d41
change kaniindex to kani::index
thanhnguyen-aws Aug 11, 2025
e9b0c54
Merge branch 'main' into decseqinvariant
thanhnguyen-aws Aug 19, 2025
425e153
Merge branch 'main' into decseqinvariant
thanhnguyen-aws Aug 21, 2025
5cd5a6c
add forall + change kani to safety
thanhnguyen-aws Aug 21, 2025
0ff0c95
fix format
thanhnguyen-aws Aug 21, 2025
e8ac050
kani::forall -> forall
thanhnguyen-aws Aug 21, 2025
f71ef69
add -Z quantifier
thanhnguyen-aws Aug 21, 2025
f74f6e8
add assumption
thanhnguyen-aws Aug 21, 2025
ebe6ace
Update library/core/src/num/dec2flt/decimal_seq.rs
tautschnig Aug 22, 2025
4559232
Update library/core/src/num/dec2flt/decimal_seq.rs
tautschnig Aug 22, 2025
c876cd6
Update scripts/run-kani.sh
tautschnig Aug 22, 2025
f9e0a67
Revert "Update scripts/run-kani.sh"
tautschnig Aug 22, 2025
b8b73bc
write_index, again
tautschnig Aug 22, 2025
8ccd693
safety -> kani
thanhnguyen-aws Aug 22, 2025
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
73 changes: 73 additions & 0 deletions library/core/src/num/dec2flt/decimal_seq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//! algorithm can be found in "ParseNumberF64 by Simple Decimal Conversion",
//! available online: <https://nigeltao.github.io/blog/2020/parse-number-f64-simple.html>.

use crate::kani;
use crate::num::dec2flt::common::{ByteSlice, is_8digits};

/// A decimal floating-point number, represented as a sequence of decimal digits.
Expand Down Expand Up @@ -83,6 +84,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;
}
Expand All @@ -98,6 +100,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 {
Expand Down Expand Up @@ -130,6 +133,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;
Expand All @@ -144,6 +154,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;
Expand Down Expand Up @@ -171,13 +182,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;
Expand All @@ -194,13 +207,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);
Expand Down Expand Up @@ -363,6 +381,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)]
for (i, &p5) in pow5.iter().enumerate().take(pow5_b - pow5_a) {
if i >= d.num_digits {
return num_new_digits - 1;
Expand All @@ -377,3 +396,57 @@ 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(),
decimal_point: kani::any(),
truncated: kani::any(),
digits: kani::any(),
};
kani::assume(a.num_digits <= DecimalSeq::MAX_DIGITS);
kani::assume(a.decimal_point >= 0);
kani::assume(a.decimal_point <= a.num_digits as i32);
kani::assume(kani::forall!(|i in (0,DecimalSeq::MAX_DIGITS)| a.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);
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);
}
}
Loading