UNPKG

typia

Version:

Superfast runtime validators with only one line

158 lines (157 loc) • 6.88 kB
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