1
1
import * as fc from 'fast-check' ;
2
2
import { DoubleConstraints } from '../../../../src/arbitrary/double' ;
3
3
import { FloatConstraints } from '../../../../src/arbitrary/float' ;
4
+ import { MAX_VALUE_32 , floatToIndex } from '../../../../src/arbitrary/_internals/helpers/FloatHelpers' ;
5
+ import { doubleToIndex } from '../../../../src/arbitrary/_internals/helpers/DoubleHelpers' ;
6
+ import { substract64 } from '../../../../src/arbitrary/_internals/helpers/ArrayInt64' ;
4
7
5
8
export function float32raw ( ) : fc . Arbitrary < number > {
6
9
return fc . integer ( ) . map ( ( n32 ) => new Float32Array ( new Int32Array ( [ n32 ] ) . buffer ) [ 0 ] ) ;
@@ -17,48 +20,90 @@ export const defaultFloatRecordConstraints = {
17
20
max : float32raw ( ) ,
18
21
noDefaultInfinity : fc . boolean ( ) ,
19
22
noNaN : fc . boolean ( ) ,
23
+ minExcluded : fc . boolean ( ) ,
24
+ maxExcluded : fc . boolean ( ) ,
20
25
} ;
21
26
22
27
export const defaultDoubleRecordConstraints = {
23
28
min : float64raw ( ) ,
24
29
max : float64raw ( ) ,
25
30
noDefaultInfinity : fc . boolean ( ) ,
26
31
noNaN : fc . boolean ( ) ,
32
+ minExcluded : fc . boolean ( ) ,
33
+ maxExcluded : fc . boolean ( ) ,
27
34
} ;
28
35
29
36
type ConstraintsInternalOut = FloatConstraints & DoubleConstraints ;
30
37
type ConstraintsInternal = {
31
38
[ K in keyof ConstraintsInternalOut ] ?: fc . Arbitrary < ConstraintsInternalOut [ K ] > ;
32
39
} ;
33
- function constraintsInternal ( recordConstraints : ConstraintsInternal ) : fc . Arbitrary < ConstraintsInternalOut > {
40
+ function constraintsInternal (
41
+ recordConstraints : ConstraintsInternal ,
42
+ is32Bits : boolean
43
+ ) : fc . Arbitrary < ConstraintsInternalOut > {
34
44
return fc
35
45
. record ( recordConstraints , { withDeletedKeys : true } )
36
- . filter ( ( ct ) => ( ct . min === undefined || ! Number . isNaN ( ct . min ) ) && ( ct . max === undefined || ! Number . isNaN ( ct . max ) ) )
37
46
. filter ( ( ct ) => {
38
- if ( ! ct . noDefaultInfinity ) return true ;
39
- if ( ct . min === Number . POSITIVE_INFINITY && ct . max === undefined ) return false ;
40
- if ( ct . min === undefined && ct . max === Number . NEGATIVE_INFINITY ) return false ;
41
- return true ;
47
+ // Forbid min and max to be NaN
48
+ return ( ct . min === undefined || ! Number . isNaN ( ct . min ) ) && ( ct . max === undefined || ! Number . isNaN ( ct . max ) ) ;
42
49
} )
43
50
. map ( ( ct ) => {
51
+ // Already valid ct, no min or no max: we just return it as-is
44
52
if ( ct . min === undefined || ct . max === undefined ) return ct ;
45
53
const { min, max } = ct ;
54
+ // Already valid ct, min < max: we just return it as-is
46
55
if ( min < max ) return ct ;
56
+ // Already valid ct, min <= max with -0 and 0 correctly ordered: we just return it as-is
47
57
if ( min === max && ( min !== 0 || 1 / min <= 1 / max ) ) return ct ;
58
+ // We have to exchange min and max to get an ordered range
48
59
return { ...ct , min : max , max : min } ;
60
+ } )
61
+ . filter ( ( ct ) => {
62
+ // No issue when automatically defaulting to +/-inf
63
+ if ( ! ct . noDefaultInfinity ) return true ;
64
+ // Invalid range, cannot have min==inf if max has to default to +max_value
65
+ if ( ct . min === Number . POSITIVE_INFINITY && ct . max === undefined ) return false ;
66
+ // Invalid range, cannot have max=-inf if min has to default to -max_value
67
+ if ( ct . min === undefined && ct . max === Number . NEGATIVE_INFINITY ) return false ;
68
+ return true ;
69
+ } )
70
+ . filter ( ( ct ) => {
71
+ const defaultMax = ct . noDefaultInfinity ? ( is32Bits ? MAX_VALUE_32 : Number . MAX_VALUE ) : Number . POSITIVE_INFINITY ;
72
+ const min = ct . min !== undefined ? ct . min : - defaultMax ;
73
+ const max = ct . max !== undefined ? ct . max : defaultMax ;
74
+ // Illegal range, values cannot be "min < value <= min" or "min <= value < min" or "min < value < min"
75
+ if ( ( ct . minExcluded || ct . maxExcluded ) && min === max ) return false ;
76
+ // Always valid range given min !== max if min=-inf or max=+inf
77
+ if ( ct . max === Number . POSITIVE_INFINITY || ct . min === Number . NEGATIVE_INFINITY ) return true ;
78
+ if ( ct . minExcluded && ct . maxExcluded ) {
79
+ if ( is32Bits ) {
80
+ const minIndex = floatToIndex ( min ) ;
81
+ const maxIndex = floatToIndex ( max ) ;
82
+ const distance = maxIndex - minIndex ;
83
+ // Illegal range, no value in range if min and max are too close from each others and both excluded
84
+ if ( distance === 1 ) return false ;
85
+ } else {
86
+ const minIndex = doubleToIndex ( min ) ;
87
+ const maxIndex = doubleToIndex ( max ) ;
88
+ const distance = substract64 ( maxIndex , minIndex ) ;
89
+ // Illegal range, no value in range if min and max are too close from each others and both excluded
90
+ if ( distance . data [ 0 ] === 0 && distance . data [ 1 ] === 1 ) return false ;
91
+ }
92
+ }
93
+ return true ;
49
94
} ) ;
50
95
}
51
96
52
97
export function floatConstraints (
53
98
recordConstraints : Partial < typeof defaultFloatRecordConstraints > = defaultFloatRecordConstraints
54
99
) : fc . Arbitrary < FloatConstraints > {
55
- return constraintsInternal ( recordConstraints ) ;
100
+ return constraintsInternal ( recordConstraints , true ) ;
56
101
}
57
102
58
103
export function doubleConstraints (
59
104
recordConstraints : Partial < typeof defaultDoubleRecordConstraints > = defaultDoubleRecordConstraints
60
105
) : fc . Arbitrary < DoubleConstraints > {
61
- return constraintsInternal ( recordConstraints ) ;
106
+ return constraintsInternal ( recordConstraints , false ) ;
62
107
}
63
108
64
109
export function isStrictlySmaller ( fa : number , fb : number ) : boolean {
0 commit comments