/
double.ts
167 lines (159 loc) · 5.77 KB
/
double.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import type { ArrayInt64 } from './_internals/helpers/ArrayInt64';
import {
add64,
isEqual64,
isStrictlyPositive64,
isStrictlySmaller64,
substract64,
Unit64,
} from './_internals/helpers/ArrayInt64';
import { arrayInt64 } from './_internals/ArrayInt64Arbitrary';
import type { Arbitrary } from '../check/arbitrary/definition/Arbitrary';
import { doubleToIndex, indexToDouble } from './_internals/helpers/DoubleHelpers';
import {
doubleOnlyMapper,
doubleOnlyUnmapper,
refineConstraintsForDoubleOnly,
} from './_internals/helpers/DoubleOnlyHelpers';
const safeNumberIsInteger = Number.isInteger;
const safeNumberIsNaN = Number.isNaN;
const safeNegativeInfinity = Number.NEGATIVE_INFINITY;
const safePositiveInfinity = Number.POSITIVE_INFINITY;
const safeMaxValue = Number.MAX_VALUE;
const safeNaN = Number.NaN;
/**
* Constraints to be applied on {@link double}
* @remarks Since 2.6.0
* @public
*/
export interface DoubleConstraints {
/**
* Lower bound for the generated 64-bit floats (included, see minExcluded to exclude it)
* @defaultValue Number.NEGATIVE_INFINITY, -1.7976931348623157e+308 when noDefaultInfinity is true
* @remarks Since 2.8.0
*/
min?: number;
/**
* Should the lower bound (aka min) be excluded?
* Note: Excluding min=Number.NEGATIVE_INFINITY would result into having min set to -Number.MAX_VALUE.
* @defaultValue false
* @remarks Since 3.12.0
*/
minExcluded?: boolean;
/**
* Upper bound for the generated 64-bit floats (included, see maxExcluded to exclude it)
* @defaultValue Number.POSITIVE_INFINITY, 1.7976931348623157e+308 when noDefaultInfinity is true
* @remarks Since 2.8.0
*/
max?: number;
/**
* Should the upper bound (aka max) be excluded?
* Note: Excluding max=Number.POSITIVE_INFINITY would result into having max set to Number.MAX_VALUE.
* @defaultValue false
* @remarks Since 3.12.0
*/
maxExcluded?: boolean;
/**
* By default, lower and upper bounds are -infinity and +infinity.
* By setting noDefaultInfinity to true, you move those defaults to minimal and maximal finite values.
* @defaultValue false
* @remarks Since 2.8.0
*/
noDefaultInfinity?: boolean;
/**
* When set to true, no more Number.NaN can be generated.
* @defaultValue false
* @remarks Since 2.8.0
*/
noNaN?: boolean;
/**
* When set to true, Number.isInteger(value) will be false for any generated value.
* Note: -infinity and +infinity, or NaN can stil be generated except if you rejected them via another constraint.
* @defaultValue false
* @remarks Since 3.18.0
*/
noInteger?: boolean;
}
/**
* Same as {@link doubleToIndex} except it throws in case of invalid double (NaN)
*
* @internal
*/
function safeDoubleToIndex(d: number, constraintsLabel: keyof DoubleConstraints) {
if (safeNumberIsNaN(d)) {
// Number.NaN does not have any associated index in the current implementation
throw new Error('fc.double constraints.' + constraintsLabel + ' must be a 64-bit float');
}
return doubleToIndex(d);
}
/** @internal */
function unmapperDoubleToIndex(value: unknown): ArrayInt64 {
if (typeof value !== 'number') throw new Error('Unsupported type');
return doubleToIndex(value);
}
/** @internal */
function numberIsNotInteger(value: number): boolean {
return !safeNumberIsInteger(value);
}
/** @internal */
function anyDouble(constraints: Omit<DoubleConstraints, 'noInteger'>): Arbitrary<number> {
const {
noDefaultInfinity = false,
noNaN = false,
minExcluded = false,
maxExcluded = false,
min = noDefaultInfinity ? -safeMaxValue : safeNegativeInfinity,
max = noDefaultInfinity ? safeMaxValue : safePositiveInfinity,
} = constraints;
const minIndexRaw = safeDoubleToIndex(min, 'min');
const minIndex = minExcluded ? add64(minIndexRaw, Unit64) : minIndexRaw;
const maxIndexRaw = safeDoubleToIndex(max, 'max');
const maxIndex = maxExcluded ? substract64(maxIndexRaw, Unit64) : maxIndexRaw;
if (isStrictlySmaller64(maxIndex, minIndex)) {
// In other words: minIndex > maxIndex
// Comparing min and max might be problematic in case min=+0 and max=-0
// For that reason, we prefer to compare computed index to be safer
throw new Error('fc.double constraints.min must be smaller or equal to constraints.max');
}
if (noNaN) {
return arrayInt64(minIndex, maxIndex).map(indexToDouble, unmapperDoubleToIndex);
}
// In case maxIndex > 0 or in other words max > 0,
// values will be [min, ..., +0, ..., max, NaN]
// or [min, ..., max, NaN] if min > +0
// Otherwise,
// values will be [NaN, min, ..., max] with max <= +0
const positiveMaxIdx = isStrictlyPositive64(maxIndex);
const minIndexWithNaN = positiveMaxIdx ? minIndex : substract64(minIndex, Unit64);
const maxIndexWithNaN = positiveMaxIdx ? add64(maxIndex, Unit64) : maxIndex;
return arrayInt64(minIndexWithNaN, maxIndexWithNaN).map(
(index) => {
if (isStrictlySmaller64(maxIndex, index) || isStrictlySmaller64(index, minIndex)) return safeNaN;
else return indexToDouble(index);
},
(value) => {
if (typeof value !== 'number') throw new Error('Unsupported type');
if (safeNumberIsNaN(value)) return !isEqual64(maxIndex, maxIndexWithNaN) ? maxIndexWithNaN : minIndexWithNaN;
return doubleToIndex(value);
},
);
}
/**
* For 64-bit floating point numbers:
* - sign: 1 bit
* - significand: 52 bits
* - exponent: 11 bits
*
* @param constraints - Constraints to apply when building instances (since 2.8.0)
*
* @remarks Since 0.0.6
* @public
*/
export function double(constraints: DoubleConstraints = {}): Arbitrary<number> {
if (!constraints.noInteger) {
return anyDouble(constraints);
}
return anyDouble(refineConstraintsForDoubleOnly(constraints))
.map(doubleOnlyMapper, doubleOnlyUnmapper)
.filter(numberIsNotInteger);
}