9
9
//! algorithm can be found in "ParseNumberF64 by Simple Decimal Conversion",
10
10
//! available online: <https://nigeltao.github.io/blog/2020/parse-number-f64-simple.html>.
11
11
12
+ use safety:: ensures;
13
+
14
+ #[ cfg( kani) ]
15
+ use crate :: forall;
16
+ #[ cfg( kani) ]
17
+ use crate :: kani;
12
18
use crate :: num:: dec2flt:: common:: { ByteSlice , is_8digits} ;
13
19
14
20
/// A decimal floating-point number, represented as a sequence of decimal digits.
@@ -83,6 +89,7 @@ impl DecimalSeq {
83
89
//
84
90
// Trim is only called in `right_shift` and `left_shift`.
85
91
debug_assert ! ( self . num_digits <= Self :: MAX_DIGITS ) ;
92
+ #[ cfg_attr( kani, kani:: loop_invariant( self . num_digits <= Self :: MAX_DIGITS ) ) ]
86
93
while self . num_digits != 0 && self . digits [ self . num_digits - 1 ] == 0 {
87
94
self . num_digits -= 1 ;
88
95
}
@@ -98,6 +105,7 @@ impl DecimalSeq {
98
105
let dp = self . decimal_point as usize ;
99
106
let mut n = 0_u64 ;
100
107
108
+ #[ cfg_attr( kani, kani:: loop_invariant( n < 10u64 . pow( kani:: index as u32 ) ) ) ]
101
109
for i in 0 ..dp {
102
110
n *= 10 ;
103
111
if i < self . num_digits {
@@ -130,6 +138,14 @@ impl DecimalSeq {
130
138
let mut write_index = self . num_digits + num_new_digits;
131
139
let mut n = 0_u64 ;
132
140
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
+ ) ) ]
133
149
while read_index != 0 {
134
150
read_index -= 1 ;
135
151
write_index -= 1 ;
@@ -144,7 +160,14 @@ impl DecimalSeq {
144
160
n = quotient;
145
161
}
146
162
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
147
167
while n > 0 {
168
+ // true but hard to write proof with kani currently
169
+ #[ cfg( kani) ]
170
+ kani:: assume ( write_index > 0 ) ;
148
171
write_index -= 1 ;
149
172
let quotient = n / 10 ;
150
173
let remainder = n - ( 10 * quotient) ;
@@ -171,13 +194,15 @@ impl DecimalSeq {
171
194
let mut read_index = 0 ;
172
195
let mut write_index = 0 ;
173
196
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 ) ) ) ]
174
198
while ( n >> shift) == 0 {
175
199
if read_index < self . num_digits {
176
200
n = ( 10 * n) + self . digits [ read_index] as u64 ;
177
201
read_index += 1 ;
178
202
} else if n == 0 {
179
203
return ;
180
204
} else {
205
+ #[ cfg_attr( kani, kani:: loop_invariant( n > 0 && read_index <= self . num_digits + 64 - n. leading_zeros( ) as usize && read_index > 0 ) ) ]
181
206
while ( n >> shift) == 0 {
182
207
n *= 10 ;
183
208
read_index += 1 ;
@@ -194,13 +219,18 @@ impl DecimalSeq {
194
219
return ;
195
220
}
196
221
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
+ ) ) ]
197
226
while read_index < self . num_digits {
198
227
let new_digit = ( n >> shift) as u8 ;
199
228
n = ( 10 * ( n & mask) ) + self . digits [ read_index] as u64 ;
200
229
read_index += 1 ;
201
230
self . digits [ write_index] = new_digit;
202
231
write_index += 1 ;
203
232
}
233
+ #[ cfg_attr( kani, kani:: loop_invariant( write_index <= Self :: MAX_DIGITS ) ) ]
204
234
while n > 0 {
205
235
let new_digit = ( n >> shift) as u8 ;
206
236
n = 10 * ( n & mask) ;
@@ -297,6 +327,7 @@ pub fn parse_decimal_seq(mut s: &[u8]) -> DecimalSeq {
297
327
d
298
328
}
299
329
330
+ #[ safety:: ensures( |result| ( shift & 63 ) <= 3 || ( shift & 63 ) >= 61 || * result > 0 ) ]
300
331
fn number_of_digits_decimal_left_shift ( d : & DecimalSeq , mut shift : usize ) -> usize {
301
332
#[ rustfmt:: skip]
302
333
const TABLE : [ u16 ; 65 ] = [
@@ -363,6 +394,7 @@ fn number_of_digits_decimal_left_shift(d: &DecimalSeq, mut shift: usize) -> usiz
363
394
let pow5_b = ( 0x7FF & x_b) as usize ;
364
395
let pow5 = & TABLE_POW5 [ pow5_a..] ;
365
396
397
+ #[ cfg_attr( kani, kani:: loop_invariant( num_new_digits > 1 || shift <= 3 || shift >= 61 ) ) ]
366
398
for ( i, & p5) in pow5. iter ( ) . enumerate ( ) . take ( pow5_b - pow5_a) {
367
399
if i >= d. num_digits {
368
400
return num_new_digits - 1 ;
@@ -377,3 +409,55 @@ fn number_of_digits_decimal_left_shift(d: &DecimalSeq, mut shift: usize) -> usiz
377
409
378
410
num_new_digits
379
411
}
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