99//! algorithm can be found in "ParseNumberF64 by Simple Decimal Conversion",
1010//! available online: <https://nigeltao.github.io/blog/2020/parse-number-f64-simple.html>.
1111
12+ use safety:: ensures;
13+
14+ #[ cfg( kani) ]
15+ use crate :: forall;
16+ #[ cfg( kani) ]
17+ use crate :: kani;
1218use crate :: num:: dec2flt:: common:: { ByteSlice , is_8digits} ;
1319
1420/// A decimal floating-point number, represented as a sequence of decimal digits.
@@ -83,6 +89,7 @@ impl DecimalSeq {
8389 //
8490 // Trim is only called in `right_shift` and `left_shift`.
8591 debug_assert ! ( self . num_digits <= Self :: MAX_DIGITS ) ;
92+ #[ cfg_attr( kani, kani:: loop_invariant( self . num_digits <= Self :: MAX_DIGITS ) ) ]
8693 while self . num_digits != 0 && self . digits [ self . num_digits - 1 ] == 0 {
8794 self . num_digits -= 1 ;
8895 }
@@ -98,6 +105,7 @@ impl DecimalSeq {
98105 let dp = self . decimal_point as usize ;
99106 let mut n = 0_u64 ;
100107
108+ #[ cfg_attr( kani, kani:: loop_invariant( n < 10u64 . pow( kani:: index as u32 ) ) ) ]
101109 for i in 0 ..dp {
102110 n *= 10 ;
103111 if i < self . num_digits {
@@ -130,6 +138,14 @@ impl DecimalSeq {
130138 let mut write_index = self . num_digits + num_new_digits;
131139 let mut n = 0_u64 ;
132140
141+ #[ cfg_attr( kani, kani:: loop_invariant( read_index <= Self :: MAX_DIGITS &&
142+ write_index == read_index + num_new_digits &&
143+ n < 10u64 << ( shift - 1 ) &&
144+ ( n == 0 || ( shift & 63 ) <= 3 || ( shift & 63 ) >= 61 || write_index > 0 ) &&
145+ self . num_digits <= Self :: MAX_DIGITS &&
146+ self . decimal_point <= self . num_digits as i32 &&
147+ forall!( |i in ( 0 , DecimalSeq :: MAX_DIGITS ) | self . digits[ i] <= 9 )
148+ ) ) ]
133149 while read_index != 0 {
134150 read_index -= 1 ;
135151 write_index -= 1 ;
@@ -144,7 +160,14 @@ impl DecimalSeq {
144160 n = quotient;
145161 }
146162
163+ #[ cfg_attr( kani, kani:: loop_invariant( self . num_digits <= Self :: MAX_DIGITS && self . decimal_point <= self . num_digits as i32 ) ) ]
164+ // TODO: should add invariant along the lines of
165+ // (n == 0 || write_index > 0) && (prev(n) as usize + 9) / 10 <= prev(write_index))]
166+ // to avoid the below kani::assume
147167 while n > 0 {
168+ // true but hard to write proof with kani currently
169+ #[ cfg( kani) ]
170+ kani:: assume ( write_index > 0 ) ;
148171 write_index -= 1 ;
149172 let quotient = n / 10 ;
150173 let remainder = n - ( 10 * quotient) ;
@@ -171,13 +194,15 @@ impl DecimalSeq {
171194 let mut read_index = 0 ;
172195 let mut write_index = 0 ;
173196 let mut n = 0_u64 ;
197+ #[ cfg_attr( kani, kani:: loop_invariant( n == 0 || ( read_index > 0 && read_index <= self . num_digits + 64 - n. leading_zeros( ) as usize ) ) ) ]
174198 while ( n >> shift) == 0 {
175199 if read_index < self . num_digits {
176200 n = ( 10 * n) + self . digits [ read_index] as u64 ;
177201 read_index += 1 ;
178202 } else if n == 0 {
179203 return ;
180204 } else {
205+ #[ cfg_attr( kani, kani:: loop_invariant( n > 0 && read_index <= self . num_digits + 64 - n. leading_zeros( ) as usize && read_index > 0 ) ) ]
181206 while ( n >> shift) == 0 {
182207 n *= 10 ;
183208 read_index += 1 ;
@@ -194,13 +219,18 @@ impl DecimalSeq {
194219 return ;
195220 }
196221 let mask = ( 1_u64 << shift) - 1 ;
222+ #[ cfg_attr( kani, kani:: loop_invariant( self . num_digits <= Self :: MAX_DIGITS &&
223+ write_index < read_index &&
224+ write_index < Self :: MAX_DIGITS - self . num_digits. saturating_sub( read_index)
225+ ) ) ]
197226 while read_index < self . num_digits {
198227 let new_digit = ( n >> shift) as u8 ;
199228 n = ( 10 * ( n & mask) ) + self . digits [ read_index] as u64 ;
200229 read_index += 1 ;
201230 self . digits [ write_index] = new_digit;
202231 write_index += 1 ;
203232 }
233+ #[ cfg_attr( kani, kani:: loop_invariant( write_index <= Self :: MAX_DIGITS ) ) ]
204234 while n > 0 {
205235 let new_digit = ( n >> shift) as u8 ;
206236 n = 10 * ( n & mask) ;
@@ -297,6 +327,7 @@ pub fn parse_decimal_seq(mut s: &[u8]) -> DecimalSeq {
297327 d
298328}
299329
330+ #[ safety:: ensures( |result| ( shift & 63 ) <= 3 || ( shift & 63 ) >= 61 || * result > 0 ) ]
300331fn number_of_digits_decimal_left_shift ( d : & DecimalSeq , mut shift : usize ) -> usize {
301332 #[ rustfmt:: skip]
302333 const TABLE : [ u16 ; 65 ] = [
@@ -363,6 +394,7 @@ fn number_of_digits_decimal_left_shift(d: &DecimalSeq, mut shift: usize) -> usiz
363394 let pow5_b = ( 0x7FF & x_b) as usize ;
364395 let pow5 = & TABLE_POW5 [ pow5_a..] ;
365396
397+ #[ cfg_attr( kani, kani:: loop_invariant( num_new_digits > 1 || shift <= 3 || shift >= 61 ) ) ]
366398 for ( i, & p5) in pow5. iter ( ) . enumerate ( ) . take ( pow5_b - pow5_a) {
367399 if i >= d. num_digits {
368400 return num_new_digits - 1 ;
@@ -377,3 +409,55 @@ fn number_of_digits_decimal_left_shift(d: &DecimalSeq, mut shift: usize) -> usiz
377409
378410 num_new_digits
379411}
412+
413+ #[ cfg( kani) ]
414+ #[ unstable( feature = "kani" , issue = "none" ) ]
415+ pub mod decimal_seq_verify {
416+ use super :: * ;
417+
418+ impl kani:: Arbitrary for DecimalSeq {
419+ fn any ( ) -> DecimalSeq {
420+ let mut ret = DecimalSeq {
421+ num_digits : kani:: any_where ( |x| * x <= DecimalSeq :: MAX_DIGITS ) ,
422+ decimal_point : kani:: any_where ( |x| * x >= 0 ) ,
423+ truncated : kani:: any ( ) ,
424+ digits : kani:: any ( ) ,
425+ } ;
426+ kani:: assume ( ret. decimal_point <= ret. num_digits as i32 ) ;
427+ kani:: assume ( forall ! ( |i in ( 0 , DecimalSeq :: MAX_DIGITS ) | ret. digits[ i] <= 9 ) ) ;
428+ ret
429+ }
430+ }
431+
432+ #[ kani:: proof]
433+ fn check_round ( ) {
434+ let mut a: DecimalSeq = kani:: any ( ) ;
435+ a. round ( ) ;
436+ }
437+
438+ #[ kani:: proof]
439+ fn check_number_of_digits_decimal_left_shift ( ) {
440+ let mut a: DecimalSeq = kani:: any ( ) ;
441+ let shift: usize = kani:: any_where ( |x| * x > 0 && * x <= 60 ) ;
442+ let n = number_of_digits_decimal_left_shift ( & a, shift) ;
443+ // 19 is the greatest number x such that 10u64^x does not overflow
444+ // It is also TABLE.max << 11
445+ assert ! ( n <= 19 ) ;
446+ assert ! ( n == 19 || 1u64 << shift < 10u64 . pow( n as u32 + 1 ) )
447+ }
448+
449+ #[ kani:: proof]
450+ fn check_right_shift ( ) {
451+ let mut a: DecimalSeq = kani:: any ( ) ;
452+ //This function is called in parse_long_mantissa function (slow.rs), in which the maximum of shift is 60
453+ let shift: usize = kani:: any_where ( |x| * x > 0 && * x <= 60 ) ;
454+ a. right_shift ( shift) ;
455+ }
456+
457+ #[ kani:: proof]
458+ fn check_left_shift ( ) {
459+ let mut a: DecimalSeq = kani:: any ( ) ;
460+ let shift: usize = kani:: any_where ( |x| * x > 0 && * x <= 60 ) ;
461+ a. left_shift ( shift) ;
462+ }
463+ }
0 commit comments