typia
Version:
Superfast runtime validators with only one line
158 lines (157 loc) • 6.88 kB
JavaScript
import { _decimalDecompose, _decimalDivide, _decimalGcd, _decimalIntegerStep, _decimalPower, _decimalToNumber } from "./_decimal.mjs";
import { _isMultipleOf } from "./_isMultipleOf.mjs";
//#region src/internal/_randomMultiple.ts
const _randomMultiple = (props) => {
const step = props.integer ? _decimalIntegerStep(props.multipleOf) : _decimalDecompose(props.multipleOf);
if (step === null || step.coefficient <= BigInt(0)) throw new Error("The multipleOf value must be a positive finite number.");
const lower = _decimalDivide(props.minimum, step);
const upper = _decimalDivide(props.maximum, step);
if (lower === null || upper === null) throw new Error("The random number range must be finite.");
const minimum = lowerBound(lower, props.exclusiveMinimum);
const maximum = upperBound(upper, props.exclusiveMaximum);
if (minimum > maximum) throw new Error("The range does not contain a multipleOf value.");
const selected = randomBigint(minimum, maximum);
const candidates = unique([
selected,
minimum,
maximum,
clamp(BigInt(0), minimum, maximum),
clamp(BigInt(1), minimum, maximum),
clamp(BigInt(-1), minimum, maximum),
...nearby(selected, minimum, maximum)
]);
for (const coefficient of candidates) {
const value = _decimalToNumber({
coefficient: step.coefficient * coefficient,
exponent: step.exponent
});
if (isValid(props, value)) return value;
}
const aligned = findRepresentableIntegerMultiple(props);
if (aligned !== null) return aligned;
const decimalAligned = findRepresentableDecimalMultiple(props, step);
if (decimalAligned !== null) return decimalAligned;
throw new Error("The range does not contain a representable multipleOf value.");
};
const isValid = (props, value) => Number.isFinite(value) && (props.integer === false || Number.isInteger(value)) && (props.exclusiveMinimum ? value > props.minimum : value >= props.minimum) && (props.exclusiveMaximum ? value < props.maximum : value <= props.maximum) && _isMultipleOf(value, props.multipleOf);
const findRepresentableDecimalMultiple = (props, step) => {
const limit = BigInt("999999999999999");
for (let exponent = -324; exponent <= 308; ++exponent) {
const unit = {
coefficient: BigInt(1),
exponent
};
const lower = _decimalDivide(props.minimum, unit);
const upper = _decimalDivide(props.maximum, unit);
if (lower === null || upper === null) return null;
const coefficientMinimum = max(-limit, lowerBound(lower, props.exclusiveMinimum));
const coefficientMaximum = min(limit, upperBound(upper, props.exclusiveMaximum));
if (coefficientMinimum > coefficientMaximum) continue;
const coefficientStep = decimalCoefficientStep(step, exponent);
const minimum = lowerBound({
numerator: coefficientMinimum,
denominator: coefficientStep
}, false);
const maximum = upperBound({
numerator: coefficientMaximum,
denominator: coefficientStep
}, false);
if (minimum > maximum) continue;
const selected = randomBigint(minimum, maximum);
for (const quotient of unique([
selected,
minimum,
maximum,
clamp(BigInt(0), minimum, maximum),
...nearby(selected, minimum, maximum)
])) {
const value = _decimalToNumber({
coefficient: coefficientStep * quotient,
exponent
});
if (isValid(props, value)) return value;
}
}
return null;
};
const decimalCoefficientStep = (step, exponent) => {
const difference = exponent - step.exponent;
if (difference >= 0) {
const power = _decimalPower(difference);
return step.coefficient / _decimalGcd(step.coefficient, power);
}
return step.coefficient * _decimalPower(-difference);
};
const findRepresentableIntegerMultiple = (props) => {
const step = _decimalIntegerStep(props.multipleOf);
if (step === null) return null;
const unit = {
coefficient: BigInt(1),
exponent: 0
};
const lower = _decimalDivide(props.minimum, unit);
const upper = _decimalDivide(props.maximum, unit);
if (lower === null || upper === null) return null;
const minimum = lowerBound(lower, props.exclusiveMinimum);
const maximum = upperBound(upper, props.exclusiveMaximum);
if (minimum > maximum) return null;
if (minimum <= BigInt(0) && maximum >= BigInt(0)) return 0;
const candidate = minimum > BigInt(0) ? findPositiveAligned(minimum, maximum, step.coefficient) : (() => {
const magnitude = findPositiveAligned(-maximum, -minimum, step.coefficient);
return magnitude === null ? null : -magnitude;
})();
if (candidate === null) return null;
const value = Number(candidate);
return isValid(props, value) ? value : null;
};
const findPositiveAligned = (minimum, maximum, integerStep) => {
const first = bitLength(minimum) - 1;
const last = bitLength(maximum) - 1;
for (let exponent = first; exponent <= last; ++exponent) {
const bandMinimum = max(minimum, BigInt(1) << BigInt(exponent));
const bandMaximum = min(maximum, (BigInt(1) << BigInt(exponent + 1)) - BigInt(1));
const quantum = exponent <= 52 ? BigInt(1) : BigInt(1) << BigInt(exponent - 52);
const alignedStep = integerStep / _decimalGcd(integerStep, quantum) * quantum;
const lower = lowerBound({
numerator: bandMinimum,
denominator: alignedStep
}, false);
const upper = upperBound({
numerator: bandMaximum,
denominator: alignedStep
}, false);
if (lower <= upper) return randomBigint(lower, upper) * alignedStep;
}
return null;
};
const bitLength = (value) => value.toString(2).length;
const min = (x, y) => x < y ? x : y;
const max = (x, y) => x > y ? x : y;
const lowerBound = (ratio, exclusive) => {
const quotient = ratio.numerator / ratio.denominator;
const remainder = ratio.numerator % ratio.denominator;
return quotient + (remainder > BigInt(0) ? BigInt(1) : BigInt(0)) + (exclusive && remainder === BigInt(0) ? BigInt(1) : BigInt(0));
};
const upperBound = (ratio, exclusive) => {
const quotient = ratio.numerator / ratio.denominator;
const remainder = ratio.numerator % ratio.denominator;
return quotient - (remainder < BigInt(0) ? BigInt(1) : BigInt(0)) - (exclusive && remainder === BigInt(0) ? BigInt(1) : BigInt(0));
};
const randomBigint = (minimum, maximum) => {
const scale = BigInt(1) << BigInt(53);
const sample = BigInt(Math.min(Number(scale - BigInt(1)), Math.floor(Math.max(0, Math.random()) * Number(scale))));
return minimum + (maximum - minimum + BigInt(1)) * sample / scale;
};
const clamp = (value, minimum, maximum) => value < minimum ? minimum : value > maximum ? maximum : value;
const nearby = (selected, minimum, maximum) => {
const output = [];
for (let distance = BigInt(1); distance <= BigInt(32); ++distance) {
if (selected - distance >= minimum) output.push(selected - distance);
if (selected + distance <= maximum) output.push(selected + distance);
}
return output;
};
const unique = (values) => [...new Set(values)];
//#endregion
export { _randomMultiple };
//# sourceMappingURL=_randomMultiple.mjs.map