UNPKG

seeded

Version:

Seedable pseudorandom number generator (PRNG).

406 lines (354 loc) 12.7 kB
const bitsInOctet = 8; const defaultDrop = 3072; const maximumSafeBinaryLength = 52; const poolWidth = 256; const maximumSafeBinary = Math.pow(2, maximumSafeBinaryLength); const numberRange = { underflow: { integer: "max ceiling must exceed min ceiling", interval: "max must exceed min" } }; const sampleWeight = { underflow: "weights must exceed 0" }; const { underflow: { integer: integerRangeUnderflowErrorMessage, interval: intervalRangeUnderflowErrorMessage, }, } = numberRange; const { underflow: sampleWeightUnderflowErrorMessage } = sampleWeight; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; } function ceiling(n) { return Math.ceil(n); } function floor(n) { return Math.floor(n); } function floorMap(a) { return a.map(floor); } function length(x) { return x.length; } function sliceAt(n) { return (sliceable) => sliceable.slice(n); } function add(x, y) { return x + y; } function addTo(x) { return (y) => add(x, y); } function divideBy(denominator) { return (numerator) => numerator / denominator; } function increment(n) { return add(n, 1); } function multiply(x, y) { return x * y; } function negate(n) { return -n; } function raiseToPowerOf(base) { return (exponent) => Math.pow(base, exponent); } const raiseTwoToPowerOf = raiseToPowerOf(2); function remainder(divisor) { return (dividend) => dividend % divisor; } function multiplyBy(x) { return (y) => multiply(x, y); } function sum(...n) { return n.reduce((prev, current) => add(prev, current), 0); } function identityPermutation(width) { // @ts-ignore return [...Array(width).keys()]; } function atIndex(a) { return (i) => a[i]; } function map(fn) { return (a) => a.map(fn); } function split(s) { return s.split(''); } const stringsToUTF16 = map((x) => x.charCodeAt(0)); function toCharCodes(s) { return stringsToUTF16(split(s)); } function key(seedParam) { const seed = toCharCodes(seedParam), atOverflowableIndex = atIndex(seed), remainderLength = remainder(length(seed)); return { atIndex(n) { return atOverflowableIndex(remainderLength(n)); }, }; } function forEach(a) { return (fn) => a.forEach(fn); } function swapIndices(a) { return (x, y) => { const array = [...a], atIndex$1 = atIndex(array), prevX = atIndex$1(x); array[x] = atIndex$1(y); array[y] = prevX; return array; }; } function pool(state) { return { state, atIndex: atIndex(state), create: pool, forEach: forEach(state), swapIndices: swapIndices(state), }; } const remainderPoolWidth = remainder(poolWidth); function roundKey(state) { return { state, addTo: addTo(state), create(newState) { return roundKey(remainderPoolWidth(newState)); }, }; } function keySchedule(seed) { const { atIndex: atKeyIndex } = key(seed); let pool$1 = pool(identityPermutation(poolWidth)), roundKey$1 = roundKey(0); pool$1.forEach((i) => { roundKey$1 = roundKey$1.create(roundKey$1.addTo(add(atKeyIndex(i), pool$1.atIndex(i)))); pool$1 = pool$1.create(pool$1.swapIndices(i, roundKey$1.state)); }); return pool$1; } function octet({ count, drop, max: prevMax, min: prevMin, state: { i: prevI, roundKey: prevRoundKeyState, pool: prevPoolState }, }) { const max = ceiling(prevMax), min = ceiling(prevMin), toGenerate = add(count, drop), prevPool = pool(prevPoolState), dropInitial = sliceAt(drop), addToMin = addTo(min), remainderPoolWidth = remainder(poolWidth), remainderRangeDiff = remainder(add(max, negate(min))); let i = prevI, innerGenerated = [], roundKey$1 = roundKey(prevRoundKeyState), pool$1 = prevPool.create(prevPool.state); while (length(innerGenerated) < toGenerate) { i = remainderPoolWidth(increment(i)); roundKey$1 = roundKey$1.create(roundKey$1.addTo(pool$1.atIndex(i))); pool$1 = pool$1.create(pool$1.swapIndices(i, roundKey$1.state)); innerGenerated = [ ...innerGenerated, addToMin(remainderRangeDiff(pool$1.atIndex(remainderPoolWidth(add(pool$1.atIndex(i), pool$1.atIndex(roundKey$1.state)))))), ]; } return { generated: dropInitial(innerGenerated), state: { i, pool: pool$1.state, roundKey: roundKey$1.state, }, }; } const strictlyEqualsOne = strictlyEquals(1); const strictlyEqualsZero = strictlyEquals(0); function strictlyEquals(x) { return (y) => x === y; } function binaryToNumber(s) { return parseInt(s, 2); } function concatenate(a) { return a.join(''); } function toBinary(n) { return n.toString(2); } function toFixedBinaryOctet(n) { return toBinary(n).padStart(bitsInOctet, '0'); } function toFixedBinaryOctets(a) { return a.map(toFixedBinaryOctet); } const maxSafeBinaryToInterval = multiplyBy(raiseTwoToPowerOf(negate(maximumSafeBinaryLength))); const divideByBitsInOctet = divideBy(bitsInOctet); const octetsNeededForMaxSafeBinary = ceiling(divideByBitsInOctet(maximumSafeBinaryLength)); const sliceToMaxSafeBinary = (sliceAt(add(negate(maximumSafeBinaryLength), multiply(octetsNeededForMaxSafeBinary, bitsInOctet)))); function octetToInterval(octet) { return maxSafeBinaryToInterval(binaryToNumber(sliceToMaxSafeBinary(concatenate(toFixedBinaryOctets(octet))))); } function intervalCipher({ count, drop, max, min, state: prevState, }) { let generated = [], state = prevState; while (length(generated) < count) { const { generated: generatedOctet, state: octetState } = octet({ state, count: octetsNeededForMaxSafeBinary, drop: strictlyEqualsZero(length(generated)) ? drop : 0, max: poolWidth, min: 0, }), prevGenerated = add(min, multiply(octetToInterval(generatedOctet), add(max, negate(min)))), overflow = prevGenerated >= max; generated = overflow ? generated : [...generated, prevGenerated]; state = octetState; } return { generated, state }; } const interval = { cipherModule: intervalCipher, defaultMax: 1, throwIfRangeUnderflowError({ max, min, }) { if (min >= max) throw new RangeError(intervalRangeUnderflowErrorMessage); }, }; function largeInteger(_a) { var { count, max, min, state: prevState } = _a, props = __rest(_a, ["count", "max", "min", "state"]); const { state, generated: generatedInterval } = intervalCipher(Object.assign(Object.assign({}, props), { count, max, min: ceiling(min), state: prevState })), generated = floorMap(generatedInterval); return { generated, state, }; } const integer = { cipherModule(_a) { var { max, min } = _a, props = __rest(_a, ["max", "min"]); const octetRangeOverflow = add(max, negate(min)) > poolWidth, cipherModule = octetRangeOverflow ? largeInteger : octet; return cipherModule(Object.assign({ max, min }, props)); }, defaultMax: maximumSafeBinary, throwIfRangeUnderflowError({ max, min, }) { if (ceiling(min) >= ceiling(max)) throw new RangeError(integerRangeUnderflowErrorMessage); }, }; function timeSinceEpoch() { return Date.now(); } function number({ count = 1, discrete = false, drop = defaultDrop, max: prevMax, min = 0, seed = `${timeSinceEpoch()}`, state: prevState = { i: 0, pool: keySchedule(seed).state, roundKey: 0, }, } = {}) { const { cipherModule, defaultMax, throwIfRangeUnderflowError, } = discrete ? integer : interval, max = prevMax !== null && prevMax !== void 0 ? prevMax : defaultMax; throwIfRangeUnderflowError({ max, min }); const { generated, state } = cipherModule({ count, drop, max, min, state: prevState, }); function next(newCount = 1) { return number({ discrete, max, min, seed, state, count: newCount, drop: 0, }); } return { generated, next, state, }; } function head(a) { return a[0]; } function not(b) { return !b; } function isExpandedDistributionSyntax(distribution) { const distributionKeys = Object.keys(head(distribution)); return strictlyEqualsZero(length(distributionKeys)) ? false : distributionKeys.every((key) => { const strictlyEqualsKey = strictlyEquals(key); return strictlyEqualsKey('value') || strictlyEqualsKey('weight'); }); } function throwIfRangeUnderflowError(distribution) { if (distribution.some(({ weight }) => not(weight > 0))) throw new RangeError(sampleWeightUnderflowErrorMessage); } function sampleUniform(_a) { var { distribution } = _a, props = __rest(_a, ["distribution"]); const { generated, state } = number(Object.assign(Object.assign({}, props), { discrete: true, max: length(distribution) })); return { state, generated: generated.map((i) => distribution[i]), }; } function sampleWeighted(_a) { var { distribution } = _a, props = __rest(_a, ["distribution"]); throwIfRangeUnderflowError(distribution); const divideByTotalWeight = divideBy(sum(...distribution.map(({ weight }) => weight))), weightedValues = distribution.sort(({ weight: prevWeight }, { weight }) => add(weight, negate(prevWeight))), { generated, state } = number(Object.assign(Object.assign({}, props), { discrete: false })); return { state, generated: generated.map((generatedInterval) => { let selected, isValueSelected = false, cumulativeWeight = 0, i = 0; while (not(isValueSelected)) { const { value, weight } = weightedValues[i]; cumulativeWeight = add(cumulativeWeight, weight); if (generatedInterval < divideByTotalWeight(cumulativeWeight)) { selected = value; isValueSelected = true; } else i = increment(i); } return selected; }), }; } function sample(_a) { var { distribution } = _a, props = __rest(_a, ["distribution"]); let generated; let state; if (isExpandedDistributionSyntax(distribution)) { if (distribution.every(({ weight }) => strictlyEqualsOne(weight))) { const { generated: prevGenerated, state: prevState } = sampleUniform(Object.assign(Object.assign({}, props), { distribution: distribution.map(({ value }) => value) })); generated = prevGenerated; state = prevState; } else { const { generated: prevGenerated, state: prevState } = sampleWeighted(Object.assign(Object.assign({}, props), { distribution })); generated = prevGenerated; state = prevState; } } else { const { generated: prevGenerated, state: prevState } = sampleUniform(Object.assign(Object.assign({}, props), { distribution })); generated = prevGenerated; state = prevState; } function next(count) { return sample(Object.assign(Object.assign({}, props), { count, distribution, state, drop: 0 })); } return { generated, next, state }; } export { number as default, number, sample }; //# sourceMappingURL=seeded.mjs.map