├── package.esm-template.json ├── .prettierignore ├── .prettierrc ├── .npmignore ├── .github ├── FUNDING.yml ├── actions │ └── install-deps-with-current-fc │ │ ├── check-hash.cjs │ │ └── action.yml └── workflows │ ├── compare.yml │ ├── benchmark.yml │ └── build-status.yml ├── src ├── pure-rand.ts ├── distribution │ ├── UnsafeSkipN.ts │ ├── SkipN.ts │ ├── UnsafeGenerateN.ts │ ├── GenerateN.ts │ ├── internals │ │ ├── UnsafeUniformIntDistributionInternal.ts │ │ ├── UnsafeUniformArrayIntDistributionInternal.ts │ │ ├── ArrayInt64.ts │ │ └── ArrayInt.ts │ ├── UnsafeUniformArrayIntDistribution.ts │ ├── UniformIntDistribution.ts │ ├── UniformBigIntDistribution.ts │ ├── UniformArrayIntDistribution.ts │ ├── UnsafeUniformBigIntDistribution.ts │ └── UnsafeUniformIntDistribution.ts ├── types │ ├── Distribution.ts │ └── RandomGenerator.ts ├── pure-rand-default.ts └── generator │ ├── LinearCongruential.ts │ ├── XoroShiro.ts │ ├── XorShift.ts │ └── MersenneTwister.ts ├── .yarnrc.yml ├── tsconfig.declaration.json ├── .gitignore ├── jest.config.js ├── tsconfig.json ├── renovate.json ├── LICENSE ├── test ├── legacy │ └── main.js ├── __test-helpers__ │ └── mocked.ts └── unit │ ├── distribution │ ├── UniformBigIntDistribution.spec.ts │ ├── UniformBigIntDistribution.noreg.spec.ts │ ├── UniformIntDistribution.noreg.spec.ts │ ├── internals │ │ ├── UnsafeUniformIntDistributionInternal.spec.ts │ │ ├── ArrayInt.spec.ts │ │ ├── ArrayInt64.spec.ts │ │ └── UnsafeUniformArrayIntDistributionInternal.spec.ts │ ├── __snapshots__ │ │ ├── UniformIntDistribution.noreg.spec.ts.snap │ │ ├── UniformBigIntDistribution.noreg.spec.ts.snap │ │ └── UniformArrayIntDistribution.noreg.spec.ts.snap │ ├── UniformArrayIntDistribution.noreg.spec.ts │ ├── UniformArrayIntDistribution.spec.ts │ └── UniformIntDistribution.spec.ts │ └── generator │ ├── XoroShiro.spec.ts │ ├── XorShift.spec.ts │ ├── RandomGenerator.properties.ts │ ├── LinearCongruencial.spec.ts │ └── MersenneTwister.spec.ts ├── CHANGELOG.md ├── CHANGELOG_3.X.md ├── postbuild └── main.mjs ├── CHANGELOG_4.X.md ├── CHANGELOG_5.X.md ├── package.json ├── assets └── logo.svg ├── perf ├── compare.cjs └── benchmark.cjs ├── CHANGELOG_6.X.md └── README.md /package.esm-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | lib-*/ 3 | coverage/ 4 | .yarn/ 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | coverage/ 3 | example/ 4 | perfs/ 5 | src/ 6 | test/ 7 | buildTypes.js -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | open_collective: fast-check 4 | github: [dubzzz] 5 | -------------------------------------------------------------------------------- /src/pure-rand.ts: -------------------------------------------------------------------------------- 1 | import * as prand from './pure-rand-default'; 2 | export default prand; 3 | 4 | export * from './pure-rand-default'; 5 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableGlobalCache: true 2 | enableScripts: false 3 | nodeLinker: node-modules 4 | enableTransparentWorkspaces: false 5 | yarnPath: .yarn/releases/yarn-4.12.0.cjs 6 | -------------------------------------------------------------------------------- /tsconfig.declaration.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "outDir": "lib/types" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/distribution/UnsafeSkipN.ts: -------------------------------------------------------------------------------- 1 | import type { RandomGenerator } from '../types/RandomGenerator'; 2 | 3 | export function unsafeSkipN(rng: RandomGenerator, num: number): void { 4 | for (let idx = 0; idx != num; ++idx) { 5 | rng.unsafeNext(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/distribution/SkipN.ts: -------------------------------------------------------------------------------- 1 | import type { RandomGenerator } from '../types/RandomGenerator'; 2 | import { unsafeSkipN } from './UnsafeSkipN'; 3 | 4 | export function skipN(rng: RandomGenerator, num: number): RandomGenerator { 5 | const nextRng = rng.clone(); 6 | unsafeSkipN(nextRng, num); 7 | return nextRng; 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | lib/ 3 | lib-*/ 4 | node_modules/ 5 | .nyc_output/ 6 | out.txt 7 | v8.log 8 | v8.out 9 | 10 | # https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored 11 | .pnp.* 12 | .yarn/* 13 | !.yarn/patches 14 | !.yarn/plugins 15 | !.yarn/releases 16 | !.yarn/sdks 17 | !.yarn/versions 18 | -------------------------------------------------------------------------------- /src/distribution/UnsafeGenerateN.ts: -------------------------------------------------------------------------------- 1 | import type { RandomGenerator } from '../types/RandomGenerator'; 2 | 3 | export function unsafeGenerateN(rng: RandomGenerator, num: number): number[] { 4 | const out: number[] = []; 5 | for (let idx = 0; idx != num; ++idx) { 6 | out.push(rng.unsafeNext()); 7 | } 8 | return out; 9 | } 10 | -------------------------------------------------------------------------------- /src/distribution/GenerateN.ts: -------------------------------------------------------------------------------- 1 | import type { RandomGenerator } from '../types/RandomGenerator'; 2 | import { unsafeGenerateN } from './UnsafeGenerateN'; 3 | 4 | export function generateN(rng: RandomGenerator, num: number): [number[], RandomGenerator] { 5 | const nextRng = rng.clone(); 6 | const out = unsafeGenerateN(nextRng, num); 7 | return [out, nextRng]; 8 | } 9 | -------------------------------------------------------------------------------- /src/types/Distribution.ts: -------------------------------------------------------------------------------- 1 | import type { RandomGenerator } from './RandomGenerator'; 2 | 3 | /** 4 | * Generate random value based on a given RandomGenerator. 5 | * Return the generated value and an offsetted version of the RandomGenerator (never alters the source rng). 6 | * @public 7 | */ 8 | export type Distribution = (rng: RandomGenerator) => [T, RandomGenerator]; 9 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // Shared Jest configuration 2 | // Useful for Jest plugin of vscode 3 | 4 | module.exports = { 5 | moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'], 6 | globals: { 7 | 'ts-jest': { 8 | tsconfig: 'tsconfig.json', 9 | }, 10 | }, 11 | testMatch: ['/test/**/*.spec.ts'], 12 | setupFiles: [], 13 | setupFilesAfterEnv: [], 14 | preset: 'ts-jest', 15 | }; 16 | -------------------------------------------------------------------------------- /.github/actions/install-deps-with-current-fc/check-hash.cjs: -------------------------------------------------------------------------------- 1 | const process = require('process'); 2 | const { __commitHash } = require('pure-rand'); 3 | 4 | const expectedCommitHash = process.env.GITHUB_SHA; 5 | if (!expectedCommitHash) { 6 | console.error('No GITHUB_SHA specified'); 7 | process.exit(1); 8 | } 9 | if (expectedCommitHash !== __commitHash) { 10 | console.error('Expected: ' + expectedCommitHash + ', got: ' + __commitHash); 11 | process.exit(2); 12 | } 13 | console.log('✔️ Referencing the correct version of pure-rand'); 14 | console.log('✔️ Commit hash matches expected one: ' + expectedCommitHash); 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": false, 4 | "sourceMap": false, 5 | "alwaysStrict": true, 6 | "noImplicitAny": true, 7 | "noImplicitThis": true, 8 | "removeComments": true, 9 | "preserveConstEnums": true, 10 | "strictNullChecks": true, 11 | "downlevelIteration": true, 12 | "moduleResolution": "node", 13 | "module": "commonjs", 14 | "target": "es5", 15 | "ignoreDeprecations": "5.0", 16 | "lib": ["es6", "esnext.bigint"], 17 | "outDir": "lib/" 18 | }, 19 | "include": ["src/**/*"], 20 | "exclude": ["node_modules", "**/*.spec.ts"] 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/compare.yml: -------------------------------------------------------------------------------- 1 | name: Compare 2 | on: workflow_dispatch 3 | jobs: 4 | compare: 5 | name: 'Compare' 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v5.0.1 9 | - name: Using Node v22.x 10 | uses: actions/setup-node@v6.0.0 11 | with: 12 | node-version: '22.21.1' 13 | - name: Install dependencies 14 | run: yarn install --immutable 15 | - name: Install extra libraries 16 | run: yarn add chance@1.1.10 @faker-js/faker@7.6.0 random-js@2.1.0 seedrandom@3.0.5 --exact --dev 17 | - name: Build package 18 | run: yarn build:prod 19 | - name: Env Info 20 | run: npx envinfo 21 | - name: Benchmark 22 | run: node perf/compare.cjs 23 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:recommended", ":dependencyDashboard"], 4 | "labels": ["dependencies"], 5 | "commitMessagePrefix": "⬆️ ", 6 | "packageRules": [ 7 | { 8 | "matchFileNames": ["**/package.json"], 9 | "matchDepTypes": ["dependencies", "peerDependencies", "optionalDependencies"], 10 | "rangeStrategy": "update-lockfile" 11 | }, 12 | { 13 | "matchFileNames": ["**/package.json"], 14 | "matchDepTypes": ["devDependencies"], 15 | "rangeStrategy": "bump" 16 | }, 17 | { 18 | "matchFileNames": ["**/!(package.json)"], 19 | "rangeStrategy": "bump" 20 | } 21 | ], 22 | "postUpdateOptions": ["yarnDedupeHighest"] 23 | } 24 | -------------------------------------------------------------------------------- /src/distribution/internals/UnsafeUniformIntDistributionInternal.ts: -------------------------------------------------------------------------------- 1 | import type { RandomGenerator } from '../../types/RandomGenerator'; 2 | 3 | /** 4 | * Uniformly generate number in range [0 ; rangeSize[ 5 | * With rangeSize <= 0x100000000 6 | * @internal 7 | */ 8 | export function unsafeUniformIntDistributionInternal(rangeSize: number, rng: RandomGenerator): number { 9 | // Range provided by the RandomGenerator is large enough, 10 | // given rangeSize <= 0x100000000 and RandomGenerator is uniform on 0x100000000 values 11 | const MaxAllowed = rangeSize > 2 ? ~~(0x100000000 / rangeSize) * rangeSize : 0x100000000; 12 | let deltaV = rng.unsafeNext() + 0x80000000; 13 | while (deltaV >= MaxAllowed) { 14 | deltaV = rng.unsafeNext() + 0x80000000; 15 | } 16 | return deltaV % rangeSize; 17 | } 18 | -------------------------------------------------------------------------------- /src/types/RandomGenerator.ts: -------------------------------------------------------------------------------- 1 | export interface RandomGenerator { 2 | /** Produce a fully independent clone of the current instance */ 3 | clone(): RandomGenerator; 4 | /** 5 | * Generate next random value along with the next generator (does not impact current instance). 6 | * Values uniform in range -0x8000_0000 (included) to 0x7fff_ffff (included) 7 | */ 8 | next(): [number, RandomGenerator]; 9 | /** Compute the jumped generator (does not impact current instance) */ 10 | jump?(): RandomGenerator; 11 | /** 12 | * Generate next value BUT alters current generator. 13 | * Values uniform in range -0x8000_0000 (included) to 0x7fff_ffff (included) 14 | */ 15 | unsafeNext(): number; 16 | /** Jump current generator */ 17 | unsafeJump?(): void; 18 | /** Access to the internal state of a RandomGenerator in a read-only fashion */ 19 | getState(): readonly number[]; 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nicolas DUBIEN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: Benchmark 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | benchType: 6 | type: choice 7 | description: 'Type (self: all generators, alone: all algorithms)' 8 | default: 'self' 9 | options: 10 | - self 11 | - alone 12 | required: true 13 | numIterations: 14 | description: 'Number of iterations' 15 | default: '1000' 16 | required: true 17 | numPerRun: 18 | description: 'Number per run' 19 | default: '100' 20 | required: true 21 | jobs: 22 | benchmark: 23 | name: 'Benchmark' 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v5.0.1 27 | - name: Using Node v22.x 28 | uses: actions/setup-node@v6.0.0 29 | with: 30 | node-version: '22.21.1' 31 | - name: Install dependencies 32 | run: yarn install --immutable 33 | - name: Build benchmark package 34 | run: yarn build:bench:new 35 | - name: Benchmark 36 | run: yarn bench ${{github.event.inputs.benchType}} ${{github.event.inputs.numIterations}} ${{github.event.inputs.numPerRun}} 37 | -------------------------------------------------------------------------------- /test/legacy/main.js: -------------------------------------------------------------------------------- 1 | var prand = require('../../lib/pure-rand'); 2 | 3 | // mersenne 4 | 5 | var seed = 42; 6 | var gMersenne = prand.mersenne(seed); 7 | for (var idx = 0; idx !== 1000; ++idx) gMersenne = gMersenne.next()[1]; // 1k loops to force a twist call 8 | // gMersenne = gMersenne.jump(); // not implemented 9 | 10 | // congruential32 11 | 12 | var gc = prand.congruential32(seed); 13 | gc = gc.next()[1]; 14 | gc = gc.next()[1]; 15 | // gc = gc.jump(); // not implemented 16 | 17 | // xorshift128plus 18 | 19 | var gXor = prand.xorshift128plus(seed); 20 | gXor = gXor.next()[1]; 21 | gXor = gXor.next()[1]; 22 | gXor = gXor.jump(); 23 | 24 | // xoroshiro128plus 25 | 26 | var gXoro = prand.xoroshiro128plus(seed); 27 | gXoro = gXoro.next()[1]; 28 | gXoro = gXoro.next()[1]; 29 | gXoro = gXoro.jump(); 30 | 31 | // uniform distribution 32 | 33 | gc = prand.uniformIntDistribution(0, 9)(gc)[1]; 34 | gc = prand.uniformIntDistribution(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER)(gc)[1]; 35 | 36 | // uniform distribution via array int 37 | 38 | var from = { sign: -1, data: [5, 21] }; 39 | var to = { sign: 1, data: [999, 999] }; 40 | gc = prand.uniformArrayIntDistribution(from, to)(gc)[1]; 41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 7.X 2 | 3 | ## 7.0.1 4 | 5 | ### Fixes 6 | 7 | - [c24bc93](https://github.com/dubzzz/pure-rand/commit/c24bc93) 🐛 Properly define exports in package.json (#758) 8 | 9 | ## 7.0.0 10 | 11 | ### Breaking Changes 12 | 13 | - [2c94832](https://github.com/dubzzz/pure-rand/commit/2c94832) 🏷️ Move to "import type" when feasible (#736) 14 | - [3741a63](https://github.com/dubzzz/pure-rand/commit/3741a63) 🏷️ Mark `getState` as compulsory on `RandomGenerator` (#733) 15 | 16 | ### Features 17 | 18 | - [228c73d](https://github.com/dubzzz/pure-rand/commit/228c73d) ⚡️ Faster uniform distributions on bigint (#757) 19 | - [86869a1](https://github.com/dubzzz/pure-rand/commit/86869a1) ✨ Expose generators and distributions (#735) 20 | 21 | ### Fixes 22 | 23 | - [680a672](https://github.com/dubzzz/pure-rand/commit/680a672) 🚚 Do not export mersenne as default (#738) 24 | - [e1758c0](https://github.com/dubzzz/pure-rand/commit/e1758c0) 🚚 Split ArrayInt into two files (#737) 25 | - [0c356cf](https://github.com/dubzzz/pure-rand/commit/0c356cf) 🚚 Moving files around (#734) 26 | - [6d9b7b4](https://github.com/dubzzz/pure-rand/commit/6d9b7b4) 📝 Document generation of float/double (#715) 27 | -------------------------------------------------------------------------------- /src/distribution/UnsafeUniformArrayIntDistribution.ts: -------------------------------------------------------------------------------- 1 | import type { RandomGenerator } from '../types/RandomGenerator'; 2 | import type { ArrayInt } from './internals/ArrayInt'; 3 | import { 4 | addArrayIntToNew, 5 | addOneToPositiveArrayInt, 6 | substractArrayIntToNew, 7 | trimArrayIntInplace, 8 | } from './internals/ArrayInt'; 9 | import { unsafeUniformArrayIntDistributionInternal } from './internals/UnsafeUniformArrayIntDistributionInternal'; 10 | 11 | /** 12 | * Uniformly generate random ArrayInt values between `from` (included) and `to` (included) 13 | * 14 | * @param from - Lower bound of the range (included) 15 | * @param to - Upper bound of the range (included) 16 | * @param rng - Instance of RandomGenerator to extract random values from 17 | * 18 | * @public 19 | */ 20 | export function unsafeUniformArrayIntDistribution(from: ArrayInt, to: ArrayInt, rng: RandomGenerator): ArrayInt { 21 | const rangeSize = trimArrayIntInplace(addOneToPositiveArrayInt(substractArrayIntToNew(to, from))); 22 | const emptyArrayIntData = rangeSize.data.slice(0); 23 | const g = unsafeUniformArrayIntDistributionInternal(emptyArrayIntData, rangeSize.data, rng); 24 | return trimArrayIntInplace(addArrayIntToNew({ sign: 1, data: g }, from)); 25 | } 26 | -------------------------------------------------------------------------------- /.github/actions/install-deps-with-current-fc/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Install dependencies but use current build for pure-rand' 2 | description: 'Install dependencies but use current build for pure-rand' 3 | inputs: 4 | path: 5 | description: 'Directory path' 6 | required: true 7 | runs: 8 | using: 'composite' 9 | steps: 10 | - run: | 11 | PROJECT_ROOT_PATH="$(pwd)" 12 | cd "${{inputs.path}}" 13 | echo "ℹ️ Clean yarn cache entry for pure-rand (if any)..." 14 | yarn cache clean pure-rand 15 | echo "ℹ️ Install dependencies..." 16 | yarn --frozen-lockfile 17 | echo "ℹ️ Clean yarn cache entry for pure-rand..." 18 | yarn cache clean pure-rand 19 | echo "ℹ️ Install current build for pure-rand..." 20 | yarn add "file:$PROJECT_ROOT_PATH/pure-rand.tgz" 21 | echo "ℹ️ Clean yarn cache entry for pure-rand..." 22 | yarn cache clean pure-rand 23 | echo "ℹ️ Revert local changes..." 24 | git checkout -- package.json 25 | git checkout -- yarn.lock 26 | shell: bash 27 | - run: | 28 | echo "ℹ️ Copy script..." 29 | cp .github/actions/install-deps-with-current-fc/check-hash.cjs "${{inputs.path}}/check-hash-$GITHUB_SHA.cjs" 30 | echo "ℹ️ Check version of pure-rand..." 31 | cd "${{inputs.path}}" 32 | node check-hash-*.cjs 33 | echo "ℹ️ Remove script..." 34 | rm "check-hash-$GITHUB_SHA.cjs" 35 | shell: bash 36 | -------------------------------------------------------------------------------- /src/distribution/UniformIntDistribution.ts: -------------------------------------------------------------------------------- 1 | import type { Distribution } from '../types/Distribution'; 2 | import type { RandomGenerator } from '../types/RandomGenerator'; 3 | import { unsafeUniformIntDistribution } from './UnsafeUniformIntDistribution'; 4 | 5 | /** 6 | * Uniformly generate random integer values between `from` (included) and `to` (included) 7 | * 8 | * @param from - Lower bound of the range (included) 9 | * @param to - Upper bound of the range (included) 10 | * 11 | * @public 12 | */ 13 | function uniformIntDistribution(from: number, to: number): Distribution; 14 | /** 15 | * Uniformly generate random integer values between `from` (included) and `to` (included) 16 | * 17 | * @param from - Lower bound of the range (included) 18 | * @param to - Upper bound of the range (included) 19 | * @param rng - Instance of RandomGenerator to extract random values from 20 | * 21 | * @public 22 | */ 23 | function uniformIntDistribution(from: number, to: number, rng: RandomGenerator): [number, RandomGenerator]; 24 | function uniformIntDistribution(from: number, to: number, rng?: RandomGenerator) { 25 | if (rng !== undefined) { 26 | const nextRng = rng.clone(); 27 | return [unsafeUniformIntDistribution(from, to, nextRng), nextRng]; 28 | } 29 | return function (rng: RandomGenerator) { 30 | const nextRng = rng.clone(); 31 | return [unsafeUniformIntDistribution(from, to, nextRng), nextRng]; 32 | }; 33 | } 34 | 35 | export { uniformIntDistribution }; 36 | -------------------------------------------------------------------------------- /test/__test-helpers__/mocked.ts: -------------------------------------------------------------------------------- 1 | // Copied from ts-jest/dist/utils/testing 2 | 3 | type MockableFunction = (...args: any[]) => any; 4 | type ArgumentsOf = T extends (...args: infer A) => any ? A : never; 5 | type ConstructorArgumentsOf = T extends new (...args: infer A) => any ? A : never; 6 | 7 | export interface MockWithArgs extends jest.MockInstance, ArgumentsOf> { 8 | new (...args: ConstructorArgumentsOf): T; 9 | (...args: ArgumentsOf): ReturnType; 10 | } 11 | 12 | type MethodKeysOf = { 13 | [K in keyof T]: T[K] extends MockableFunction ? K : never; 14 | }[keyof T]; 15 | type PropertyKeysOf = { 16 | [K in keyof T]: T[K] extends MockableFunction ? never : K; 17 | }[keyof T]; 18 | type MaybeMockedConstructor = T extends new (...args: any[]) => infer R 19 | ? jest.MockInstance> 20 | : T; 21 | type MockedFunction = MockWithArgs & { 22 | [K in keyof T]: T[K]; 23 | }; 24 | type MockedObject = MaybeMockedConstructor & { 25 | [K in MethodKeysOf]: T[K] extends MockableFunction ? MockedFunction : T[K]; 26 | } & { 27 | [K in PropertyKeysOf]: T[K]; 28 | }; 29 | 30 | // eslint-disable-next-line @typescript-eslint/ban-types 31 | export type MaybeMocked = T extends MockableFunction ? MockedFunction : T extends object ? MockedObject : T; 32 | 33 | export function mocked(item: T): MaybeMocked { 34 | return item as MaybeMocked; 35 | } 36 | -------------------------------------------------------------------------------- /src/distribution/UniformBigIntDistribution.ts: -------------------------------------------------------------------------------- 1 | import type { Distribution } from '../types/Distribution'; 2 | import type { RandomGenerator } from '../types/RandomGenerator'; 3 | import { unsafeUniformBigIntDistribution } from './UnsafeUniformBigIntDistribution'; 4 | 5 | /** 6 | * Uniformly generate random bigint values between `from` (included) and `to` (included) 7 | * 8 | * @param from - Lower bound of the range (included) 9 | * @param to - Upper bound of the range (included) 10 | * 11 | * @public 12 | */ 13 | function uniformBigIntDistribution(from: bigint, to: bigint): Distribution; 14 | /** 15 | * Uniformly generate random bigint values between `from` (included) and `to` (included) 16 | * 17 | * @param from - Lower bound of the range (included) 18 | * @param to - Upper bound of the range (included) 19 | * @param rng - Instance of RandomGenerator to extract random values from 20 | * 21 | * @public 22 | */ 23 | function uniformBigIntDistribution(from: bigint, to: bigint, rng: RandomGenerator): [bigint, RandomGenerator]; 24 | function uniformBigIntDistribution(from: bigint, to: bigint, rng?: RandomGenerator) { 25 | if (rng !== undefined) { 26 | const nextRng = rng.clone(); 27 | return [unsafeUniformBigIntDistribution(from, to, nextRng), nextRng]; 28 | } 29 | return function (rng: RandomGenerator) { 30 | const nextRng = rng.clone(); 31 | return [unsafeUniformBigIntDistribution(from, to, nextRng), nextRng]; 32 | }; 33 | } 34 | 35 | export { uniformBigIntDistribution }; 36 | -------------------------------------------------------------------------------- /src/distribution/UniformArrayIntDistribution.ts: -------------------------------------------------------------------------------- 1 | import type { Distribution } from '../types/Distribution'; 2 | import type { RandomGenerator } from '../types/RandomGenerator'; 3 | import type { ArrayInt } from './internals/ArrayInt'; 4 | import { unsafeUniformArrayIntDistribution } from './UnsafeUniformArrayIntDistribution'; 5 | 6 | /** 7 | * Uniformly generate random ArrayInt values between `from` (included) and `to` (included) 8 | * 9 | * @param from - Lower bound of the range (included) 10 | * @param to - Upper bound of the range (included) 11 | * 12 | * @public 13 | */ 14 | function uniformArrayIntDistribution(from: ArrayInt, to: ArrayInt): Distribution; 15 | /** 16 | * Uniformly generate random ArrayInt values between `from` (included) and `to` (included) 17 | * 18 | * @param from - Lower bound of the range (included) 19 | * @param to - Upper bound of the range (included) 20 | * @param rng - Instance of RandomGenerator to extract random values from 21 | * 22 | * @public 23 | */ 24 | function uniformArrayIntDistribution(from: ArrayInt, to: ArrayInt, rng: RandomGenerator): [ArrayInt, RandomGenerator]; 25 | function uniformArrayIntDistribution(from: ArrayInt, to: ArrayInt, rng?: RandomGenerator) { 26 | if (rng !== undefined) { 27 | const nextRng = rng.clone(); 28 | return [unsafeUniformArrayIntDistribution(from, to, nextRng), nextRng]; 29 | } 30 | return function (rng: RandomGenerator) { 31 | const nextRng = rng.clone(); 32 | return [unsafeUniformArrayIntDistribution(from, to, nextRng), nextRng]; 33 | }; 34 | } 35 | 36 | export { uniformArrayIntDistribution }; 37 | -------------------------------------------------------------------------------- /src/distribution/internals/UnsafeUniformArrayIntDistributionInternal.ts: -------------------------------------------------------------------------------- 1 | import type { RandomGenerator } from '../../types/RandomGenerator'; 2 | import type { ArrayInt } from './ArrayInt'; 3 | import { unsafeUniformIntDistributionInternal } from './UnsafeUniformIntDistributionInternal'; 4 | 5 | /** 6 | * Uniformly generate ArrayInt in range [0 ; rangeSize[ 7 | * 8 | * @remarks 9 | * In the worst case scenario it may discard half of the randomly generated value. 10 | * Worst case being: most significant number is 1 and remaining part evaluates to 0. 11 | * 12 | * @internal 13 | */ 14 | export function unsafeUniformArrayIntDistributionInternal( 15 | out: ArrayInt['data'], 16 | rangeSize: ArrayInt['data'], 17 | rng: RandomGenerator, 18 | ): ArrayInt['data'] { 19 | const rangeLength = rangeSize.length; 20 | 21 | // We iterate until we find a valid value for arrayInt 22 | while (true) { 23 | // We compute a new value for arrayInt 24 | for (let index = 0; index !== rangeLength; ++index) { 25 | const indexRangeSize = index === 0 ? rangeSize[0] + 1 : 0x100000000; 26 | const g = unsafeUniformIntDistributionInternal(indexRangeSize, rng); 27 | out[index] = g; 28 | } 29 | 30 | // If in the correct range we can return it 31 | for (let index = 0; index !== rangeLength; ++index) { 32 | const current = out[index]; 33 | const currentInRange = rangeSize[index]; 34 | if (current < currentInRange) { 35 | return out; // arrayInt < rangeSize 36 | } else if (current > currentInRange) { 37 | break; // arrayInt > rangeSize 38 | } 39 | } 40 | // Otherwise we need to try another one 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/pure-rand-default.ts: -------------------------------------------------------------------------------- 1 | import type { RandomGenerator } from './types/RandomGenerator'; 2 | import { congruential32 } from './generator/LinearCongruential'; 3 | import { mersenne } from './generator/MersenneTwister'; 4 | import { xorshift128plus } from './generator/XorShift'; 5 | import { xoroshiro128plus } from './generator/XoroShiro'; 6 | 7 | import type { Distribution } from './types/Distribution'; 8 | import { uniformArrayIntDistribution } from './distribution/UniformArrayIntDistribution'; 9 | import { uniformBigIntDistribution } from './distribution/UniformBigIntDistribution'; 10 | import { uniformIntDistribution } from './distribution/UniformIntDistribution'; 11 | import { unsafeUniformArrayIntDistribution } from './distribution/UnsafeUniformArrayIntDistribution'; 12 | import { unsafeUniformBigIntDistribution } from './distribution/UnsafeUniformBigIntDistribution'; 13 | import { unsafeUniformIntDistribution } from './distribution/UnsafeUniformIntDistribution'; 14 | import { skipN } from './distribution/SkipN'; 15 | import { generateN } from './distribution/GenerateN'; 16 | import { unsafeGenerateN } from './distribution/UnsafeGenerateN'; 17 | import { unsafeSkipN } from './distribution/UnsafeSkipN'; 18 | 19 | // Explicit cast into string to avoid to have __type: "__PACKAGE_TYPE__" 20 | const __type = '__PACKAGE_TYPE__' as string; 21 | const __version = '__PACKAGE_VERSION__' as string; 22 | const __commitHash = '__COMMIT_HASH__' as string; 23 | 24 | export { 25 | __type, 26 | __version, 27 | __commitHash, 28 | RandomGenerator, 29 | generateN, 30 | skipN, 31 | unsafeGenerateN, 32 | unsafeSkipN, 33 | congruential32, 34 | mersenne, 35 | xorshift128plus, 36 | xoroshiro128plus, 37 | Distribution, 38 | uniformArrayIntDistribution, 39 | uniformBigIntDistribution, 40 | uniformIntDistribution, 41 | unsafeUniformArrayIntDistribution, 42 | unsafeUniformBigIntDistribution, 43 | unsafeUniformIntDistribution, 44 | }; 45 | -------------------------------------------------------------------------------- /src/distribution/internals/ArrayInt64.ts: -------------------------------------------------------------------------------- 1 | import type { ArrayInt } from './ArrayInt'; 2 | 3 | // Helpers specific to 64 bits versions 4 | 5 | /** @internal */ 6 | export type ArrayInt64 = Required & { data: [number, number] }; 7 | 8 | /** 9 | * We only accept safe integers here 10 | * @internal 11 | */ 12 | export function fromNumberToArrayInt64(out: ArrayInt64, n: number): ArrayInt64 { 13 | if (n < 0) { 14 | const posN = -n; 15 | out.sign = -1; 16 | out.data[0] = ~~(posN / 0x100000000); 17 | out.data[1] = posN >>> 0; 18 | } else { 19 | out.sign = 1; 20 | out.data[0] = ~~(n / 0x100000000); 21 | out.data[1] = n >>> 0; 22 | } 23 | return out; 24 | } 25 | 26 | /** 27 | * Substract two ArrayInt of 64 bits on 64 bits. 28 | * With arrayIntA - arrayIntB >= 0 29 | * @internal 30 | */ 31 | export function substractArrayInt64(out: ArrayInt64, arrayIntA: ArrayInt64, arrayIntB: ArrayInt64): ArrayInt64 { 32 | const lowA = arrayIntA.data[1]; 33 | const highA = arrayIntA.data[0]; 34 | const signA = arrayIntA.sign; 35 | const lowB = arrayIntB.data[1]; 36 | const highB = arrayIntB.data[0]; 37 | const signB = arrayIntB.sign; 38 | 39 | // Requirement: arrayIntA - arrayIntB >= 0 40 | out.sign = 1; 41 | 42 | if (signA === 1 && signB === -1) { 43 | // Operation is a simple sum of arrayIntA + abs(arrayIntB) 44 | const low = lowA + lowB; 45 | const high = highA + highB + (low > 0xffffffff ? 1 : 0); 46 | out.data[0] = high >>> 0; 47 | out.data[1] = low >>> 0; 48 | return out; 49 | } 50 | // signA === -1 with signB === 1 is impossible given: arrayIntA - arrayIntB >= 0 51 | 52 | // Operation is a substraction 53 | let lowFirst = lowA; 54 | let highFirst = highA; 55 | let lowSecond = lowB; 56 | let highSecond = highB; 57 | if (signA === -1) { 58 | lowFirst = lowB; 59 | highFirst = highB; 60 | lowSecond = lowA; 61 | highSecond = highA; 62 | } 63 | let reminderLow = 0; 64 | let low = lowFirst - lowSecond; 65 | if (low < 0) { 66 | reminderLow = 1; 67 | low = low >>> 0; 68 | } 69 | out.data[0] = highFirst - highSecond - reminderLow; 70 | out.data[1] = low; 71 | return out; 72 | } 73 | -------------------------------------------------------------------------------- /CHANGELOG_3.X.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 3.X 2 | 3 | ## 3.1.0 4 | 5 | ### Features 6 | 7 | - [21990ee](https://github.com/dubzzz/pure-rand/commit/21990ee) ⚡ Faster computation of uniformBigInt (#64) 8 | - [a7cc22e](https://github.com/dubzzz/pure-rand/commit/a7cc22e) ⚡ Improve performances of uniform distributions on wide ranges (#53) 9 | 10 | ### Fixes 11 | 12 | - [22da324](https://github.com/dubzzz/pure-rand/commit/22da324) 💡 Add some comments concerning possible overflows (#57) 13 | - [d1cda0a](https://github.com/dubzzz/pure-rand/commit/d1cda0a) 🔥 Remove useless configuration files 14 | - [a52bbb1](https://github.com/dubzzz/pure-rand/commit/a52bbb1) ✅ Add snapshots for distributions to avoid regressions (#52) 15 | 16 | ## 3.0.0 17 | 18 | ### Breaking Changes 19 | 20 | - [4a14bce](https://github.com/dubzzz/pure-rand/commit/4a14bce) (💥)✨ Support ES Modules and CommonJS (#35) 21 | - [1563d5e](https://github.com/dubzzz/pure-rand/commit/1563d5e) 💥 Remove support for TypeScript <3.2 (#33) 22 | 23 | ### Features 24 | 25 | - [5301ffc](https://github.com/dubzzz/pure-rand/commit/5301ffc) ✨ Add metadata on the generated sources and .js on esm imports (#34) 26 | - [c92e942](https://github.com/dubzzz/pure-rand/commit/c92e942) ⚡️ Replace product32bits by Math.imul in mersenne 27 | - [ab8b82d](https://github.com/dubzzz/pure-rand/commit/ab8b82d) ⚡️ Replace toUint32 in mersenne by >>>0 28 | 29 | ### Fixes 30 | 31 | - [a33fc65](https://github.com/dubzzz/pure-rand/commit/a33fc65) 🔨 Add some types of profilers into profiler.cjs (#37) 32 | - [8f5b252](https://github.com/dubzzz/pure-rand/commit/8f5b252) 🎨 Move scripts for benchmark to cjs (#36) 33 | - [f5ca954](https://github.com/dubzzz/pure-rand/commit/f5ca954) 🔧 Migrate to yarn (#30) 34 | - [cc739b4](https://github.com/dubzzz/pure-rand/commit/cc739b4) 🔧 Bump prettier (#29) 35 | - [3955c2d](https://github.com/dubzzz/pure-rand/commit/3955c2d) 🔧 Update .travis.yml 36 | - [75311b1](https://github.com/dubzzz/pure-rand/commit/75311b1) 🔧 Update .travis.yml 37 | - [074d0bf](https://github.com/dubzzz/pure-rand/commit/074d0bf) 🔧 Do not run travis outside of master and PRs for master 38 | - [96790fd](https://github.com/dubzzz/pure-rand/commit/96790fd) 🔧 Create dependabot.yml 39 | -------------------------------------------------------------------------------- /test/unit/distribution/UniformBigIntDistribution.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as fc from 'fast-check'; 3 | 4 | import { uniformBigIntDistribution } from '../../../src/distribution/UniformBigIntDistribution'; 5 | import { uniformIntDistribution } from '../../../src/distribution/UniformIntDistribution'; 6 | import { mersenne } from '../../../src/generator/MersenneTwister'; 7 | 8 | const bigIntArbitrary = fc 9 | .tuple(fc.boolean(), fc.nat(0xffffffff), fc.nat(0xffffffff), fc.nat(0xffffffff), fc.nat(0xffffffff)) 10 | .map(([sign, a3, a2, a1, a0]) => { 11 | return ( 12 | (sign ? BigInt(-1) : BigInt(1)) * 13 | ((BigInt(a3) << BigInt(96)) + (BigInt(a2) << BigInt(64)) + (BigInt(a1) << BigInt(32)) + BigInt(a0)) 14 | ); 15 | }); 16 | 17 | describe('uniformBigIntDistribution', () => { 18 | if (typeof BigInt === 'undefined') { 19 | it('no test', () => { 20 | expect(true).toBe(true); 21 | }); 22 | return; 23 | } 24 | if (typeof BigInt !== 'undefined') { 25 | it('Should always generate values within the range', () => 26 | fc.assert( 27 | fc.property(fc.noShrink(fc.integer()), bigIntArbitrary, bigIntArbitrary, (seed, a, b) => { 28 | const minV = a < b ? a : b; 29 | const maxV = a < b ? b : a; 30 | const [v, nrng] = uniformBigIntDistribution(minV, maxV)(mersenne(seed)); 31 | return v >= minV && v <= maxV; 32 | }), 33 | )); 34 | it('Should be equivalent to uniformIntDistribution integers within generator range', () => 35 | fc.assert( 36 | fc.property( 37 | fc.noShrink(fc.integer()), 38 | fc.integer({ min: -0x80000000, max: 0x7fffffff }), 39 | fc.integer({ min: -0x80000000, max: 0x7fffffff }), 40 | (seed, a, b) => { 41 | const minV = a < b ? a : b; 42 | const maxV = a < b ? b : a; 43 | const [vInt, nrngInt] = uniformIntDistribution(minV, maxV)(mersenne(seed)); 44 | const [vBigInt, nrngBigInt] = uniformBigIntDistribution(BigInt(minV), BigInt(maxV))(mersenne(seed)); 45 | assert.strictEqual(Number(vBigInt), vInt); // same values 46 | assert.strictEqual(nrngBigInt.next()[0], nrngInt.next()[0]); // same generator 47 | }, 48 | ), 49 | )); 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /src/distribution/UnsafeUniformBigIntDistribution.ts: -------------------------------------------------------------------------------- 1 | import type { RandomGenerator } from '../types/RandomGenerator'; 2 | 3 | // We are capturing the reference to BigInt so that it cannot be altered 4 | // by any external code after that point. 5 | const SBigInt: typeof BigInt = typeof BigInt !== 'undefined' ? BigInt : undefined!; 6 | const One: bigint = typeof BigInt !== 'undefined' ? BigInt(1) : undefined!; 7 | const ThirtyTwo: bigint = typeof BigInt !== 'undefined' ? BigInt(32) : undefined!; 8 | const NumValues: bigint = typeof BigInt !== 'undefined' ? BigInt(0x100000000) : undefined!; 9 | 10 | /** 11 | * Uniformly generate random bigint values between `from` (included) and `to` (included) 12 | * 13 | * @param from - Lower bound of the range (included) 14 | * @param to - Upper bound of the range (included) 15 | * @param rng - Instance of RandomGenerator to extract random values from 16 | * 17 | * @public 18 | */ 19 | export function unsafeUniformBigIntDistribution(from: bigint, to: bigint, rng: RandomGenerator): bigint { 20 | const diff = to - from + One; 21 | 22 | // Number of iterations required to have enough random 23 | // to build uniform entries in the asked range 24 | let FinalNumValues = NumValues; 25 | let NumIterations = 1; // NumValues being large enough no need for bigint on NumIterations 26 | while (FinalNumValues < diff) { 27 | FinalNumValues <<= ThirtyTwo; // equivalent to: *=NumValues 28 | ++NumIterations; 29 | } 30 | 31 | let value = generateNext(NumIterations, rng); 32 | if (value < diff) { 33 | return value + from; 34 | } 35 | if (value + diff < FinalNumValues) { 36 | return (value % diff) + from; 37 | } 38 | const MaxAcceptedRandom = FinalNumValues - (FinalNumValues % diff); 39 | while (value >= MaxAcceptedRandom) { 40 | value = generateNext(NumIterations, rng); 41 | } 42 | return (value % diff) + from; 43 | } 44 | 45 | function generateNext(NumIterations: number, rng: RandomGenerator): bigint { 46 | // Aggregate mutiple calls to next() into a single random value 47 | let value = SBigInt(rng.unsafeNext() + 0x80000000); 48 | for (let num = 1; num < NumIterations; ++num) { 49 | const out = rng.unsafeNext(); 50 | value = (value << ThirtyTwo) + SBigInt(out + 0x80000000); // <> 16; 16 | }; 17 | 18 | class LinearCongruential32 implements RandomGenerator { 19 | constructor(private seed: number) {} 20 | 21 | clone(): LinearCongruential32 { 22 | return new LinearCongruential32(this.seed); 23 | } 24 | 25 | next(): [number, LinearCongruential32] { 26 | const nextRng = new LinearCongruential32(this.seed); 27 | const out = nextRng.unsafeNext(); 28 | return [out, nextRng]; 29 | } 30 | 31 | unsafeNext(): number { 32 | const s1 = computeNextSeed(this.seed); 33 | const v1 = computeValueFromNextSeed(s1); 34 | const s2 = computeNextSeed(s1); 35 | const v2 = computeValueFromNextSeed(s2); 36 | this.seed = computeNextSeed(s2); 37 | const v3 = computeValueFromNextSeed(this.seed); 38 | 39 | // value between: -0x80000000 and 0x7fffffff 40 | // in theory it should have been: v1 & 3 instead of v1 alone 41 | // but as binary operations truncate between -0x80000000 and 0x7fffffff in JavaScript 42 | // we can get rid of this operation 43 | const vnext = v3 + ((v2 + (v1 << 15)) << 15); 44 | return vnext | 0; 45 | } 46 | 47 | getState(): readonly number[] { 48 | return [this.seed]; 49 | } 50 | } 51 | 52 | function fromState(state: readonly number[]): RandomGenerator { 53 | const valid = state.length === 1; 54 | if (!valid) { 55 | throw new Error('The state must have been produced by a congruential32 RandomGenerator'); 56 | } 57 | return new LinearCongruential32(state[0]); 58 | } 59 | 60 | export const congruential32 = Object.assign( 61 | function (seed: number): RandomGenerator { 62 | return new LinearCongruential32(seed); 63 | }, 64 | { fromState }, 65 | ); 66 | -------------------------------------------------------------------------------- /test/unit/distribution/UniformBigIntDistribution.noreg.spec.ts: -------------------------------------------------------------------------------- 1 | import { uniformBigIntDistribution } from '../../../src/distribution/UniformBigIntDistribution'; 2 | import { mersenne } from '../../../src/generator/MersenneTwister'; 3 | import { RandomGenerator } from '../../../src/types/RandomGenerator'; 4 | 5 | describe('uniformBigIntDistribution [non regression]', () => { 6 | if (typeof BigInt === 'undefined') { 7 | it('no test', () => { 8 | expect(true).toBe(true); 9 | }); 10 | return; 11 | } 12 | if (typeof BigInt !== 'undefined') { 13 | it.each` 14 | from | to | topic 15 | ${0} | ${2 ** 3 - 1} | ${"range of size divisor of mersenne's one"} 16 | ${0} | ${2 ** 3 - 2} | ${"range of size divisor of mersenne's one minus one"} 17 | ${0} | ${2 ** 3} | ${"range of size divisor of mersenne's one plus one"} 18 | ${48} | ${69} | ${'random range'} 19 | ${-0x80000000} | ${0x7fffffff} | ${"mersenne's range"} 20 | ${-0x80000000} | ${0x7fffffff - 1} | ${"mersenne's range minus one"} 21 | ${-0x80000000} | ${0x7fffffff + 1} | ${"mersenne's range plus one"} 22 | ${0} | ${2 ** 40 - 1} | ${"range of size multiple of mersenne's one"} 23 | ${0} | ${2 ** 40 - 2} | ${"range of size multiple of mersenne's one minus one"} 24 | ${0} | ${2 ** 40} | ${"range of size multiple of mersenne's one plus one"} 25 | ${Number.MIN_SAFE_INTEGER} | ${Number.MAX_SAFE_INTEGER} | ${'full integer range'} 26 | `('Should not change its output in range ($from, $to) except for major bumps', ({ from, to }) => { 27 | // Remark: 28 | // ======================== 29 | // This test is purely there to ensure that we do not introduce any regression 30 | // during a commit without noticing it. 31 | // The values we expect in the output are just a snapshot taken at a certain time 32 | // in the past. They might be wrong values with bugs. 33 | 34 | let rng: RandomGenerator = mersenne(0); 35 | const distribution = uniformBigIntDistribution(BigInt(from), BigInt(to)); 36 | 37 | const values: bigint[] = []; 38 | for (let idx = 0; idx !== 10; ++idx) { 39 | const [v, nrng] = distribution(rng); 40 | values.push(v); 41 | rng = nrng; 42 | } 43 | expect(values).toMatchSnapshot(); 44 | }); 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /src/distribution/UnsafeUniformIntDistribution.ts: -------------------------------------------------------------------------------- 1 | import type { RandomGenerator } from '../types/RandomGenerator'; 2 | import { unsafeUniformIntDistributionInternal } from './internals/UnsafeUniformIntDistributionInternal'; 3 | import type { ArrayInt64 } from './internals/ArrayInt64'; 4 | import { fromNumberToArrayInt64, substractArrayInt64 } from './internals/ArrayInt64'; 5 | import { unsafeUniformArrayIntDistributionInternal } from './internals/UnsafeUniformArrayIntDistributionInternal'; 6 | 7 | const safeNumberMaxSafeInteger = Number.MAX_SAFE_INTEGER; 8 | 9 | const sharedA: ArrayInt64 = { sign: 1, data: [0, 0] }; 10 | const sharedB: ArrayInt64 = { sign: 1, data: [0, 0] }; 11 | const sharedC: ArrayInt64 = { sign: 1, data: [0, 0] }; 12 | const sharedData = [0, 0]; 13 | 14 | function uniformLargeIntInternal(from: number, to: number, rangeSize: number, rng: RandomGenerator): number { 15 | const rangeSizeArrayIntValue = 16 | rangeSize <= safeNumberMaxSafeInteger 17 | ? fromNumberToArrayInt64(sharedC, rangeSize) // no possible overflow given rangeSize is in a safe range 18 | : substractArrayInt64(sharedC, fromNumberToArrayInt64(sharedA, to), fromNumberToArrayInt64(sharedB, from)); // rangeSize might be incorrect, we compute a safer range 19 | 20 | // Adding 1 to the range 21 | if (rangeSizeArrayIntValue.data[1] === 0xffffffff) { 22 | // rangeSizeArrayIntValue.length === 2 by construct 23 | // rangeSize >= 0x00000001_00000000 and rangeSize <= 0x003fffff_fffffffe 24 | // with Number.MAX_SAFE_INTEGER - Number.MIN_SAFE_INTEGER = 0x003fffff_fffffffe 25 | rangeSizeArrayIntValue.data[0] += 1; 26 | rangeSizeArrayIntValue.data[1] = 0; 27 | } else { 28 | rangeSizeArrayIntValue.data[1] += 1; 29 | } 30 | 31 | unsafeUniformArrayIntDistributionInternal(sharedData, rangeSizeArrayIntValue.data, rng); 32 | return sharedData[0] * 0x100000000 + sharedData[1] + from; 33 | } 34 | 35 | /** 36 | * Uniformly generate random integer values between `from` (included) and `to` (included) 37 | * 38 | * @param from - Lower bound of the range (included) 39 | * @param to - Upper bound of the range (included) 40 | * @param rng - Instance of RandomGenerator to extract random values from 41 | * 42 | * @public 43 | */ 44 | export function unsafeUniformIntDistribution(from: number, to: number, rng: RandomGenerator): number { 45 | const rangeSize = to - from; 46 | if (rangeSize <= 0xffffffff) { 47 | // Calling unsafeUniformIntDistributionInternal can be considered safe 48 | // up-to 2**32 values. Above this range it may miss values. 49 | const g = unsafeUniformIntDistributionInternal(rangeSize + 1, rng); 50 | return g + from; 51 | } 52 | return uniformLargeIntInternal(from, to, rangeSize, rng); 53 | } 54 | -------------------------------------------------------------------------------- /postbuild/main.mjs: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import { replaceInFileSync } from 'replace-in-file'; 5 | import { fileURLToPath } from 'url'; 6 | 7 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 8 | 9 | // Append *.js file extension on all local imports 10 | 11 | const options = { 12 | files: ['lib/**/*.js', 'lib/**/*.d.ts'], 13 | from: [/from '\.(.*)(? { 32 | if (err) { 33 | // eslint-disable-next-line 34 | console.error(err.message); 35 | return; 36 | } 37 | 38 | const packageVersion = JSON.parse(data.toString()).version; 39 | 40 | const commonJsReplacement = replaceInFileSync({ 41 | files: 'lib/pure-rand-default.js', 42 | from: [/__PACKAGE_TYPE__/g, /__PACKAGE_VERSION__/g, /__COMMIT_HASH__/g], 43 | to: ['commonjs', packageVersion, commitHash], 44 | }); 45 | if (commonJsReplacement.length === 1 && commonJsReplacement[0].hasChanged) { 46 | // eslint-disable-next-line 47 | console.info(`Package details added onto commonjs version`); 48 | } 49 | 50 | const moduleReplacement = replaceInFileSync({ 51 | files: 'lib/esm/pure-rand-default.js', 52 | from: [/__PACKAGE_TYPE__/g, /__PACKAGE_VERSION__/g, /__COMMIT_HASH__/g], 53 | to: ['module', packageVersion, commitHash], 54 | }); 55 | if (moduleReplacement.length === 1 && moduleReplacement[0].hasChanged) { 56 | // eslint-disable-next-line 57 | console.info(`Package details added onto module version`); 58 | } 59 | }); 60 | 61 | // Helpers 62 | function getCommitHash() { 63 | const gitHubCommitHash = process.env.GITHUB_SHA && process.env.GITHUB_SHA.split('\n')[0]; 64 | if (gitHubCommitHash) { 65 | // eslint-disable-next-line 66 | console.info(`Using env variable GITHUB_SHA for the commit hash, got: ${gitHubCommitHash}`); 67 | return gitHubCommitHash; 68 | } 69 | if (process.env.EXPECT_GITHUB_SHA) { 70 | if (!gitHubCommitHash) { 71 | // eslint-disable-next-line 72 | console.error('No GITHUB_SHA specified'); 73 | process.exit(1); 74 | } 75 | } 76 | const out = execSync('git rev-parse HEAD'); 77 | return out.toString().split('\n')[0]; 78 | } 79 | -------------------------------------------------------------------------------- /test/unit/distribution/UniformIntDistribution.noreg.spec.ts: -------------------------------------------------------------------------------- 1 | import fc from 'fast-check'; 2 | 3 | import { uniformIntDistribution } from '../../../src/distribution/UniformIntDistribution'; 4 | import { mersenne } from '../../../src/generator/MersenneTwister'; 5 | import { RandomGenerator } from '../../../src/types/RandomGenerator'; 6 | 7 | describe('uniformIntDistribution [non regression]', () => { 8 | it.each` 9 | from | to | topic 10 | ${0} | ${2 ** 3 - 1} | ${"range of size divisor of mersenne's one"} 11 | ${0} | ${2 ** 3 - 2} | ${"range of size divisor of mersenne's one minus one"} 12 | ${0} | ${2 ** 3} | ${"range of size divisor of mersenne's one plus one"} 13 | ${48} | ${69} | ${'random range'} 14 | ${-0x80000000} | ${0x7fffffff} | ${"mersenne's range"} 15 | ${-0x80000000} | ${0x7fffffff - 1} | ${"mersenne's range minus one"} 16 | ${-0x80000000} | ${0x7fffffff + 1} | ${"mersenne's range plus one"} 17 | ${0} | ${2 ** 40 - 1} | ${"range of size multiple of mersenne's one"} 18 | ${0} | ${2 ** 40 - 2} | ${"range of size multiple of mersenne's one minus one"} 19 | ${0} | ${2 ** 40} | ${"range of size multiple of mersenne's one plus one"} 20 | ${Number.MIN_SAFE_INTEGER} | ${Number.MAX_SAFE_INTEGER} | ${'full integer range'} 21 | `('Should not change its output in range ($from, $to) except for major bumps', ({ from, to }) => { 22 | // Remark: 23 | // ======================== 24 | // This test is purely there to ensure that we do not introduce any regression 25 | // during a commit without noticing it. 26 | // The values we expect in the output are just a snapshot taken at a certain time 27 | // in the past. They might be wrong values with bugs. 28 | 29 | let rng: RandomGenerator = mersenne(0); 30 | const distribution = uniformIntDistribution(from, to); 31 | 32 | const values: number[] = []; 33 | for (let idx = 0; idx !== 10; ++idx) { 34 | const [v, nrng] = distribution(rng); 35 | values.push(v); 36 | rng = nrng; 37 | } 38 | expect(values).toMatchSnapshot(); 39 | }); 40 | 41 | it('Should always generate values within the range [from ; to]', () => 42 | fc.assert( 43 | fc.property(fc.noShrink(fc.integer()), fc.maxSafeInteger(), fc.maxSafeInteger(), (seed, a, b) => { 44 | const [from, to] = a < b ? [a, b] : [b, a]; 45 | const [v, _nrng] = uniformIntDistribution(from, to)(mersenne(seed)); 46 | return v >= from && v <= to; 47 | }), 48 | )); 49 | }); 50 | -------------------------------------------------------------------------------- /src/generator/XoroShiro.ts: -------------------------------------------------------------------------------- 1 | import type { RandomGenerator } from '../types/RandomGenerator'; 2 | 3 | // XoroShiro128+ with a=24, b=16, c=37, 4 | // - https://en.wikipedia.org/wiki/Xoroshiro128%2B 5 | // - http://prng.di.unimi.it/xoroshiro128plus.c 6 | class XoroShiro128Plus implements RandomGenerator { 7 | constructor( 8 | private s01: number, 9 | private s00: number, 10 | private s11: number, 11 | private s10: number, 12 | ) {} 13 | clone(): XoroShiro128Plus { 14 | return new XoroShiro128Plus(this.s01, this.s00, this.s11, this.s10); 15 | } 16 | next(): [number, XoroShiro128Plus] { 17 | const nextRng = new XoroShiro128Plus(this.s01, this.s00, this.s11, this.s10); 18 | const out = nextRng.unsafeNext(); 19 | return [out, nextRng]; 20 | } 21 | unsafeNext(): number { 22 | const out = (this.s00 + this.s10) | 0; 23 | // a = s0[n] ^ s1[n] 24 | const a0 = this.s10 ^ this.s00; 25 | const a1 = this.s11 ^ this.s01; 26 | const s00 = this.s00; 27 | const s01 = this.s01; 28 | // s0[n+1] = rotl(s0[n], 24) ^ a ^ (a << 16) 29 | this.s00 = (s00 << 24) ^ (s01 >>> 8) ^ a0 ^ (a0 << 16); 30 | this.s01 = (s01 << 24) ^ (s00 >>> 8) ^ a1 ^ ((a1 << 16) | (a0 >>> 16)); 31 | // s1[n+1] = rotl(a, 37) 32 | this.s10 = (a1 << 5) ^ (a0 >>> 27); 33 | this.s11 = (a0 << 5) ^ (a1 >>> 27); 34 | return out; 35 | } 36 | jump(): XoroShiro128Plus { 37 | const nextRng = new XoroShiro128Plus(this.s01, this.s00, this.s11, this.s10); 38 | nextRng.unsafeJump(); 39 | return nextRng; 40 | } 41 | unsafeJump(): void { 42 | // equivalent to 2^64 calls to next() 43 | // can be used to generate 2^64 non-overlapping subsequences 44 | let ns01 = 0; 45 | let ns00 = 0; 46 | let ns11 = 0; 47 | let ns10 = 0; 48 | const jump = [0xd8f554a5, 0xdf900294, 0x4b3201fc, 0x170865df]; 49 | for (let i = 0; i !== 4; ++i) { 50 | for (let mask = 1; mask; mask <<= 1) { 51 | // Because: (1 << 31) << 1 === 0 52 | if (jump[i] & mask) { 53 | ns01 ^= this.s01; 54 | ns00 ^= this.s00; 55 | ns11 ^= this.s11; 56 | ns10 ^= this.s10; 57 | } 58 | this.unsafeNext(); 59 | } 60 | } 61 | this.s01 = ns01; 62 | this.s00 = ns00; 63 | this.s11 = ns11; 64 | this.s10 = ns10; 65 | } 66 | getState(): readonly number[] { 67 | return [this.s01, this.s00, this.s11, this.s10]; 68 | } 69 | } 70 | 71 | function fromState(state: readonly number[]): RandomGenerator { 72 | const valid = state.length === 4; 73 | if (!valid) { 74 | throw new Error('The state must have been produced by a xoroshiro128plus RandomGenerator'); 75 | } 76 | return new XoroShiro128Plus(state[0], state[1], state[2], state[3]); 77 | } 78 | 79 | export const xoroshiro128plus = Object.assign( 80 | function (seed: number): RandomGenerator { 81 | return new XoroShiro128Plus(-1, ~seed, seed | 0, 0); 82 | }, 83 | { fromState }, 84 | ); 85 | -------------------------------------------------------------------------------- /src/generator/XorShift.ts: -------------------------------------------------------------------------------- 1 | import type { RandomGenerator } from '../types/RandomGenerator'; 2 | 3 | // XorShift128+ with a=23, b=18, c=5 4 | // - http://vigna.di.unimi.it/ftp/papers/xorshiftplus.pdf 5 | // - http://vigna.di.unimi.it/xorshift/xorshift128plus.c 6 | // - https://docs.rs/crate/xorshift/0.1.3/source/src/xorshift128.rs 7 | // 8 | // NOTE: Math.random() of V8 uses XorShift128+ with a=23, b=17, c=26, 9 | // See https://github.com/v8/v8/blob/4b9b23521e6fd42373ebbcb20ebe03bf445494f9/src/base/utils/random-number-generator.h#L119-L128 10 | class XorShift128Plus implements RandomGenerator { 11 | constructor( 12 | private s01: number, 13 | private s00: number, 14 | private s11: number, 15 | private s10: number, 16 | ) {} 17 | clone(): XorShift128Plus { 18 | return new XorShift128Plus(this.s01, this.s00, this.s11, this.s10); 19 | } 20 | next(): [number, XorShift128Plus] { 21 | const nextRng = new XorShift128Plus(this.s01, this.s00, this.s11, this.s10); 22 | const out = nextRng.unsafeNext(); 23 | return [out, nextRng]; 24 | } 25 | unsafeNext(): number { 26 | const a0 = this.s00 ^ (this.s00 << 23); 27 | const a1 = this.s01 ^ ((this.s01 << 23) | (this.s00 >>> 9)); 28 | const b0 = a0 ^ this.s10 ^ ((a0 >>> 18) | (a1 << 14)) ^ ((this.s10 >>> 5) | (this.s11 << 27)); 29 | const b1 = a1 ^ this.s11 ^ (a1 >>> 18) ^ (this.s11 >>> 5); 30 | const out = (this.s00 + this.s10) | 0; 31 | this.s01 = this.s11; 32 | this.s00 = this.s10; 33 | this.s11 = b1; 34 | this.s10 = b0; 35 | return out; 36 | } 37 | jump(): XorShift128Plus { 38 | const nextRng = new XorShift128Plus(this.s01, this.s00, this.s11, this.s10); 39 | nextRng.unsafeJump(); 40 | return nextRng; 41 | } 42 | unsafeJump() { 43 | // equivalent to 2^64 calls to next() 44 | // can be used to generate 2^64 non-overlapping subsequences 45 | let ns01 = 0; 46 | let ns00 = 0; 47 | let ns11 = 0; 48 | let ns10 = 0; 49 | const jump = [0x635d2dff, 0x8a5cd789, 0x5c472f96, 0x121fd215]; 50 | for (let i = 0; i !== 4; ++i) { 51 | for (let mask = 1; mask; mask <<= 1) { 52 | // Because: (1 << 31) << 1 === 0 53 | if (jump[i] & mask) { 54 | ns01 ^= this.s01; 55 | ns00 ^= this.s00; 56 | ns11 ^= this.s11; 57 | ns10 ^= this.s10; 58 | } 59 | this.unsafeNext(); 60 | } 61 | } 62 | this.s01 = ns01; 63 | this.s00 = ns00; 64 | this.s11 = ns11; 65 | this.s10 = ns10; 66 | } 67 | getState(): readonly number[] { 68 | return [this.s01, this.s00, this.s11, this.s10]; 69 | } 70 | } 71 | 72 | function fromState(state: readonly number[]): RandomGenerator { 73 | const valid = state.length === 4; 74 | if (!valid) { 75 | throw new Error('The state must have been produced by a xorshift128plus RandomGenerator'); 76 | } 77 | return new XorShift128Plus(state[0], state[1], state[2], state[3]); 78 | } 79 | 80 | export const xorshift128plus = Object.assign( 81 | function (seed: number): RandomGenerator { 82 | return new XorShift128Plus(-1, ~seed, seed | 0, 0); 83 | }, 84 | { fromState }, 85 | ); 86 | -------------------------------------------------------------------------------- /test/unit/distribution/internals/UnsafeUniformIntDistributionInternal.spec.ts: -------------------------------------------------------------------------------- 1 | import * as fc from 'fast-check'; 2 | 3 | import { unsafeUniformIntDistributionInternal } from '../../../../src/distribution/internals/UnsafeUniformIntDistributionInternal'; 4 | import { RandomGenerator } from '../../../../src/types/RandomGenerator'; 5 | 6 | class NatGenerator implements RandomGenerator { 7 | constructor(private current: number) { 8 | this.current = current | 0; 9 | } 10 | clone(): RandomGenerator { 11 | return new NatGenerator(this.current); 12 | } 13 | next(): [number, RandomGenerator] { 14 | const nextRng = this.clone(); 15 | return [nextRng.unsafeNext(), nextRng]; 16 | } 17 | unsafeNext(): number { 18 | const previousCurrent = this.current; 19 | this.current = (this.current + 1) | 0; 20 | return previousCurrent; 21 | } 22 | getState(): readonly number[] { 23 | throw new Error('Method not implemented.'); 24 | } 25 | } 26 | 27 | const MAX_RANGE: number = 1000; 28 | 29 | describe('unsafeUniformIntDistributionInternal', () => { 30 | it('Should always generate values within the range [0 ; rangeSize[', () => 31 | fc.assert( 32 | fc.property(fc.nat(), fc.integer({ min: 1, max: MAX_RANGE }), (offset, rangeSize) => { 33 | const v = unsafeUniformIntDistributionInternal(rangeSize, new NatGenerator(offset)); 34 | return v >= 0 && v < rangeSize; 35 | }), 36 | )); 37 | it('Should be able to generate all values of the range [0 ; rangeSize[', () => 38 | fc.assert( 39 | fc.property(fc.nat(), fc.integer({ min: 1, max: MAX_RANGE }), fc.nat(), (offset, rangeSize, targetOffset) => { 40 | const target = targetOffset % rangeSize; 41 | const rng: RandomGenerator = new NatGenerator(offset); 42 | for (let numTries = 0; numTries < 2 * rangeSize; ++numTries) { 43 | const v = unsafeUniformIntDistributionInternal(rangeSize, rng); 44 | if (v === target) { 45 | return true; 46 | } 47 | } 48 | return false; //twice the length should always be enough (+1 to avoid length = 0) 49 | }), 50 | )); 51 | it('Should be evenly distributed over the range [0 ; rangeSize[', () => 52 | // NOTE: 53 | // > Actually this property is true for any rangeSize >= 1 such that 54 | // > there exists an N >= 1 55 | // > where RNG_RANGE_SIZE ** N >= rangeSize and RNG_RANGE_SIZE ** N <= Number.MAX_SAFE_INTEGER 56 | // > with RNG_RANGE_SIZE = rng.max() - rng.min() + 1 57 | fc.assert( 58 | fc.property( 59 | fc.nat(), 60 | fc.integer({ min: 1, max: MAX_RANGE }), 61 | fc.integer({ min: 1, max: 100 }), 62 | (offset, rangeSize, num) => { 63 | let buckets = [...Array(rangeSize)].map(() => 0); 64 | const rng: RandomGenerator = new NatGenerator(offset); 65 | for (let numTries = 0; numTries < num * rangeSize; ++numTries) { 66 | const v = unsafeUniformIntDistributionInternal(rangeSize, rng); 67 | buckets[v] += 1; 68 | } 69 | return buckets.every((n) => n === num); 70 | }, 71 | ), 72 | )); 73 | }); 74 | -------------------------------------------------------------------------------- /CHANGELOG_4.X.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 4.X 2 | 3 | ## 4.2.1 4 | 5 | ### Fixes 6 | 7 | - [153895c](https://github.com/dubzzz/pure-rand/commit/153895c) ⚡️ Avoid creating an unneeded instance in `xoroshiro::jump` (#239) 8 | 9 | ## 4.2.0 10 | 11 | ### Features 12 | 13 | - [44a96be](https://github.com/dubzzz/pure-rand/commit/44a96be) ⚡️ Faster jump for `xoroshiro` by avoiding intermediate instances (#232) 14 | - [e277205](https://github.com/dubzzz/pure-rand/commit/e277205) ⚡️ Faster `jump` for xorshift by avoiding intermediate instances (#231) 15 | 16 | ### Fixes 17 | 18 | - [c95a0a2](https://github.com/dubzzz/pure-rand/commit/c95a0a2) 👷 Rename script `format:fix` into `format` (#234) 19 | - [124e6b5](https://github.com/dubzzz/pure-rand/commit/124e6b5) ⬆️ Bump prettier from 2.2.1 to 2.3.0 (#218) 20 | - [a760fb6](https://github.com/dubzzz/pure-rand/commit/a760fb6) 👷 Drop node 10 from build chain and move to node 16 (#233) 21 | 22 | ## 4.1.2 23 | 24 | ### Fixes 25 | 26 | - [123313f](https://github.com/dubzzz/pure-rand/commit/123313f) 🚚 Rename master into main (#175) 27 | - [d108d64](https://github.com/dubzzz/pure-rand/commit/d108d64) 💰 Add funding details (#174) 28 | - [944c593](https://github.com/dubzzz/pure-rand/commit/944c593) 👷 Move CI to gh actions (#171) 29 | 30 | ## 4.1.1 31 | 32 | ### Fixes 33 | 34 | - [b26320a](https://github.com/dubzzz/pure-rand/commit/b26320a) 🐛 Prevent infinite loops when array int for from and to start by zeros (#143) 35 | 36 | ## 4.1.0 37 | 38 | ### Features 39 | 40 | - [b71a557](https://github.com/dubzzz/pure-rand/commit/b71a557) ✨ Add uniform distribution for ranges outside of integers (#141) 41 | 42 | ### Fixes 43 | 44 | - [8db6236](https://github.com/dubzzz/pure-rand/commit/8db6236) 🔖 Back to 4.1.0 as publications for both 4.1.0 and 4.1.1 fail 45 | - [cefbf62](https://github.com/dubzzz/pure-rand/commit/cefbf62) 👷 Update travis key 46 | - [7a65041](https://github.com/dubzzz/pure-rand/commit/7a65041) 🚑 Add missing uniformArrayIntDistribution in 4.1.x 47 | 48 | ## 4.0.0 49 | 50 | - [df3d188](https://github.com/dubzzz/pure-rand/commit/df3d188) 🐛 uniformIntDistribution not uniform for gaps outside of 32 bits integers (#117) 51 | - [0322eb5](https://github.com/dubzzz/pure-rand/commit/0322eb5) 🔧 Clean warnings related to ts-jest (#130) 52 | - [4fed949](https://github.com/dubzzz/pure-rand/commit/4fed949) 🔨 Faster and more precise benchmarks (#129) 53 | - [eceb8fd](https://github.com/dubzzz/pure-rand/commit/eceb8fd) 🔨 Benchmark `--allow-local-changes` failed to un-stash (#126) 54 | - [8f358da](https://github.com/dubzzz/pure-rand/commit/8f358da) 🔨 Reports for benchmarks expose a confidence range option (#125) 55 | - [dd39020](https://github.com/dubzzz/pure-rand/commit/dd39020) 🔨 Rework benchmarks test suite of `pure-rand` (#123) 56 | - [c55f987](https://github.com/dubzzz/pure-rand/commit/c55f987) ♻️ Extract internal logic of uniformIntDistribution into shared internals (#116) 57 | - [670e2ab](https://github.com/dubzzz/pure-rand/commit/670e2ab) 📝 Add JSDoc on public code in /distribution (#115) 58 | - [cde43b5](https://github.com/dubzzz/pure-rand/commit/cde43b5) Revert "👷 Update dependabot configuration on versioning-strategy" 59 | - [56b1546](https://github.com/dubzzz/pure-rand/commit/56b1546) 👷 Update dependabot configuration on versioning-strategy 60 | -------------------------------------------------------------------------------- /CHANGELOG_5.X.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 5.X 2 | 3 | ## 5.0.5 4 | 5 | ### Fixes 6 | 7 | - [97fae65](https://github.com/dubzzz/pure-rand/commit/97fae65) 💸 Move GitHub sponsors link first for npm display (#468) 8 | 9 | ## 5.0.4 10 | 11 | ### Fixes 12 | 13 | - [a922a54](https://github.com/dubzzz/pure-rand/commit/a922a54) 👷 Add extra runs in benchmarks (#463) 14 | - [aa21bc8](https://github.com/dubzzz/pure-rand/commit/aa21bc8) 👷 Rework benchmark part (#461) 15 | - [f2937e8](https://github.com/dubzzz/pure-rand/commit/f2937e8) 💸 Add link to GitHub sponsors in funding (#462) 16 | 17 | ## 5.0.3 18 | 19 | ### Fixes 20 | 21 | - [bccd91a](https://github.com/dubzzz/pure-rand/commit/bccd91a) 🐛 Avoid BigInt crash when importing pure-rand (#432) 22 | 23 | ## 5.0.2 24 | 25 | ### Fixes 26 | 27 | - [e3c3052](https://github.com/dubzzz/pure-rand/commit/e3c3052) 🐛 More resiliency to poisoning on globals (#431) 28 | 29 | ## 5.0.1 30 | 31 | ### Fixes 32 | 33 | - [e4cafac](https://github.com/dubzzz/pure-rand/commit/e4cafac) 🐛 Add "types" to "exports" (#363) 34 | 35 | ## 5.0.0 36 | 37 | ### Breaking Changes 38 | 39 | - [4d43670](https://github.com/dubzzz/pure-rand/commit/4d43670) 💥 Remove old API of `RandomGenerator` (#245) 40 | 41 | ### Features 42 | 43 | - [64c9033](https://github.com/dubzzz/pure-rand/commit/64c9033) 🏷️ Remove never used type (#253) 44 | - [79dcea6](https://github.com/dubzzz/pure-rand/commit/79dcea6) ⚡️ Re-implement safe skipN/generateN with unsafe ones (#251) 45 | - [1137604](https://github.com/dubzzz/pure-rand/commit/1137604) ⚡️ Add unsafe version of `uniformArrayIntDistribution` (#252) 46 | - [1d23433](https://github.com/dubzzz/pure-rand/commit/1d23433) ⚡️ Add unsafe version of `uniformBigIntDistribution` (#250) 47 | - [eb6fdea](https://github.com/dubzzz/pure-rand/commit/eb6fdea) ⚡️ Add unsafe version of `uniformIntDistribution` (#249) 48 | - [0c66c2a](https://github.com/dubzzz/pure-rand/commit/0c66c2a) ⚡️ Prefer unsafe for internal on uniform array-int distributions (#248) 49 | - [d4b6353](https://github.com/dubzzz/pure-rand/commit/d4b6353) ⚡️ Prefer unsafe for internal on uniform int distributions (#247) 50 | - [ac1afe1](https://github.com/dubzzz/pure-rand/commit/ac1afe1) 🏷️ Introduce typings for `UnsafeDistribution` (#246) 51 | - [5b29db5](https://github.com/dubzzz/pure-rand/commit/5b29db5) ✨ Migrate congruential to new API (#244) 52 | - [b5da3e9](https://github.com/dubzzz/pure-rand/commit/b5da3e9) ✨ Migrate mersenne to new API (#243) 53 | - [08cb943](https://github.com/dubzzz/pure-rand/commit/08cb943) ✨ Migrate xorshift to new API (#242) 54 | - [1c8b02f](https://github.com/dubzzz/pure-rand/commit/1c8b02f) ✨ Migrate xoroshiro to new API (#241) 55 | - [153895c](https://github.com/dubzzz/pure-rand/commit/153895c) ⚡️ Avoid creating an unneeded instance in `xoroshiro::jump` (#239) 56 | - [fa94947](https://github.com/dubzzz/pure-rand/commit/fa94947) ⚡️ Avoid creating an unneeded instance in `xoroshiro::jump` (#239) 57 | - [bbad88f](https://github.com/dubzzz/pure-rand/commit/bbad88f) 🏷️ Add temporary typings of `RandomGeneratorWithUnsafe` (#238) 58 | - [dbc8582](https://github.com/dubzzz/pure-rand/commit/dbc8582) 🚧 Temporarily add a forked interface for `RandomGenerator` (#235) 59 | 60 | ### Fixes 61 | 62 | - [1eb1467](https://github.com/dubzzz/pure-rand/commit/1eb1467) ✅ Add test helpers for new generators (#240) 63 | -------------------------------------------------------------------------------- /test/unit/distribution/internals/ArrayInt.spec.ts: -------------------------------------------------------------------------------- 1 | import * as fc from 'fast-check'; 2 | 3 | import { 4 | addArrayIntToNew, 5 | addOneToPositiveArrayInt, 6 | ArrayInt, 7 | substractArrayIntToNew, 8 | trimArrayIntInplace, 9 | } from '../../../../src/distribution/internals/ArrayInt'; 10 | 11 | describe('ArrayInt', () => { 12 | describe('addArrayIntToNew', () => { 13 | // Skip next tests if BigInt is not supported 14 | if (typeof BigInt === 'undefined') return it('no test', () => expect(true).toBe(true)); 15 | 16 | it('Should properly compute a plus b', () => 17 | fc.assert( 18 | fc.property(arrayIntArb(), arrayIntArb(), (a, b) => { 19 | const r = addArrayIntToNew(a, b); 20 | expect(arrayIntToBigInt(r)).toEqual(arrayIntToBigInt(a) + arrayIntToBigInt(b)); 21 | }), 22 | )); 23 | }); 24 | 25 | describe('addOneToPositiveArrayInt', () => { 26 | // Skip next tests if BigInt is not supported 27 | if (typeof BigInt === 'undefined') return it('no test', () => expect(true).toBe(true)); 28 | 29 | it('Should properly compute a plus 1', () => 30 | fc.assert( 31 | fc.property(arrayIntArb(), (a) => { 32 | fc.pre(a.sign === 1); 33 | const r = { sign: a.sign, data: a.data.slice(0) }; 34 | addOneToPositiveArrayInt(r); 35 | expect(arrayIntToBigInt(r)).toEqual(arrayIntToBigInt(a) + BigInt(1)); 36 | }), 37 | )); 38 | }); 39 | 40 | describe('substractArrayIntToNew', () => { 41 | // Skip next tests if BigInt is not supported 42 | if (typeof BigInt === 'undefined') return it('no test', () => expect(true).toBe(true)); 43 | 44 | it('Should properly compute a minus b', () => 45 | fc.assert( 46 | fc.property(arrayIntArb(), arrayIntArb(), (a, b) => { 47 | const r = substractArrayIntToNew(a, b); 48 | expect(arrayIntToBigInt(r)).toEqual(arrayIntToBigInt(a) - arrayIntToBigInt(b)); 49 | }), 50 | )); 51 | }); 52 | 53 | describe('trimArrayIntInplace', () => { 54 | it.each` 55 | zero 56 | ${{ sign: 1, data: [] }} 57 | ${{ sign: 1, data: [0] }} 58 | ${{ sign: 1, data: [0, 0, 0, 0, 0] }} 59 | ${{ sign: -1, data: [] }} 60 | ${{ sign: -1, data: [0] }} 61 | ${{ sign: -1, data: [0, 0, 0, 0, 0] }} 62 | `('Should build a unique representation for zero (including $zero}', ({ zero }) => 63 | expect(trimArrayIntInplace(zero)).toEqual({ sign: 1, data: [0] }), 64 | ); 65 | 66 | // Skip next tests if BigInt is not supported 67 | if (typeof BigInt === 'undefined') return it('no test', () => expect(true).toBe(true)); 68 | 69 | it('Should trim leading zeros but preserve the value', () => 70 | fc.assert( 71 | fc.property(arrayIntArb(), (a) => { 72 | const originalValue = arrayIntToBigInt(a); 73 | const r = trimArrayIntInplace(a); 74 | expect(r).toBe(a); 75 | expect(r.data).not.toHaveLength(0); 76 | if (r.data.length !== 1) { 77 | expect(r.data[0]).not.toBe(0); 78 | } 79 | expect(arrayIntToBigInt(r)).toEqual(originalValue); 80 | }), 81 | )); 82 | }); 83 | }); 84 | 85 | // Helpers 86 | 87 | const arrayIntArb = () => 88 | fc.record({ 89 | sign: fc.constantFrom(1 as const, -1 as const), 90 | data: fc.array(fc.integer({ min: 0, max: 0xffffffff })), 91 | }); 92 | 93 | function arrayIntToBigInt(arrayInt: ArrayInt): bigint { 94 | let current = BigInt(0); 95 | for (let index = 0; index < arrayInt.data.length; ++index) { 96 | current <<= BigInt(32); 97 | current += BigInt(arrayInt.data[index]); 98 | } 99 | return current * BigInt(arrayInt.sign); 100 | } 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pure-rand", 3 | "version": "7.0.1", 4 | "description": " Pure random number generator written in TypeScript", 5 | "type": "commonjs", 6 | "main": "lib/pure-rand.js", 7 | "exports": { 8 | "./package.json": "./package.json", 9 | "./distribution/*": { 10 | "require": { 11 | "types": "./lib/types/distribution/*.d.ts", 12 | "default": "./lib/distribution/*.js" 13 | }, 14 | "import": { 15 | "types": "./lib/esm/types/distribution/*.d.ts", 16 | "default": "./lib/esm/distribution/*.js" 17 | } 18 | }, 19 | "./generator/*": { 20 | "require": { 21 | "types": "./lib/types/generator/*.d.ts", 22 | "default": "./lib/generator/*.js" 23 | }, 24 | "import": { 25 | "types": "./lib/esm/types/generator/*.d.ts", 26 | "default": "./lib/esm/generator/*.js" 27 | } 28 | }, 29 | "./types/*": { 30 | "require": { 31 | "types": "./lib/types/types/*.d.ts", 32 | "default": "./lib/types/*.js" 33 | }, 34 | "import": { 35 | "types": "./lib/esm/types/types/*.d.ts", 36 | "default": "./lib/esm/types/*.js" 37 | } 38 | }, 39 | ".": { 40 | "require": { 41 | "types": "./lib/types/pure-rand.d.ts", 42 | "default": "./lib/pure-rand.js" 43 | }, 44 | "import": { 45 | "types": "./lib/esm/types/pure-rand.d.ts", 46 | "default": "./lib/esm/pure-rand.js" 47 | } 48 | } 49 | }, 50 | "module": "lib/esm/pure-rand.js", 51 | "types": "lib/types/pure-rand.d.ts", 52 | "files": [ 53 | "lib" 54 | ], 55 | "sideEffects": false, 56 | "packageManager": "yarn@4.12.0", 57 | "scripts": { 58 | "format:check": "prettier --list-different .", 59 | "format": "prettier --write .", 60 | "build": "tsc && tsc -p ./tsconfig.declaration.json", 61 | "build:esm": "tsc --module es2015 --outDir lib/esm --moduleResolution node && tsc -p ./tsconfig.declaration.json --outDir lib/esm/types && cp package.esm-template.json lib/esm/package.json", 62 | "build:prod": "yarn build && yarn build:esm && node postbuild/main.mjs", 63 | "build:prod-ci": "cross-env EXPECT_GITHUB_SHA=true yarn build:prod", 64 | "test": "jest --config jest.config.js --coverage", 65 | "build:bench:old": "tsc --outDir lib-reference/", 66 | "build:bench:new": "tsc --outDir lib-test/", 67 | "bench": "node perf/benchmark.cjs" 68 | }, 69 | "repository": { 70 | "type": "git", 71 | "url": "git+https://github.com/dubzzz/pure-rand.git" 72 | }, 73 | "author": "Nicolas DUBIEN ", 74 | "license": "MIT", 75 | "bugs": { 76 | "url": "https://github.com/dubzzz/pure-rand/issues" 77 | }, 78 | "homepage": "https://github.com/dubzzz/pure-rand#readme", 79 | "devDependencies": { 80 | "@types/jest": "^30.0.0", 81 | "@types/node": "^22.19.1", 82 | "cross-env": "^10.1.0", 83 | "fast-check": "^4.3.0", 84 | "jest": "^30.0.5", 85 | "prettier": "3.7.2", 86 | "replace-in-file": "^8.3.0", 87 | "source-map-support": "^0.5.21", 88 | "tinybench": "^3.1.1", 89 | "ts-jest": "^29.4.5", 90 | "ts-node": "^10.9.2", 91 | "typescript": "^5.9.3" 92 | }, 93 | "keywords": [ 94 | "seed", 95 | "random", 96 | "prng", 97 | "generator", 98 | "pure", 99 | "rand", 100 | "mersenne", 101 | "random number generator", 102 | "fastest", 103 | "fast" 104 | ], 105 | "funding": [ 106 | { 107 | "type": "individual", 108 | "url": "https://github.com/sponsors/dubzzz" 109 | }, 110 | { 111 | "type": "opencollective", 112 | "url": "https://opencollective.com/fast-check" 113 | } 114 | ] 115 | } 116 | -------------------------------------------------------------------------------- /test/unit/distribution/__snapshots__/UniformIntDistribution.noreg.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`uniformIntDistribution [non regression] Should not change its output in range (-2147483648, 2147483646) except for major bumps 1`] = ` 4 | [ 5 | -1937831252, 6 | -1748719057, 7 | -1223252363, 8 | -668873536, 9 | -1706118333, 10 | -610118917, 11 | -1954711869, 12 | -656048793, 13 | 1819583497, 14 | -1616781613, 15 | ] 16 | `; 17 | 18 | exports[`uniformIntDistribution [non regression] Should not change its output in range (-2147483648, 2147483647) except for major bumps 1`] = ` 19 | [ 20 | -1937831252, 21 | -1748719057, 22 | -1223252363, 23 | -668873536, 24 | -1706118333, 25 | -610118917, 26 | -1954711869, 27 | -656048793, 28 | 1819583497, 29 | -1616781613, 30 | ] 31 | `; 32 | 33 | exports[`uniformIntDistribution [non regression] Should not change its output in range (-2147483648, 2147483648) except for major bumps 1`] = ` 34 | [ 35 | -1748719057, 36 | 1277901399, 37 | 243580376, 38 | 1171049868, 39 | 2051556033, 40 | -806729177, 41 | 1686997841, 42 | 602801999, 43 | 2034131043, 44 | -855081807, 45 | ] 46 | `; 47 | 48 | exports[`uniformIntDistribution [non regression] Should not change its output in range (-9007199254740991, 9007199254740991) except for major bumps 1`] = ` 49 | [ 50 | 8737458527447601, 51 | -2631726847111999, 52 | -4869447699246340, 53 | 8293355706744169, 54 | 5809858626576085, 55 | -1881170402421005, 56 | -7398455699097000, 57 | -5844700774482471, 58 | 5236255647124877, 59 | 2596200260321985, 60 | ] 61 | `; 62 | 63 | exports[`uniformIntDistribution [non regression] Should not change its output in range (0, 6) except for major bumps 1`] = ` 64 | [ 65 | 2, 66 | 1, 67 | 5, 68 | 0, 69 | 6, 70 | 0, 71 | 4, 72 | 1, 73 | 6, 74 | 3, 75 | ] 76 | `; 77 | 78 | exports[`uniformIntDistribution [non regression] Should not change its output in range (0, 7) except for major bumps 1`] = ` 79 | [ 80 | 4, 81 | 7, 82 | 5, 83 | 0, 84 | 3, 85 | 3, 86 | 3, 87 | 7, 88 | 1, 89 | 3, 90 | ] 91 | `; 92 | 93 | exports[`uniformIntDistribution [non regression] Should not change its output in range (0, 8) except for major bumps 1`] = ` 94 | [ 95 | 6, 96 | 7, 97 | 0, 98 | 4, 99 | 5, 100 | 4, 101 | 5, 102 | 8, 103 | 3, 104 | 7, 105 | ] 106 | `; 107 | 108 | exports[`uniformIntDistribution [non regression] Should not change its output in range (0, 1099511627774) except for major bumps 1`] = ` 109 | [ 110 | 739133139503, 111 | 503989783744, 112 | 289300173563, 113 | 839010057575, 114 | 39185407699, 115 | 93992703730, 116 | 158044207703, 117 | 303038774744, 118 | 381275655564, 119 | 253307142849, 120 | ] 121 | `; 122 | 123 | exports[`uniformIntDistribution [non regression] Should not change its output in range (0, 1099511627775) except for major bumps 1`] = ` 124 | [ 125 | 86298110511, 126 | 753097886912, 127 | 847645922043, 128 | 821830188391, 129 | 1027027885779, 130 | 1073245247218, 131 | 570361068119, 132 | 1041773149656, 133 | 1081355324812, 134 | 317731652289, 135 | ] 136 | `; 137 | 138 | exports[`uniformIntDistribution [non regression] Should not change its output in range (0, 1099511627776) except for major bumps 1`] = ` 139 | [ 140 | 86298110511, 141 | 753097886912, 142 | 847645922043, 143 | 821830188391, 144 | 1027027885779, 145 | 1073245247218, 146 | 570361068119, 147 | 1041773149656, 148 | 1081355324812, 149 | 317731652289, 150 | ] 151 | `; 152 | 153 | exports[`uniformIntDistribution [non regression] Should not change its output in range (48, 69) except for major bumps 1`] = ` 154 | [ 155 | 56, 156 | 53, 157 | 69, 158 | 60, 159 | 65, 160 | 49, 161 | 61, 162 | 57, 163 | 67, 164 | 65, 165 | ] 166 | `; 167 | -------------------------------------------------------------------------------- /src/generator/MersenneTwister.ts: -------------------------------------------------------------------------------- 1 | import type { RandomGenerator } from '../types/RandomGenerator'; 2 | 3 | class MersenneTwister implements RandomGenerator { 4 | static readonly N = 624; 5 | static readonly M = 397; 6 | static readonly R = 31; 7 | static readonly A = 0x9908b0df; 8 | static readonly F = 1812433253; 9 | static readonly U = 11; 10 | static readonly S = 7; 11 | static readonly B = 0x9d2c5680; 12 | static readonly T = 15; 13 | static readonly C = 0xefc60000; 14 | static readonly L = 18; 15 | static readonly MASK_LOWER = 2 ** MersenneTwister.R - 1; 16 | static readonly MASK_UPPER = 2 ** MersenneTwister.R; 17 | 18 | private static twist(prev: number[]): number[] { 19 | const mt = prev.slice(); 20 | for (let idx = 0; idx !== MersenneTwister.N - MersenneTwister.M; ++idx) { 21 | const y = (mt[idx] & MersenneTwister.MASK_UPPER) + (mt[idx + 1] & MersenneTwister.MASK_LOWER); 22 | mt[idx] = mt[idx + MersenneTwister.M] ^ (y >>> 1) ^ (-(y & 1) & MersenneTwister.A); 23 | } 24 | for (let idx = MersenneTwister.N - MersenneTwister.M; idx !== MersenneTwister.N - 1; ++idx) { 25 | const y = (mt[idx] & MersenneTwister.MASK_UPPER) + (mt[idx + 1] & MersenneTwister.MASK_LOWER); 26 | mt[idx] = mt[idx + MersenneTwister.M - MersenneTwister.N] ^ (y >>> 1) ^ (-(y & 1) & MersenneTwister.A); 27 | } 28 | const y = (mt[MersenneTwister.N - 1] & MersenneTwister.MASK_UPPER) + (mt[0] & MersenneTwister.MASK_LOWER); 29 | mt[MersenneTwister.N - 1] = mt[MersenneTwister.M - 1] ^ (y >>> 1) ^ (-(y & 1) & MersenneTwister.A); 30 | return mt; 31 | } 32 | 33 | private static seeded(seed: number): number[] { 34 | const out = Array(MersenneTwister.N); 35 | out[0] = seed; 36 | for (let idx = 1; idx !== MersenneTwister.N; ++idx) { 37 | const xored = out[idx - 1] ^ (out[idx - 1] >>> 30); 38 | out[idx] = (Math.imul(MersenneTwister.F, xored) + idx) | 0; 39 | } 40 | return out; 41 | } 42 | 43 | private constructor( 44 | private states: number[], 45 | private index: number, 46 | ) { 47 | // states: between -0x80000000 and 0x7fffffff 48 | } 49 | 50 | static from(seed: number): MersenneTwister { 51 | return new MersenneTwister(MersenneTwister.twist(MersenneTwister.seeded(seed)), 0); 52 | } 53 | 54 | clone(): MersenneTwister { 55 | return new MersenneTwister(this.states, this.index); 56 | } 57 | 58 | next(): [number, MersenneTwister] { 59 | const nextRng = new MersenneTwister(this.states, this.index); 60 | const out = nextRng.unsafeNext(); 61 | return [out, nextRng]; 62 | } 63 | 64 | unsafeNext(): number { 65 | let y = this.states[this.index]; 66 | y ^= this.states[this.index] >>> MersenneTwister.U; 67 | y ^= (y << MersenneTwister.S) & MersenneTwister.B; 68 | y ^= (y << MersenneTwister.T) & MersenneTwister.C; 69 | y ^= y >>> MersenneTwister.L; 70 | if (++this.index >= MersenneTwister.N) { 71 | this.states = MersenneTwister.twist(this.states); 72 | this.index = 0; 73 | } 74 | return y; 75 | } 76 | 77 | getState(): readonly number[] { 78 | return [this.index, ...this.states]; 79 | } 80 | 81 | public static fromState(state: readonly number[]): MersenneTwister { 82 | const valid = state.length === MersenneTwister.N + 1 && state[0] >= 0 && state[0] < MersenneTwister.N; 83 | if (!valid) { 84 | throw new Error('The state must have been produced by a mersenne RandomGenerator'); 85 | } 86 | return new MersenneTwister(state.slice(1), state[0]); 87 | } 88 | } 89 | 90 | function fromState(state: readonly number[]): RandomGenerator { 91 | return MersenneTwister.fromState(state); 92 | } 93 | 94 | export const mersenne = Object.assign( 95 | function (seed: number): RandomGenerator { 96 | return MersenneTwister.from(seed); 97 | }, 98 | { fromState }, 99 | ); 100 | -------------------------------------------------------------------------------- /test/unit/distribution/internals/ArrayInt64.spec.ts: -------------------------------------------------------------------------------- 1 | import * as fc from 'fast-check'; 2 | 3 | import { 4 | ArrayInt64, 5 | fromNumberToArrayInt64, 6 | substractArrayInt64, 7 | } from '../../../../src/distribution/internals/ArrayInt64'; 8 | 9 | describe('ArrayInt64', () => { 10 | describe('fromNumberToArrayInt64', () => { 11 | it('Should be able to convert any 32 bits positive integer to an ArrayInt64', () => 12 | fc.assert( 13 | fc.property(fc.integer({ min: 0, max: 0xffffffff }), (value) => { 14 | const arrayInt = fromNumberToArrayInt64(arrayInt64Buffer(), value); 15 | expect(arrayInt).toEqual({ sign: 1, data: [0, value] }); 16 | }), 17 | )); 18 | 19 | it('Should be able to convert any 32 bits negative integer to an ArrayInt64', () => 20 | fc.assert( 21 | fc.property(fc.integer({ min: 1, max: 0xffffffff }), (value) => { 22 | const arrayInt = fromNumberToArrayInt64(arrayInt64Buffer(), -value); 23 | expect(arrayInt).toEqual({ sign: -1, data: [0, value] }); 24 | }), 25 | )); 26 | 27 | it('Should be able to convert any safe integer to an ArrayInt64', () => 28 | fc.assert( 29 | fc.property(fc.maxSafeInteger(), (value) => { 30 | const arrayInt = fromNumberToArrayInt64(arrayInt64Buffer(), value); 31 | 32 | expect(arrayInt.sign).toBe(value < 0 ? -1 : 1); 33 | expect(arrayInt.data).toHaveLength(2); 34 | 35 | const arrayIntHexaRepr = 36 | arrayInt.data[0].toString(16).padStart(8, '0') + arrayInt.data[1].toString(16).padStart(8, '0'); 37 | const valueHexaRepr = Math.abs(value).toString(16).padStart(16, '0'); 38 | expect(arrayIntHexaRepr).toBe(valueHexaRepr); 39 | }), 40 | )); 41 | 42 | it('Should be able to read back itself using toNumber', () => 43 | fc.assert( 44 | fc.property(fc.maxSafeInteger(), (value) => { 45 | const arrayInt = fromNumberToArrayInt64(arrayInt64Buffer(), value); 46 | expect(toNumber(arrayInt)).toBe(value); 47 | }), 48 | )); 49 | }); 50 | 51 | describe('substractArrayInt64', () => { 52 | // Skip next tests if BigInt is not supported 53 | if (typeof BigInt === 'undefined') return it('no test', () => expect(true).toBe(true)); 54 | 55 | const fromBigIntToArrayInt64 = (n: bigint): ArrayInt64 => { 56 | const posN = n < BigInt(0) ? -n : n; 57 | return { 58 | sign: n < BigInt(0) ? -1 : 1, 59 | data: [Number(posN >> BigInt(32)), Number(posN % (BigInt(1) << BigInt(32)))], 60 | }; 61 | }; 62 | 63 | it('Should be able to substract two non-overflowing ArrayInt64', () => 64 | fc.assert( 65 | fc.property(bigInt64(), bigInt64(), (a, b) => { 66 | const min = a < b ? a : b; 67 | const max = a < b ? b : a; 68 | const result = max - min; 69 | fc.pre(result < BigInt(1) << BigInt(64)); 70 | 71 | const minArrayInt = fromBigIntToArrayInt64(min); 72 | const maxArrayInt = fromBigIntToArrayInt64(max); 73 | const resultArrayInt = fromBigIntToArrayInt64(result); 74 | expect(substractArrayInt64(arrayInt64Buffer(), maxArrayInt, minArrayInt)).toEqual(resultArrayInt); 75 | }), 76 | )); 77 | }); 78 | }); 79 | 80 | // Helpers 81 | 82 | function arrayInt64Buffer(): ArrayInt64 { 83 | return { sign: 1, data: [0, 0] }; 84 | } 85 | 86 | function toNumber(arrayInt: ArrayInt64): number { 87 | let current = arrayInt.data[0]; 88 | const arrayIntLength = arrayInt.data.length; 89 | for (let index = 1; index < arrayIntLength; ++index) { 90 | current *= 0x100000000; 91 | current += arrayInt.data[index]; 92 | } 93 | return current * (arrayInt.sign || 1); 94 | } 95 | 96 | function bigInt64() { 97 | // @ts-ignore 98 | return fc.bigInt({ min: -(BigInt(1) << BigInt(63)), max: (BigInt(1) << BigInt(63)) - BigInt(1) }); 99 | } 100 | -------------------------------------------------------------------------------- /test/unit/distribution/__snapshots__/UniformBigIntDistribution.noreg.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`uniformBigIntDistribution [non regression] Should not change its output in range (-2147483648, 2147483646) except for major bumps 1`] = ` 4 | [ 5 | -1937831252n, 6 | -1748719057n, 7 | -1223252363n, 8 | -668873536n, 9 | -1706118333n, 10 | -610118917n, 11 | -1954711869n, 12 | -656048793n, 13 | 1819583497n, 14 | -1616781613n, 15 | ] 16 | `; 17 | 18 | exports[`uniformBigIntDistribution [non regression] Should not change its output in range (-2147483648, 2147483647) except for major bumps 1`] = ` 19 | [ 20 | -1937831252n, 21 | -1748719057n, 22 | -1223252363n, 23 | -668873536n, 24 | -1706118333n, 25 | -610118917n, 26 | -1954711869n, 27 | -656048793n, 28 | 1819583497n, 29 | -1616781613n, 30 | ] 31 | `; 32 | 33 | exports[`uniformBigIntDistribution [non regression] Should not change its output in range (-2147483648, 2147483648) except for major bumps 1`] = ` 34 | [ 35 | -1958371453n, 36 | -1593104821n, 37 | -1051484232n, 38 | -848820572n, 39 | -1288881461n, 40 | 1024296413n, 41 | 1545962292n, 42 | -1439071854n, 43 | -820366540n, 44 | -1742796409n, 45 | ] 46 | `; 47 | 48 | exports[`uniformBigIntDistribution [non regression] Should not change its output in range (-9007199254740991, 9007199254740991) except for major bumps 1`] = ` 49 | [ 50 | 8737458527447649n, 51 | -2631726847111779n, 52 | -4869447699246235n, 53 | 8293355706744213n, 54 | 5809858626577029n, 55 | -1881170402420856n, 56 | -7398455699096040n, 57 | -5844700774482070n, 58 | 5236255647125351n, 59 | 2596200260322890n, 60 | ] 61 | `; 62 | 63 | exports[`uniformBigIntDistribution [non regression] Should not change its output in range (0, 6) except for major bumps 1`] = ` 64 | [ 65 | 2n, 66 | 1n, 67 | 5n, 68 | 0n, 69 | 6n, 70 | 0n, 71 | 4n, 72 | 1n, 73 | 6n, 74 | 3n, 75 | ] 76 | `; 77 | 78 | exports[`uniformBigIntDistribution [non regression] Should not change its output in range (0, 7) except for major bumps 1`] = ` 79 | [ 80 | 4n, 81 | 7n, 82 | 5n, 83 | 0n, 84 | 3n, 85 | 3n, 86 | 3n, 87 | 7n, 88 | 1n, 89 | 3n, 90 | ] 91 | `; 92 | 93 | exports[`uniformBigIntDistribution [non regression] Should not change its output in range (0, 8) except for major bumps 1`] = ` 94 | [ 95 | 6n, 96 | 7n, 97 | 0n, 98 | 4n, 99 | 5n, 100 | 4n, 101 | 5n, 102 | 8n, 103 | 3n, 104 | 7n, 105 | ] 106 | `; 107 | 108 | exports[`uniformBigIntDistribution [non regression] Should not change its output in range (0, 1099511627774) except for major bumps 1`] = ` 109 | [ 110 | 739133958457n, 111 | 503993394022n, 112 | 289301897646n, 113 | 839010810589n, 114 | 39200904055n, 115 | 93995151427n, 116 | 158059937806n, 117 | 303045347604n, 118 | 381283434534n, 119 | 253321964538n, 120 | ] 121 | `; 122 | 123 | exports[`uniformBigIntDistribution [non regression] Should not change its output in range (0, 1099511627775) except for major bumps 1`] = ` 124 | [ 125 | 739133139503n, 126 | 503989783744n, 127 | 289300173563n, 128 | 839010057575n, 129 | 39185407699n, 130 | 93992703730n, 131 | 158044207703n, 132 | 303038774744n, 133 | 381275655564n, 134 | 253307142849n, 135 | ] 136 | `; 137 | 138 | exports[`uniformBigIntDistribution [non regression] Should not change its output in range (0, 1099511627776) except for major bumps 1`] = ` 139 | [ 140 | 739132320549n, 141 | 503986173466n, 142 | 289298449480n, 143 | 839009304561n, 144 | 39169911343n, 145 | 93990256033n, 146 | 158028477600n, 147 | 303032201884n, 148 | 381267876594n, 149 | 253292321160n, 150 | ] 151 | `; 152 | 153 | exports[`uniformBigIntDistribution [non regression] Should not change its output in range (48, 69) except for major bumps 1`] = ` 154 | [ 155 | 56n, 156 | 53n, 157 | 69n, 158 | 60n, 159 | 65n, 160 | 49n, 161 | 61n, 162 | 57n, 163 | 67n, 164 | 65n, 165 | ] 166 | `; 167 | -------------------------------------------------------------------------------- /src/distribution/internals/ArrayInt.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An ArrayInt represents an integer larger than what can be represented in classical JavaScript. 3 | * The values stored in data must be in the range [0, 0xffffffff]. 4 | * 5 | * @example 6 | * ```js 7 | * { sign: 1, data: [ 42 ] } // = 42 8 | * { sign: -1, data: [ 42 ] } // = -42 9 | * { sign: -1, data: [ 5, 42 ] } // = -1 * (5 * 2**32 + 42) 10 | * { sign: -1, data: [ 1, 5, 42 ] } // = -1 * (1 * 2**64 + 5 * 2**32 + 42) 11 | * ``` 12 | */ 13 | export type ArrayInt = { 14 | /** 15 | * Sign of the represented number 16 | */ 17 | sign: -1 | 1; 18 | /** 19 | * Value of the number, must only contain numbers in the range [0, 0xffffffff] 20 | */ 21 | data: number[]; 22 | }; 23 | 24 | /** 25 | * Add two ArrayInt 26 | * @internal 27 | */ 28 | export function addArrayIntToNew(arrayIntA: ArrayInt, arrayIntB: ArrayInt): ArrayInt { 29 | if (arrayIntA.sign !== arrayIntB.sign) { 30 | return substractArrayIntToNew(arrayIntA, { sign: -arrayIntB.sign as -1 | 1, data: arrayIntB.data }); 31 | } 32 | const data: number[] = []; 33 | let reminder = 0; 34 | const dataA = arrayIntA.data; 35 | const dataB = arrayIntB.data; 36 | for (let indexA = dataA.length - 1, indexB = dataB.length - 1; indexA >= 0 || indexB >= 0; --indexA, --indexB) { 37 | const vA = indexA >= 0 ? dataA[indexA] : 0; 38 | const vB = indexB >= 0 ? dataB[indexB] : 0; 39 | const current = vA + vB + reminder; 40 | data.push(current >>> 0); 41 | reminder = ~~(current / 0x100000000); 42 | } 43 | if (reminder !== 0) { 44 | data.push(reminder); 45 | } 46 | return { sign: arrayIntA.sign, data: data.reverse() }; 47 | } 48 | 49 | /** 50 | * Add one to a given positive ArrayInt 51 | * @internal 52 | */ 53 | export function addOneToPositiveArrayInt(arrayInt: ArrayInt): ArrayInt { 54 | arrayInt.sign = 1; // handling case { sign: -1, data: [0,...,0] } 55 | const data = arrayInt.data; 56 | for (let index = data.length - 1; index >= 0; --index) { 57 | if (data[index] === 0xffffffff) { 58 | data[index] = 0; 59 | } else { 60 | data[index] += 1; 61 | return arrayInt; 62 | } 63 | } 64 | data.unshift(1); 65 | return arrayInt; 66 | } 67 | 68 | /** @internal */ 69 | function isStrictlySmaller(dataA: number[], dataB: number[]): boolean { 70 | const maxLength = Math.max(dataA.length, dataB.length); 71 | for (let index = 0; index < maxLength; ++index) { 72 | const indexA = index + dataA.length - maxLength; 73 | const indexB = index + dataB.length - maxLength; 74 | const vA = indexA >= 0 ? dataA[indexA] : 0; 75 | const vB = indexB >= 0 ? dataB[indexB] : 0; 76 | if (vA < vB) return true; 77 | if (vA > vB) return false; 78 | } 79 | return false; 80 | } 81 | 82 | /** 83 | * Substract two ArrayInt 84 | * @internal 85 | */ 86 | export function substractArrayIntToNew(arrayIntA: ArrayInt, arrayIntB: ArrayInt): ArrayInt { 87 | if (arrayIntA.sign !== arrayIntB.sign) { 88 | return addArrayIntToNew(arrayIntA, { sign: -arrayIntB.sign as -1 | 1, data: arrayIntB.data }); 89 | } 90 | const dataA = arrayIntA.data; 91 | const dataB = arrayIntB.data; 92 | if (isStrictlySmaller(dataA, dataB)) { 93 | const out = substractArrayIntToNew(arrayIntB, arrayIntA); 94 | out.sign = -out.sign as -1 | 1; 95 | return out; 96 | } 97 | const data: number[] = []; 98 | let reminder = 0; 99 | for (let indexA = dataA.length - 1, indexB = dataB.length - 1; indexA >= 0 || indexB >= 0; --indexA, --indexB) { 100 | const vA = indexA >= 0 ? dataA[indexA] : 0; 101 | const vB = indexB >= 0 ? dataB[indexB] : 0; 102 | const current = vA - vB - reminder; 103 | data.push(current >>> 0); 104 | reminder = current < 0 ? 1 : 0; 105 | } 106 | return { sign: arrayIntA.sign, data: data.reverse() }; 107 | } 108 | 109 | /** 110 | * Trim uneeded zeros in ArrayInt 111 | * and uniform notation for zero: {sign: 1, data: [0]} 112 | */ 113 | export function trimArrayIntInplace(arrayInt: ArrayInt) { 114 | const data = arrayInt.data; 115 | let firstNonZero = 0; 116 | for (; firstNonZero !== data.length && data[firstNonZero] === 0; ++firstNonZero) {} 117 | if (firstNonZero === data.length) { 118 | // only zeros 119 | arrayInt.sign = 1; 120 | arrayInt.data = [0]; 121 | return arrayInt; 122 | } 123 | data.splice(0, firstNonZero); 124 | return arrayInt; 125 | } 126 | -------------------------------------------------------------------------------- /.github/workflows/build-status.yml: -------------------------------------------------------------------------------- 1 | name: Build Status 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - next-* 8 | - fix-v* 9 | tags: 10 | - v** 11 | pull_request: 12 | branches: 13 | - main 14 | - next-* 15 | - fix-v* 16 | 17 | jobs: 18 | warmup_yarn_cache: 19 | name: 'Warm up Yarn cache' 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v5.0.1 23 | - name: Using Node v22.x 24 | uses: actions/setup-node@v6 25 | id: yarn-cache 26 | with: 27 | node-version: '22.21.1' 28 | cache: 'yarn' 29 | - name: Update Yarn cache 30 | if: steps.yarn-cache.outputs.cache-hit != 'true' 31 | env: 32 | # Using PNP linker for better performance 33 | YARN_NODE_LINKER: pnp 34 | run: yarn install --immutable 35 | format: 36 | name: 'Format' 37 | needs: warmup_yarn_cache 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v5.0.1 41 | - name: Using Node v22.x 42 | uses: actions/setup-node@v6 43 | with: 44 | node-version: '22.21.1' 45 | cache: 'yarn' 46 | - name: Install dependencies 47 | run: yarn install --immutable 48 | - name: Check format 49 | run: yarn format:check 50 | test: 51 | name: 'Test' 52 | needs: warmup_yarn_cache 53 | runs-on: ubuntu-latest 54 | strategy: 55 | matrix: 56 | node-version: [18.x, 20.x] 57 | steps: 58 | - uses: actions/checkout@v5.0.1 59 | - name: Using Node v${{matrix.node-version}} 60 | uses: actions/setup-node@v6 61 | with: 62 | node-version: ${{matrix.node-version}} 63 | cache: 'yarn' 64 | - name: Install dependencies 65 | run: yarn install --immutable 66 | - name: Build production package 67 | run: yarn build:prod-ci 68 | - name: Unit tests 69 | run: yarn test 70 | - name: Codecov 71 | uses: codecov/codecov-action@v5 72 | with: 73 | name: unit-tests-${{matrix.node-version}}-${{runner.os}} 74 | flags: unit-tests, unit-tests-${{matrix.node-version}}-${{runner.os}} 75 | fail_ci_if_error: false # default: false 76 | verbose: false # default: false 77 | test_package_quality: 78 | name: 'Test package quality' 79 | needs: warmup_yarn_cache 80 | runs-on: ubuntu-latest 81 | steps: 82 | - uses: actions/checkout@v5.0.1 83 | - name: Using Node v22.x 84 | uses: actions/setup-node@v6 85 | with: 86 | node-version: '22.21.1' 87 | cache: 'yarn' 88 | - name: Check package score using skypack 89 | run: yarn dlx @skypack/package-check 90 | production_package: 91 | name: 'Build production package' 92 | needs: warmup_yarn_cache 93 | runs-on: ubuntu-latest 94 | steps: 95 | - uses: actions/checkout@v5.0.1 96 | - name: Using Node v22.x 97 | uses: actions/setup-node@v6 98 | with: 99 | node-version: '22.21.1' 100 | cache: 'yarn' 101 | - name: Install dependencies 102 | run: yarn install --immutable 103 | - name: Build production package 104 | run: yarn build:prod-ci 105 | - name: Create bundle 106 | run: yarn pack 107 | - name: Upload production package 108 | uses: actions/upload-artifact@v5 109 | with: 110 | name: bundle 111 | path: package.tgz 112 | if-no-files-found: error 113 | retention-days: 1 114 | test_legacy: 115 | name: 'Test legacy' 116 | needs: production_package 117 | runs-on: ubuntu-latest 118 | steps: 119 | - uses: actions/checkout@v5.0.1 120 | - name: Using Node 0.12 121 | shell: bash -l {0} 122 | run: nvm install 0.12 123 | - name: Download production package 124 | uses: actions/download-artifact@v6 125 | with: 126 | name: bundle 127 | - name: Untar the published package 128 | run: | 129 | tar -zxvf package.tgz 130 | mv package/lib lib 131 | - name: Test 132 | run: node test/legacy/main.js 133 | publish_package: 134 | name: 'Publish package' 135 | needs: 136 | - production_package 137 | - format 138 | - test 139 | - test_legacy 140 | - test_package_quality 141 | if: startsWith(github.ref, 'refs/tags/v') 142 | runs-on: ubuntu-latest 143 | permissions: 144 | contents: read 145 | id-token: write 146 | steps: 147 | - name: Using Node v22.x 148 | uses: actions/setup-node@v6 149 | with: 150 | node-version: '22.21.1' 151 | registry-url: 'https://registry.npmjs.org' 152 | - name: Install latest npm version 153 | run: npm install -g npm 154 | - name: Download production package 155 | uses: actions/download-artifact@v6 156 | with: 157 | name: bundle 158 | - name: Publish package 159 | run: npm publish --provenance --access public package.tgz 160 | env: 161 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 162 | -------------------------------------------------------------------------------- /test/unit/distribution/UniformArrayIntDistribution.noreg.spec.ts: -------------------------------------------------------------------------------- 1 | import fc from 'fast-check'; 2 | 3 | import { ArrayInt } from '../../../src/distribution/internals/ArrayInt'; 4 | import { uniformArrayIntDistribution } from '../../../src/distribution/UniformArrayIntDistribution'; 5 | import { mersenne } from '../../../src/generator/MersenneTwister'; 6 | import { RandomGenerator } from '../../../src/types/RandomGenerator'; 7 | 8 | describe('uniformArrayIntDistribution [non regression]', () => { 9 | it.each` 10 | from | to | topic 11 | ${{ sign: -1, data: [1] }} | ${{ sign: 1, data: [1] }} | ${'3 states [-1, 0, 1]'} 12 | ${{ sign: -1, data: [2] }} | ${{ sign: 1, data: [1] }} | ${'4 states [-2, -1, 0, 1]'} 13 | ${{ sign: -1, data: [0x80000000] }} | ${{ sign: 1, data: [0x7fffffff] }} | ${'32-bit signed'} 14 | ${{ sign: 1, data: [0x00000000] }} | ${{ sign: 1, data: [0xffffffff] }} | ${'32-bit unsigned'} 15 | ${{ sign: -1, data: [0x80000000, 0x00000000] }} | ${{ sign: 1, data: [0x7fffffff, 0xffffffff] }} | ${'64-bit signed'} 16 | ${{ sign: 1, data: [0x00000000, 0x00000000] }} | ${{ sign: 1, data: [0xffffffff, 0xffffffff] }} | ${'64-bit unsigned'} 17 | ${{ sign: -1, data: [0x80000000, 0, 0, 0] }} | ${{ sign: 1, data: [0x7fffffff, 0xffffffff, 0xffffffff, 0xffffffff] }} | ${'128-bit signed'} 18 | ${{ sign: 1, data: [0x12345678, 0x90abcdef] }} | ${{ sign: 1, data: [0xfedcba09, 0x87654321] }} | ${'fuzzy'} 19 | ${{ sign: 1, data: [0, 0] }} | ${{ sign: 1, data: [0, 5] }} | ${'trailing zeros'} 20 | `('Should not change its output in asked range except for major bumps ($topic)', ({ from, to }) => { 21 | // Remark: 22 | // ======================== 23 | // This test is purely there to ensure that we do not introduce any regression 24 | // during a commit without noticing it. 25 | // The values we expect in the output are just a snapshot taken at a certain time 26 | // in the past. They might be wrong values with bugs. 27 | 28 | let rng: RandomGenerator = mersenne(0); 29 | const distribution = uniformArrayIntDistribution(from, to); 30 | 31 | const values: ArrayInt[] = []; 32 | for (let idx = 0; idx !== 10; ++idx) { 33 | const [v, nrng] = distribution(rng); 34 | values.push(v); 35 | rng = nrng; 36 | } 37 | expect(values).toMatchSnapshot(); 38 | }); 39 | 40 | // Skip next tests if BigInt is not supported 41 | if (typeof BigInt === 'undefined') return it('no test', () => expect(true).toBe(true)); 42 | 43 | it('Should always generate values within the range [from ; to]', () => 44 | fc.assert( 45 | fc.property(fc.noShrink(fc.integer()), arrayIntArb(), arrayIntArb(), (seed, a, b) => { 46 | const [from, to] = arrayIntToBigInt(a) < arrayIntToBigInt(b) ? [a, b] : [b, a]; 47 | const [v, _nrng] = uniformArrayIntDistribution(from, to)(mersenne(seed)); 48 | const vBigInt = arrayIntToBigInt(v); 49 | return vBigInt >= arrayIntToBigInt(from) && vBigInt <= arrayIntToBigInt(to); 50 | }), 51 | )); 52 | 53 | it('Should always trim the zeros from the resulting value', () => 54 | fc.assert( 55 | fc.property(fc.noShrink(fc.integer()), arrayIntArb(), arrayIntArb(), (seed, a, b) => { 56 | const [from, to] = arrayIntToBigInt(a) < arrayIntToBigInt(b) ? [a, b] : [b, a]; 57 | const [v, _nrng] = uniformArrayIntDistribution(from, to)(mersenne(seed)); 58 | expect(v.data).not.toHaveLength(0); 59 | if (v.data.length !== 1) { 60 | expect(v.data[0]).not.toBe(0); // do not start by zero when data has multiple values 61 | } else if (v.data[0] === 0) { 62 | expect(v.sign).toBe(1); // zero has sign=1 63 | } 64 | }), 65 | )); 66 | 67 | it('Should always produce valid ArrayInt', () => 68 | fc.assert( 69 | fc.property(fc.noShrink(fc.integer()), arrayIntArb(), arrayIntArb(), (seed, a, b) => { 70 | const [from, to] = arrayIntToBigInt(a) < arrayIntToBigInt(b) ? [a, b] : [b, a]; 71 | const [v, _nrng] = uniformArrayIntDistribution(from, to)(mersenne(seed)); 72 | expect([-1, 1]).toContainEqual(v.sign); // sign is either 1 or -1 73 | expect(v.data).not.toHaveLength(0); // data is never empty 74 | for (const d of v.data) { 75 | // data in [0 ; 0xffffffff] 76 | expect(d).toBeGreaterThanOrEqual(0); 77 | expect(d).toBeLessThanOrEqual(0xffffffff); 78 | } 79 | }), 80 | )); 81 | }); 82 | 83 | // Helpers 84 | 85 | const arrayIntArb = () => 86 | fc.record({ 87 | sign: fc.constantFrom(1 as const, -1 as const), 88 | data: fc.array(fc.integer({ min: 0, max: 0xffffffff })), 89 | }); 90 | 91 | function arrayIntToBigInt(arrayInt: ArrayInt): bigint { 92 | let current = BigInt(0); 93 | for (let index = 0; index < arrayInt.data.length; ++index) { 94 | current <<= BigInt(32); 95 | current += BigInt(arrayInt.data[index]); 96 | } 97 | return current * BigInt(arrayInt.sign); 98 | } 99 | -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | - randpurepure- rand -------------------------------------------------------------------------------- /test/unit/distribution/UniformArrayIntDistribution.spec.ts: -------------------------------------------------------------------------------- 1 | import * as fc from 'fast-check'; 2 | import { mocked } from '../../__test-helpers__/mocked'; 3 | 4 | import { uniformArrayIntDistribution } from '../../../src/distribution/UniformArrayIntDistribution'; 5 | import { RandomGenerator } from '../../../src/types/RandomGenerator'; 6 | 7 | import * as UnsafeUniformArrayIntDistributionInternalMock from '../../../src/distribution/internals/UnsafeUniformArrayIntDistributionInternal'; 8 | import { ArrayInt } from '../../../src/distribution/internals/ArrayInt'; 9 | jest.mock('../../../src/distribution/internals/UnsafeUniformArrayIntDistributionInternal'); 10 | 11 | function buildUniqueRng(clonedRng?: RandomGenerator): RandomGenerator { 12 | return { 13 | clone() { 14 | if (clonedRng !== undefined) { 15 | return clonedRng; 16 | } 17 | return buildUniqueRng(); 18 | }, 19 | } as RandomGenerator; 20 | } 21 | function clean() { 22 | jest.resetAllMocks(); 23 | jest.clearAllMocks(); 24 | } 25 | 26 | beforeEach(clean); 27 | describe('uniformArrayIntDistribution', () => { 28 | // Skip next tests if BigInt is not supported 29 | if (typeof BigInt === 'undefined') return it('no test', () => expect(true).toBe(true)); 30 | 31 | it('Should call unsafeUniformArrayIntDistributionInternal with correct range and source rng', () => 32 | fc.assert( 33 | fc 34 | .property(arrayIntArb(), arrayIntArb(), (a, b) => { 35 | // Arrange 36 | const { unsafeUniformArrayIntDistributionInternal, from, to } = mockInternals(a, b); 37 | const expectedRangeSize = arrayIntToBigInt(to) - arrayIntToBigInt(from) + BigInt(1); 38 | const expectedRng = buildUniqueRng(); 39 | 40 | // Act 41 | uniformArrayIntDistribution(from, to, buildUniqueRng(expectedRng)); 42 | 43 | // Assert 44 | const { rangeSize, rng } = extractParams(unsafeUniformArrayIntDistributionInternal); 45 | expect(arrayIntToBigInt({ sign: 1, data: rangeSize })).toBe(expectedRangeSize); 46 | expect(rng).toBe(expectedRng); 47 | }) 48 | .beforeEach(clean), 49 | )); 50 | 51 | it('Should call unsafeUniformArrayIntDistributionInternal with non-empty range', () => 52 | fc.assert( 53 | fc 54 | .property(arrayIntArb(), arrayIntArb(), (a, b) => { 55 | // Arrange 56 | const { unsafeUniformArrayIntDistributionInternal, from, to } = mockInternals(a, b); 57 | 58 | // Act 59 | uniformArrayIntDistribution(from, to, buildUniqueRng()); 60 | 61 | // Assert 62 | const { rangeSize } = extractParams(unsafeUniformArrayIntDistributionInternal); 63 | expect(rangeSize.length).toBeGreaterThanOrEqual(1); 64 | }) 65 | .beforeEach(clean), 66 | )); 67 | 68 | it('Should call unsafeUniformArrayIntDistributionInternal with trimmed range (no trailing zeros)', () => 69 | fc.assert( 70 | fc 71 | .property(arrayIntArb(), arrayIntArb(), (a, b) => { 72 | // Arrange 73 | const { unsafeUniformArrayIntDistributionInternal, from, to } = mockInternals(a, b); 74 | 75 | // Act 76 | uniformArrayIntDistribution(from, to, buildUniqueRng()); 77 | 78 | // Assert 79 | const { rangeSize } = extractParams(unsafeUniformArrayIntDistributionInternal); 80 | expect(rangeSize[0]).not.toBe(0); // rangeSize >= 1 81 | }) 82 | .beforeEach(clean), 83 | )); 84 | 85 | it('Should call unsafeUniformArrayIntDistributionInternal with out having same length as range', () => 86 | fc.assert( 87 | fc 88 | .property(arrayIntArb(), arrayIntArb(), (a, b) => { 89 | // Arrange 90 | const { unsafeUniformArrayIntDistributionInternal, from, to } = mockInternals(a, b); 91 | 92 | // Act 93 | uniformArrayIntDistribution(from, to, buildUniqueRng()); 94 | 95 | // Assert 96 | const { out, rangeSize } = extractParams(unsafeUniformArrayIntDistributionInternal); 97 | expect(out).toHaveLength(rangeSize.length); 98 | }) 99 | .beforeEach(clean), 100 | )); 101 | }); 102 | 103 | // Helpers 104 | 105 | const arrayIntArb = () => 106 | fc.record({ 107 | sign: fc.constantFrom(1 as const, -1 as const), 108 | data: fc.array(fc.integer({ min: 0, max: 0xffffffff })), 109 | }); 110 | 111 | function arrayIntToBigInt(arrayInt: ArrayInt): bigint { 112 | let current = BigInt(0); 113 | for (let index = 0; index < arrayInt.data.length; ++index) { 114 | current <<= BigInt(32); 115 | current += BigInt(arrayInt.data[index]); 116 | } 117 | return current * BigInt(arrayInt.sign); 118 | } 119 | 120 | function mockInternals(a: ArrayInt, b: ArrayInt) { 121 | const { unsafeUniformArrayIntDistributionInternal } = mocked(UnsafeUniformArrayIntDistributionInternalMock); 122 | unsafeUniformArrayIntDistributionInternal.mockImplementation((out, _rangeSize, _rng) => out); 123 | const [from, to] = arrayIntToBigInt(a) < arrayIntToBigInt(b) ? [a, b] : [b, a]; 124 | return { unsafeUniformArrayIntDistributionInternal, from, to }; 125 | } 126 | 127 | function extractParams( 128 | unsafeUniformArrayIntDistributionInternal: ReturnType< 129 | typeof mockInternals 130 | >['unsafeUniformArrayIntDistributionInternal'], 131 | ) { 132 | expect(unsafeUniformArrayIntDistributionInternal).toHaveBeenCalledTimes(1); 133 | expect(unsafeUniformArrayIntDistributionInternal).toHaveBeenCalledWith( 134 | expect.any(Array), 135 | expect.any(Array), 136 | expect.anything(), 137 | ); 138 | const params = unsafeUniformArrayIntDistributionInternal.mock.calls[0]; 139 | const [out, rangeSize, rng] = params; 140 | return { out, rangeSize, rng }; 141 | } 142 | -------------------------------------------------------------------------------- /perf/compare.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const { Bench } = require('tinybench'); 3 | const prand = require('../lib/pure-rand'); 4 | const Chance = require('chance'); 5 | const { faker } = require('@faker-js/faker'); 6 | const { Random, MersenneTwister19937 } = require('random-js'); 7 | var seedrandom = require('seedrandom'); 8 | 9 | // Algorithms under tests 10 | function fisherYates(data, rand) { 11 | // for i from n−1 downto 1 do 12 | //j ← random integer such that 0 ≤ j ≤ i 13 | //exchange a[j] and a[i] 14 | for (let i = data.length - 1; i >= 1; --i) { 15 | const j = rand(0, i); // such that 0 ≤ j ≤ i 16 | const tmp = data[j]; 17 | data[j] = data[i]; 18 | data[i] = tmp; 19 | } 20 | } 21 | 22 | async function run() { 23 | // Global Setup 24 | const numIterations = 1_000; 25 | const seed = Date.now() | 0; 26 | const bench = new Bench({ warmupIterations: Math.ceil(numIterations / 20), iterations: numIterations }); 27 | const data = [...Array(1_000_000)].map((_, i) => i); 28 | 29 | // Add algorithms (shuffling 1M items) 30 | bench.add('[native]', () => { 31 | const rand = (min, max) => { 32 | return min + Math.floor(Math.random() * (max - min + 1)); 33 | }; 34 | fisherYates(data, rand); 35 | }); 36 | bench.add('pure-rand (xorshift128plus) (not uniform)', () => { 37 | const g = prand.xorshift128plus(seed); 38 | const rand = (min, max) => { 39 | const out = (g.unsafeNext() >>> 0) / 0x1_0000_0000; 40 | return min + Math.floor(out * (max - min + 1)); 41 | }; 42 | fisherYates(data, rand); 43 | }); 44 | bench.add('pure-rand (xoroshiro128plus) (not uniform)', () => { 45 | const g = prand.xoroshiro128plus(seed); 46 | const rand = (min, max) => { 47 | const out = (g.unsafeNext() >>> 0) / 0x1_0000_0000; 48 | return min + Math.floor(out * (max - min + 1)); 49 | }; 50 | fisherYates(data, rand); 51 | }); 52 | bench.add('pure-rand (mersenne) (not uniform)', () => { 53 | const g = prand.mersenne(seed); 54 | const rand = (min, max) => { 55 | const out = (g.unsafeNext() >>> 0) / 0x1_0000_0000; 56 | return min + Math.floor(out * (max - min + 1)); 57 | }; 58 | fisherYates(data, rand); 59 | }); 60 | bench.add('pure-rand (congruential32) (not uniform)', () => { 61 | const g = prand.congruential32(seed); 62 | const rand = (min, max) => { 63 | const out = (g.unsafeNext() >>> 0) / 0x1_0000_0000; 64 | return min + Math.floor(out * (max - min + 1)); 65 | }; 66 | fisherYates(data, rand); 67 | }); 68 | bench.add('pure-rand (xorshift128plus)', () => { 69 | const g = prand.xorshift128plus(seed); 70 | const rand = (min, max) => { 71 | return prand.unsafeUniformIntDistribution(min, max, g); 72 | }; 73 | fisherYates(data, rand); 74 | }); 75 | bench.add('pure-rand (xoroshiro128plus)', () => { 76 | const g = prand.xoroshiro128plus(seed); 77 | const rand = (min, max) => { 78 | return prand.unsafeUniformIntDistribution(min, max, g); 79 | }; 80 | fisherYates(data, rand); 81 | }); 82 | bench.add('pure-rand (mersenne)', () => { 83 | const g = prand.mersenne(seed); 84 | const rand = (min, max) => { 85 | return prand.unsafeUniformIntDistribution(min, max, g); 86 | }; 87 | fisherYates(data, rand); 88 | }); 89 | bench.add('pure-rand (congruential32)', () => { 90 | const g = prand.congruential32(seed); 91 | const rand = (min, max) => { 92 | return prand.unsafeUniformIntDistribution(min, max, g); 93 | }; 94 | fisherYates(data, rand); 95 | }); 96 | bench.add('chance', () => { 97 | const chance = new Chance(seed); 98 | const rand = (min, max) => { 99 | return chance.integer({ min, max }); 100 | }; 101 | fisherYates(data, rand); 102 | }); 103 | bench.add('faker', () => { 104 | faker.seed(seed); 105 | const rand = (min, max) => { 106 | return faker.datatype.number({ min, max }); 107 | }; 108 | fisherYates(data, rand); 109 | }); 110 | bench.add('random-js', () => { 111 | const random = new Random(MersenneTwister19937.seed(seed)); 112 | const rand = (min, max) => { 113 | return random.integer(min, max); 114 | }; 115 | fisherYates(data, rand); 116 | }); 117 | bench.add('seedrandom (alea)', () => { 118 | const random = seedrandom.alea(String(seed)); 119 | const rand = (min, max) => { 120 | return min + Math.floor(random() * (max - min + 1)); 121 | }; 122 | fisherYates(data, rand); 123 | }); 124 | bench.add('seedrandom (xor128)', () => { 125 | const random = seedrandom.xor128(String(seed)); 126 | const rand = (min, max) => { 127 | return min + Math.floor(random() * (max - min + 1)); 128 | }; 129 | fisherYates(data, rand); 130 | }); 131 | bench.add('seedrandom (tychei)', () => { 132 | const random = seedrandom.tychei(String(seed)); 133 | const rand = (min, max) => { 134 | return min + Math.floor(random() * (max - min + 1)); 135 | }; 136 | fisherYates(data, rand); 137 | }); 138 | bench.add('seedrandom (xorwow)', () => { 139 | const random = seedrandom.xorwow(String(seed)); 140 | const rand = (min, max) => { 141 | return min + Math.floor(random() * (max - min + 1)); 142 | }; 143 | fisherYates(data, rand); 144 | }); 145 | bench.add('seedrandom (xor4096)', () => { 146 | const random = seedrandom.xor4096(String(seed)); 147 | const rand = (min, max) => { 148 | return min + Math.floor(random() * (max - min + 1)); 149 | }; 150 | fisherYates(data, rand); 151 | }); 152 | bench.add('seedrandom (xorshift7)', () => { 153 | const random = seedrandom.xorshift7(String(seed)); 154 | const rand = (min, max) => { 155 | return min + Math.floor(random() * (max - min + 1)); 156 | }; 157 | fisherYates(data, rand); 158 | }); 159 | 160 | // Run the benchmark 161 | await bench.warmup(); 162 | await bench.run(); 163 | 164 | // Log the results 165 | console.table( 166 | bench.tasks.map(({ name, result }) => { 167 | return { Library: name, Mean: result?.mean }; 168 | }), 169 | ); 170 | } 171 | run(); 172 | -------------------------------------------------------------------------------- /CHANGELOG_6.X.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 6.X 2 | 3 | ## 6.1.0 4 | 5 | ### Features 6 | 7 | - [c60c828](https://github.com/dubzzz/pure-rand/commit/c60c828) ✨ Clone from state on `xorshift128plus` (#697) 8 | - [6a16bfe](https://github.com/dubzzz/pure-rand/commit/6a16bfe) ✨ Clone from state on `mersenne` (#698) 9 | - [fb78e2d](https://github.com/dubzzz/pure-rand/commit/fb78e2d) ✨ Clone from state on `xoroshiro128plus` (#699) 10 | - [a7dd56c](https://github.com/dubzzz/pure-rand/commit/a7dd56c) ✨ Clone from state on congruential32 (#696) 11 | - [1f6c3a5](https://github.com/dubzzz/pure-rand/commit/1f6c3a5) 🏷️ Expose internal state of generators (#694) 12 | 13 | ### Fixes 14 | 15 | - [30d439a](https://github.com/dubzzz/pure-rand/commit/30d439a) 💚 Fix broken lock file (#695) 16 | - [9f935ae](https://github.com/dubzzz/pure-rand/commit/9f935ae) 👷 Speed-up CI with better cache (#677) 17 | 18 | ## 6.0.4 19 | 20 | ### Fixes 21 | 22 | - [716e073](https://github.com/dubzzz/pure-rand/commit/716e073) 🐛 Fix typings for node native esm (#649) 23 | 24 | ## 6.0.3 25 | 26 | ### Fixes 27 | 28 | - [9aca792](https://github.com/dubzzz/pure-rand/commit/9aca792) 🏷️ Better declare ESM's types (#634) 29 | 30 | ## 6.0.2 31 | 32 | ### Fixes 33 | 34 | - [6d05e8f](https://github.com/dubzzz/pure-rand/commit/6d05e8f) 🔐 Sign published packages (#591) 35 | - [8b4e165](https://github.com/dubzzz/pure-rand/commit/8b4e165) 👷 Switch default to Node 18 in CI (#578) 36 | 37 | ## 6.0.1 38 | 39 | ### Fixes 40 | 41 | - [05421f2](https://github.com/dubzzz/pure-rand/commit/05421f2) 🚨 Reformat README.md (#563) 42 | - [ffacfbd](https://github.com/dubzzz/pure-rand/commit/ffacfbd) 📝 Give simple seed computation example (#562) 43 | - [e432d59](https://github.com/dubzzz/pure-rand/commit/e432d59) 📝 Add extra keywords (#561) 44 | - [f5b18d4](https://github.com/dubzzz/pure-rand/commit/f5b18d4) 🐛 Declare types first for package (#560) 45 | - [a5b30db](https://github.com/dubzzz/pure-rand/commit/a5b30db) 📝 Final clean-up of the README (#559) 46 | - [5254ee0](https://github.com/dubzzz/pure-rand/commit/5254ee0) 📝 Fix simple examples not fully working (#558) 47 | - [8daf460](https://github.com/dubzzz/pure-rand/commit/8daf460) 📝 Clarify the README (#556) 48 | - [a915b6a](https://github.com/dubzzz/pure-rand/commit/a915b6a) 📝 Fix url error in README for logo (#554) 49 | - [f94885c](https://github.com/dubzzz/pure-rand/commit/f94885c) 📝 Rework README header with logo (#553) 50 | - [5f7645e](https://github.com/dubzzz/pure-rand/commit/5f7645e) 📝 Typo in link to comparison SVG (#551) 51 | - [61726af](https://github.com/dubzzz/pure-rand/commit/61726af) 📝 Better keywords for NPM (#550) 52 | - [6001e5a](https://github.com/dubzzz/pure-rand/commit/6001e5a) 📝 Update performance section with recent stats (#549) 53 | - [556ec33](https://github.com/dubzzz/pure-rand/commit/556ec33) ⚗️ Rewrite not uniform of pure-rand (#547) 54 | - [b3dfea5](https://github.com/dubzzz/pure-rand/commit/b3dfea5) ⚗️ Add more libraries to the experiment (#546) 55 | - [ac8b85d](https://github.com/dubzzz/pure-rand/commit/ac8b85d) ⚗️ Add some more non-uniform versions (#543) 56 | - [44af2ad](https://github.com/dubzzz/pure-rand/commit/44af2ad) ⚗️ Add some more self comparisons (#542) 57 | - [6d3342d](https://github.com/dubzzz/pure-rand/commit/6d3342d) 📝 Add some more details on the algorithms in compare (#541) 58 | - [359e214](https://github.com/dubzzz/pure-rand/commit/359e214) 📝 Fix some typos in README (#540) 59 | - [28a7bfe](https://github.com/dubzzz/pure-rand/commit/28a7bfe) 📝 Document some performance stats (#539) 60 | - [81860b7](https://github.com/dubzzz/pure-rand/commit/81860b7) ⚗️ Measure performance against other libraries (#538) 61 | - [114c2c7](https://github.com/dubzzz/pure-rand/commit/114c2c7) 📝 Publish changelogs from 3.X to 6.X (#537) 62 | 63 | ## 6.0.0 64 | 65 | ### Breaking Changes 66 | 67 | - [c45912f](https://github.com/dubzzz/pure-rand/commit/c45912f) 💥 Require generators uniform in int32 (#513) 68 | - [0bde03e](https://github.com/dubzzz/pure-rand/commit/0bde03e) 💥 Drop congruencial generator (#511) 69 | 70 | ### Features 71 | 72 | - [7587984](https://github.com/dubzzz/pure-rand/commit/7587984) ⚡️ Faster uniform distribution on bigint (#517) 73 | - [464960a](https://github.com/dubzzz/pure-rand/commit/464960a) ⚡️ Faster uniform distribution on small ranges (#516) 74 | - [b4852a8](https://github.com/dubzzz/pure-rand/commit/b4852a8) ⚡️ Faster Congruencial 32bits (#512) 75 | - [fdb6ec8](https://github.com/dubzzz/pure-rand/commit/fdb6ec8) ⚡️ Faster Mersenne-Twister (#510) 76 | - [bb69be5](https://github.com/dubzzz/pure-rand/commit/bb69be5) ⚡️ Drop infinite loop for explicit loop (#507) 77 | 78 | ### Fixes 79 | 80 | - [00fc62b](https://github.com/dubzzz/pure-rand/commit/00fc62b) 🔨 Add missing benchType to the script (#522) 81 | - [db4a0a6](https://github.com/dubzzz/pure-rand/commit/db4a0a6) 🔨 Add more options to benchmark (#521) 82 | - [5c1ca0e](https://github.com/dubzzz/pure-rand/commit/5c1ca0e) 🔨 Fix typo in benchmark code (#520) 83 | - [36c965f](https://github.com/dubzzz/pure-rand/commit/36c965f) 👷 Define a benchmark workflow (#519) 84 | - [0281cfd](https://github.com/dubzzz/pure-rand/commit/0281cfd) 🔨 More customizable benchmark (#518) 85 | - [a7e19a8](https://github.com/dubzzz/pure-rand/commit/a7e19a8) 🔥 Clean internals of uniform distribution (#515) 86 | - [520cca7](https://github.com/dubzzz/pure-rand/commit/520cca7) 🔨 Add some more benchmarks (#514) 87 | - [c2d6ee6](https://github.com/dubzzz/pure-rand/commit/c2d6ee6) 🔨 Fix typo in bench for large reference (#509) 88 | - [2dd7280](https://github.com/dubzzz/pure-rand/commit/2dd7280) 🔥 Clean useless variable (#506) 89 | - [dd621c9](https://github.com/dubzzz/pure-rand/commit/dd621c9) 🔨 Adapt benchmarks to make them reliable (#505) 90 | - [122f968](https://github.com/dubzzz/pure-rand/commit/122f968) 👷 Drop dependabot 91 | - [f11d2e8](https://github.com/dubzzz/pure-rand/commit/f11d2e8) 💸 Add GitHub sponsors in repository's configuration 92 | - [6a23e48](https://github.com/dubzzz/pure-rand/commit/6a23e48) 👷 Stop running tests against node 12 (#486) 93 | - [cbefd3e](https://github.com/dubzzz/pure-rand/commit/cbefd3e) 🔧 Better configuration of prettier (#474) 94 | - [c6712d3](https://github.com/dubzzz/pure-rand/commit/c6712d3) 🔧 Configure Renovate (#470) 95 | -------------------------------------------------------------------------------- /test/unit/generator/XoroShiro.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as fc from 'fast-check'; 3 | 4 | import { xoroshiro128plus } from '../../../src/generator/XoroShiro'; 5 | import * as p from './RandomGenerator.properties'; 6 | 7 | describe('xoroshiro128plus', () => { 8 | it('Should produce the right sequence for seed=42', () => { 9 | let g = xoroshiro128plus(42); 10 | let data = []; 11 | for (let idx = 0; idx !== 100; ++idx) { 12 | const [v, nextG] = g.next(); 13 | data.push(v); 14 | g = nextG; 15 | } 16 | // should be equivalent to the following C code: 17 | // uint64_t s[] = { (uint64_t) (~42), ((uint64_t) 42) << 32 }; 18 | // uint64_t rotl(const uint64_t x, int k) { 19 | // return (x << k) | (x >> (64 - k)); 20 | // } 21 | // uint64_t next() { 22 | // const uint64_t s0 = s[0]; 23 | // uint64_t s1 = s[1]; 24 | // const uint64_t result = s0 + s1; 25 | // s1 ^= s0; 26 | // s[0] = rotl(s0, 24) ^ s1 ^ (s1 << 16); // a, b 27 | // s[1] = rotl(s1, 37); // c 28 | // return result & 0xffffffff; 29 | // } 30 | assert.deepEqual( 31 | data, 32 | [ 33 | 4294967253, 3587504873, 4286635183, 3511956468, 673719186, 1055838436, 982607204, 1805613139, 3223288787, 34 | 1244866785, 2728956151, 371855737, 3026236645, 761656985, 3623017146, 1674769232, 1260144694, 1416578544, 35 | 2676463084, 2327532132, 471399469, 3030140883, 3568270373, 1826979091, 469148973, 3655950307, 3683414099, 36 | 145605805, 1297033582, 3082414981, 1426789818, 3231579470, 3488546250, 1264086088, 2948764953, 372475665, 37 | 3412248164, 1493309586, 3927896870, 3919452640, 2709861855, 2298347239, 572622743, 2011037876, 3360650359, 38 | 1693810876, 1171187038, 3872489959, 3989285417, 2747878152, 773928046, 4189989944, 1112534369, 4090243208, 39 | 3154249958, 1333914584, 3040415146, 4032858677, 453868310, 825945095, 4289451331, 91466297, 1431128327, 40 | 3208131715, 1831493458, 1461061492, 236677200, 651954392, 3509171451, 2033752905, 2253549766, 1751887713, 41 | 4106536982, 3543831362, 2833653165, 2379144789, 2545941655, 3165371118, 300732224, 2117517824, 2796938915, 42 | 2864151717, 1141572753, 4186463190, 1556859054, 1314617775, 4077757361, 2161308990, 3777135249, 1363575427, 43 | 198627145, 3707137083, 4244826523, 3176117579, 881773079, 2488531002, 1345130922, 1379428837, 1687164873, 44 | 325336063, 45 | ].map((v) => v | 0), 46 | ); 47 | }); 48 | it('Should produce the right sequence after jump for seed=42', () => { 49 | let g = xoroshiro128plus(42).jump!(); 50 | let data = []; 51 | for (let idx = 0; idx !== 100; ++idx) { 52 | const [v, nextG] = g.next(); 53 | data.push(v); 54 | g = nextG; 55 | } 56 | // should be equivalent to the following C code (+previous): 57 | // void jump() { 58 | // static const uint64_t JUMP[] = { 0xdf900294d8f554a5, 0x170865df4b3201fc }; 59 | // uint64_t s0 = 0; 60 | // uint64_t s1 = 0; 61 | // for(int i = 0; i < sizeof JUMP / sizeof *JUMP; i++) { 62 | // for(int b = 0; b < 64; b++) { 63 | // if (JUMP[i] & UINT64_C(1) << b) { 64 | // s0 ^= s[0]; 65 | // s1 ^= s[1]; 66 | // } 67 | // next(); 68 | // } 69 | // } 70 | // s[0] = s0; 71 | // s[1] = s1; 72 | // } 73 | assert.deepEqual( 74 | data, 75 | [ 76 | 1900530380, 2341274553, 4162717490, 2793985206, 3278912033, 1720265279, 1825471876, 3286742441, 1587050712, 77 | 3950106747, 540536355, 991034460, 2981829782, 4159175603, 2930607761, 2509744087, 137421383, 4073225526, 78 | 1650357769, 3278907225, 1023629082, 1415062380, 1032291476, 630729539, 1062523753, 2745179945, 1492748476, 79 | 2700841735, 540597325, 3257104696, 2538947884, 3763735773, 3752663556, 75786448, 959579757, 794851477, 80 | 2028874214, 802763541, 501515614, 3011491499, 3209732194, 2106362488, 573325014, 692069843, 2018337928, 81 | 1079162215, 4086381254, 4010906511, 2073612605, 1843940750, 2647345033, 3519462589, 834294388, 1754260385, 82 | 3973457473, 4129446855, 2052775225, 2106507442, 4178200040, 1507482091, 831962939, 4036397176, 3450323052, 83 | 810857617, 1009339640, 544755776, 2015019841, 1590786875, 2300047967, 1153713139, 3461511701, 2255374235, 84 | 3041282447, 3874500660, 2365439220, 3476174909, 2804287165, 3529576764, 2482037719, 3758708190, 2041488362, 85 | 3953105597, 2604691846, 4241700961, 3231746381, 2481435019, 2100261339, 1442114730, 756514823, 316144959, 86 | 3160143158, 2910044562, 4048037725, 1229183044, 2685549593, 173816268, 3565373219, 4220080560, 3252249431, 87 | 2240144151, 88 | ].map((v) => v | 0), 89 | ); 90 | }); 91 | it('Should return the same sequence given same seeds', () => fc.assert(p.sameSeedSameSequences(xoroshiro128plus))); 92 | it('Should return the same sequence when built from state', () => 93 | fc.assert(p.clonedFromStateSameSequences(xoroshiro128plus))); 94 | it('Should return the same sequence if called twice', () => fc.assert(p.sameSequencesIfCallTwice(xoroshiro128plus))); 95 | it('Should generate values between -2**31 and 2**31 -1', () => fc.assert(p.valuesInRange(xoroshiro128plus))); 96 | it('Should not depend on ordering between jump and next', () => fc.assert(p.noOrderNextJump(xoroshiro128plus))); 97 | it('Should impact itself with unsafeNext', () => fc.assert(p.changeSelfWithUnsafeNext(xoroshiro128plus))); 98 | it('Should impact itself with unsafeJump', () => fc.assert(p.changeSelfWithUnsafeJump(xoroshiro128plus))); 99 | it('Should not impact itself with next', () => fc.assert(p.noChangeSelfWithNext(xoroshiro128plus))); 100 | it('Should not impact itself with jump', () => fc.assert(p.noChangeSelfWithJump(xoroshiro128plus))); 101 | it('Should not impact clones when impacting itself on unsafeNext', () => 102 | fc.assert(p.noChangeOnClonedWithUnsafeNext(xoroshiro128plus))); 103 | it('Should not impact clones when impacting itself on unsafeJump', () => 104 | fc.assert(p.noChangeOnClonedWithUnsafeJump(xoroshiro128plus))); 105 | }); 106 | -------------------------------------------------------------------------------- /test/unit/generator/XorShift.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as fc from 'fast-check'; 3 | 4 | import { xorshift128plus } from '../../../src/generator/XorShift'; 5 | import * as p from './RandomGenerator.properties'; 6 | 7 | describe('xorshift128plus', () => { 8 | it('Should produce the right sequence for seed=42', () => { 9 | let g = xorshift128plus(42); 10 | let data = []; 11 | for (let idx = 0; idx !== 100; ++idx) { 12 | const [v, nextG] = g.next(); 13 | data.push(v); 14 | g = nextG; 15 | } 16 | // should be equivalent to the following C code: 17 | // uint64_t s[] = { (uint64_t) (~42), ((uint64_t) 42) << 32 }; 18 | // uint64_t next() { 19 | // uint64_t s1 = s[0]; 20 | // const uint64_t s0 = s[1]; 21 | // const uint64_t result = s0 + s1; 22 | // s[0] = s0; 23 | // s1 ^= s1 << 23; // a 24 | // s[1] = s1 ^ s0 ^ (s1 >> 18) ^ (s0 >> 5); // b, c 25 | // return result & 0xffffffff; 26 | // } 27 | // int main() { 28 | // for (int i = 0 ; i != 100 ; ++i) { std::cout << next() << ","; } 29 | // } 30 | assert.deepEqual( 31 | data, 32 | [ 33 | 4294967253, 1166015114, 1692303336, 3482095935, 4288634584, 1325520545, 1367235622, 1759582253, 2328126844, 34 | 649610899, 3328014937, 278910909, 2928761053, 1702820659, 3325106640, 2884641937, 2678880596, 999204680, 35 | 3611521009, 2947011440, 3416747393, 1634581895, 1067867852, 88558932, 2888797634, 2105694663, 1296024496, 36 | 2583386047, 2401573111, 2171058030, 657541993, 915947238, 696903927, 2687397535, 161811119, 793638981, 37 | 3330697646, 532898537, 3343714389, 1441469376, 1718504920, 1003802668, 3343696598, 1851708816, 2581827611, 38 | 1621906647, 3349035115, 264489114, 3672600296, 2277593912, 1157240243, 3243862613, 1246478095, 3690736557, 39 | 1366822450, 1444073535, 841971999, 1179174583, 3726899152, 817046495, 509174047, 1199791094, 2405463672, 40 | 827001813, 1820926848, 400677546, 1599444384, 3885120670, 2210955775, 2746964964, 211002306, 1381674843, 41 | 2689051285, 1702045018, 882144076, 553855887, 2369937669, 3656191263, 1560721536, 3818918581, 86283002, 42 | 3023862018, 1296400131, 625483410, 2364517346, 3034167893, 1805836022, 2782947729, 1110539129, 3221939945, 43 | 2436039688, 1150739462, 3430900671, 439413983, 4238985145, 3053101980, 1358457066, 1504768706, 2433376141, 44 | 4069088729, 45 | ].map((v) => v | 0), 46 | ); 47 | }); 48 | it('Should produce the right sequence after jump for seed=42', () => { 49 | let g = xorshift128plus(42).jump!(); 50 | let data = []; 51 | for (let idx = 0; idx !== 100; ++idx) { 52 | const [v, nextG] = g.next(); 53 | data.push(v); 54 | g = nextG; 55 | } 56 | // should be equivalent to the following C++ code (+previous): 57 | // void jump() { 58 | // static const uint64_t JUMP[] = { 0x8a5cd789635d2dff, 0x121fd2155c472f96 }; 59 | // uint64_t s0 = 0; 60 | // uint64_t s1 = 0; 61 | // for(int i = 0; i < sizeof JUMP / sizeof *JUMP; i++) { 62 | // for(int b = 0; b < 64; b++) { 63 | // if (JUMP[i] & UINT64_C(1) << b) { 64 | // s0 ^= s[0]; 65 | // s1 ^= s[1]; 66 | // } 67 | // next(); 68 | // } 69 | // } 70 | // s[0] = s0; 71 | // s[1] = s1; 72 | // } 73 | // int main() { 74 | // jump(); 75 | // for (int i = 0 ; i != 100 ; ++i) { std::cout << next() << ","; } 76 | // } 77 | assert.deepEqual( 78 | data, 79 | [ 80 | 2971276074, 3466165198, 456875496, 2879848137, 4162428146, 2513269982, 2277233661, 2163024882, 3082356668, 81 | 1459960119, 3225207140, 418458707, 465389025, 33345291, 9975393, 1398264340, 2941704490, 4219353700, 1050887263, 82 | 3537623901, 1011298813, 2886999094, 2095512742, 719748796, 2031575611, 246165700, 306697934, 932458853, 83 | 3811330946, 2780216938, 3008525324, 1217535119, 3060075487, 3829564179, 1862997734, 3188200581, 3652713690, 84 | 1950714292, 2865049298, 1937705104, 1297917374, 1333060788, 4089226157, 205959794, 2227024661, 3714058862, 85 | 3728103989, 3728972300, 659396325, 3185943613, 1039549819, 2822001969, 406983436, 2343603502, 299506842, 86 | 383551218, 698423599, 3611096673, 3762219019, 2293131106, 373955997, 2445208504, 3057049169, 1255899189, 87 | 4215756297, 957357335, 3456668141, 1989928862, 3510600746, 3806106322, 2542824253, 3920739152, 2851853721, 88 | 4208037803, 1276020689, 3735104409, 3925674077, 3708482212, 4262192769, 2607567703, 531897672, 3317376658, 89 | 1903132291, 353686572, 509303103, 3991810724, 1786729004, 1709580489, 550755974, 1081340778, 1040041085, 90 | 2723398036, 3276389190, 506083384, 2810025290, 3110824839, 3094567277, 2333614204, 3869414189, 678984428, 91 | ].map((v) => v | 0), 92 | ); 93 | }); 94 | it('Should return the same sequence given same seeds', () => fc.assert(p.sameSeedSameSequences(xorshift128plus))); 95 | it('Should return the same sequence when built from state', () => 96 | fc.assert(p.clonedFromStateSameSequences(xorshift128plus))); 97 | it('Should return the same sequence if called twice', () => fc.assert(p.sameSequencesIfCallTwice(xorshift128plus))); 98 | it('Should generate values between -2**31 and 2**31 -1', () => fc.assert(p.valuesInRange(xorshift128plus))); 99 | it('Should not depend on ordering between jump and next', () => fc.assert(p.noOrderNextJump(xorshift128plus))); 100 | it('Should impact itself with unsafeNext', () => fc.assert(p.changeSelfWithUnsafeNext(xorshift128plus))); 101 | it('Should impact itself with unsafeJump', () => fc.assert(p.changeSelfWithUnsafeJump(xorshift128plus))); 102 | it('Should not impact itself with next', () => fc.assert(p.noChangeSelfWithNext(xorshift128plus))); 103 | it('Should not impact itself with jump', () => fc.assert(p.noChangeSelfWithJump(xorshift128plus))); 104 | it('Should not impact clones when impacting itself on unsafeNext', () => 105 | fc.assert(p.noChangeOnClonedWithUnsafeNext(xorshift128plus))); 106 | it('Should not impact clones when impacting itself on unsafeJump', () => 107 | fc.assert(p.noChangeOnClonedWithUnsafeJump(xorshift128plus))); 108 | }); 109 | -------------------------------------------------------------------------------- /test/unit/generator/RandomGenerator.properties.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as fc from 'fast-check'; 3 | 4 | import { RandomGenerator } from '../../../src/types/RandomGenerator'; 5 | import { skipN } from '../../../src/distribution/SkipN'; 6 | import { generateN } from '../../../src/distribution/GenerateN'; 7 | import { unsafeSkipN } from '../../../src/distribution/UnsafeSkipN'; 8 | 9 | const MAX_SIZE: number = 2048; 10 | 11 | export function sameSeedSameSequences(rng_for: (seed: number) => RandomGenerator) { 12 | return fc.property(fc.integer(), fc.nat(MAX_SIZE), fc.nat(MAX_SIZE), (seed, offset, num) => { 13 | const seq1 = generateN(skipN(rng_for(seed), offset), num)[0]; 14 | const seq2 = generateN(skipN(rng_for(seed), offset), num)[0]; 15 | assert.deepEqual(seq1, seq2); 16 | }); 17 | } 18 | 19 | export function sameSequencesIfCallTwice(rng_for: (seed: number) => RandomGenerator) { 20 | return fc.property(fc.integer(), fc.nat(MAX_SIZE), fc.nat(MAX_SIZE), (seed, offset, num) => { 21 | const rng = skipN(rng_for(seed), offset); 22 | const seq1 = generateN(rng, num)[0]; 23 | const seq2 = generateN(rng, num)[0]; 24 | assert.deepEqual(seq1, seq2); 25 | }); 26 | } 27 | 28 | export function valuesInRange(rng_for: (seed: number) => RandomGenerator) { 29 | return fc.property(fc.integer(), fc.nat(MAX_SIZE), (seed, offset) => { 30 | const rng = rng_for(seed); 31 | const value = skipN(rng, offset).next()[0]; 32 | assert.ok(value >= -0x80000000); 33 | assert.ok(value <= 0x7fffffff); 34 | }); 35 | } 36 | 37 | export function noOrderNextJump(rng_for: (seed: number) => RandomGenerator) { 38 | return fc.property(fc.integer(), fc.nat(MAX_SIZE), (seed, offset) => { 39 | const rng = rng_for(seed); 40 | // rngNextFirst = rng.next.next..(offset times)..next.jump 41 | const rngNextFirst = skipN(rng, offset).jump!(); 42 | // rngJumpFirst = rng.jump.next.next..(offset times)..next 43 | const rngJumpFirst = skipN(rng.jump!(), offset); 44 | expect(rngNextFirst.next()[0]).toBe(rngJumpFirst.next()[0]); 45 | }); 46 | } 47 | 48 | export function changeSelfWithUnsafeNext(rng_for: (seed: number) => RandomGenerator) { 49 | return fc.property(fc.integer(), fc.nat(MAX_SIZE), (seed, offset) => { 50 | // Arrange 51 | const [expectedValue, expectedNextRng] = skipN(rng_for(seed), offset).next(); 52 | const rng = rng_for(seed); 53 | unsafeSkipN(rng, offset); 54 | const rngReprBefore = JSON.stringify(rng); 55 | const expectedRngReprAfter = JSON.stringify(expectedNextRng); 56 | 57 | // Act 58 | const value = rng.unsafeNext(); 59 | const rngReprAfter = JSON.stringify(rng); 60 | 61 | // Assert 62 | expect(value).toBe(expectedValue); 63 | expect(rngReprAfter).not.toBe(rngReprBefore); 64 | expect(rngReprAfter).toBe(expectedRngReprAfter); 65 | }); 66 | } 67 | 68 | export function changeSelfWithUnsafeJump(rng_for: (seed: number) => RandomGenerator) { 69 | return fc.property(fc.integer(), fc.nat(MAX_SIZE), (seed, offset) => { 70 | // Arrange 71 | const expectedJumpRng = skipN(rng_for(seed), offset).jump!(); 72 | const rng = rng_for(seed); 73 | unsafeSkipN(rng, offset); 74 | const rngReprBefore = JSON.stringify(rng); 75 | const expectedRngReprAfter = JSON.stringify(expectedJumpRng); 76 | 77 | // Act 78 | rng.unsafeJump!(); 79 | const rngReprAfter = JSON.stringify(rng); 80 | 81 | // Assert 82 | expect(rngReprAfter).not.toBe(rngReprBefore); 83 | expect(rngReprAfter).toBe(expectedRngReprAfter); 84 | }); 85 | } 86 | 87 | export function noChangeSelfWithNext(rng_for: (seed: number) => RandomGenerator) { 88 | return fc.property(fc.integer(), fc.nat(MAX_SIZE), (seed, offset) => { 89 | // Arrange 90 | const rng = rng_for(seed); 91 | unsafeSkipN(rng, offset); 92 | const rngReprBefore = JSON.stringify(rng); 93 | 94 | // Act 95 | rng.next(); 96 | const rngReprAfter = JSON.stringify(rng); 97 | 98 | // Assert 99 | expect(rngReprAfter).toBe(rngReprBefore); 100 | }); 101 | } 102 | 103 | export function noChangeSelfWithJump(rng_for: (seed: number) => RandomGenerator) { 104 | return fc.property(fc.integer(), fc.nat(MAX_SIZE), (seed, offset) => { 105 | // Arrange 106 | const rng = rng_for(seed); 107 | unsafeSkipN(rng, offset); 108 | const rngReprBefore = JSON.stringify(rng); 109 | 110 | // Act 111 | rng.jump!(); 112 | const rngReprAfter = JSON.stringify(rng); 113 | 114 | // Assert 115 | expect(rngReprAfter).toBe(rngReprBefore); 116 | }); 117 | } 118 | 119 | export function noChangeOnClonedWithUnsafeNext(rng_for: (seed: number) => RandomGenerator) { 120 | return fc.property(fc.integer(), fc.nat(MAX_SIZE), (seed, offset) => { 121 | // Arrange 122 | const rng = rng_for(seed); 123 | unsafeSkipN(rng, offset); 124 | const rngCloned = rng.clone(); 125 | const rngReprBefore = JSON.stringify(rng); 126 | const rngClonedReprBefore = JSON.stringify(rngCloned); 127 | 128 | // Act 129 | rng.unsafeNext(); 130 | const rngReprAfter = JSON.stringify(rng); 131 | const rngClonedReprAfter = JSON.stringify(rngCloned); 132 | 133 | // Assert 134 | expect(rngClonedReprBefore).toBe(rngReprBefore); 135 | expect(rngClonedReprAfter).toBe(rngReprBefore); 136 | expect(rngClonedReprAfter).not.toBe(rngReprAfter); 137 | }); 138 | } 139 | 140 | export function noChangeOnClonedWithUnsafeJump(rng_for: (seed: number) => RandomGenerator) { 141 | return fc.property(fc.integer(), fc.nat(MAX_SIZE), (seed, offset) => { 142 | // Arrange 143 | const rng = rng_for(seed); 144 | unsafeSkipN(rng, offset); 145 | const rngCloned = rng.clone(); 146 | const rngReprBefore = JSON.stringify(rng); 147 | const rngClonedReprBefore = JSON.stringify(rngCloned); 148 | 149 | // Act 150 | rng.unsafeJump!(); 151 | const rngReprAfter = JSON.stringify(rng); 152 | const rngClonedReprAfter = JSON.stringify(rngCloned); 153 | 154 | // Assert 155 | expect(rngClonedReprBefore).toBe(rngReprBefore); 156 | expect(rngClonedReprAfter).toBe(rngReprBefore); 157 | expect(rngClonedReprAfter).not.toBe(rngReprAfter); 158 | }); 159 | } 160 | 161 | export function clonedFromStateSameSequences( 162 | rng_for: ((seed: number) => RandomGenerator) & { fromState: (state: readonly number[]) => RandomGenerator }, 163 | ) { 164 | return fc.property(fc.integer(), fc.nat(MAX_SIZE), fc.nat(MAX_SIZE), (seed, offset, num) => { 165 | const source = skipN(rng_for(seed), offset); 166 | assert.notEqual(source.getState, undefined); 167 | const state = source.getState!(); 168 | const clonedFromState = rng_for.fromState(state); 169 | const seq1 = generateN(source, num)[0]; 170 | const seq2 = generateN(clonedFromState, num)[0]; 171 | assert.deepEqual(seq1, seq2); 172 | }); 173 | } 174 | -------------------------------------------------------------------------------- /test/unit/distribution/UniformIntDistribution.spec.ts: -------------------------------------------------------------------------------- 1 | import * as fc from 'fast-check'; 2 | import { mocked } from '../../__test-helpers__/mocked'; 3 | 4 | import { uniformIntDistribution } from '../../../src/distribution/UniformIntDistribution'; 5 | import { RandomGenerator } from '../../../src/types/RandomGenerator'; 6 | 7 | import * as UnsafeUniformIntDistributionInternalMock from '../../../src/distribution/internals/UnsafeUniformIntDistributionInternal'; 8 | import * as UnsafeUniformArrayIntDistributionInternalMock from '../../../src/distribution/internals/UnsafeUniformArrayIntDistributionInternal'; 9 | jest.mock('../../../src/distribution/internals/UnsafeUniformIntDistributionInternal'); 10 | jest.mock('../../../src/distribution/internals/UnsafeUniformArrayIntDistributionInternal'); 11 | 12 | function buildUniqueRng(clonedRng?: RandomGenerator) { 13 | if (clonedRng !== undefined) { 14 | return { 15 | clone() { 16 | return clonedRng; 17 | }, 18 | } as RandomGenerator; 19 | } 20 | return {} as RandomGenerator; 21 | } 22 | function clean() { 23 | jest.resetAllMocks(); 24 | jest.clearAllMocks(); 25 | } 26 | 27 | beforeEach(clean); 28 | describe('uniformIntDistribution', () => { 29 | describe('Small ranges (<= 2**32)', () => { 30 | it('Should call unsafeUniformIntDistributionInternal with correct size of range and source rng', () => 31 | fc.assert( 32 | fc 33 | .property(settingsArbitrary, (settings) => { 34 | // Arrange 35 | const { from, to, rng, clonedRng, unsafeUniformIntDistributionInternal } = mockInternals(settings); 36 | 37 | // Act 38 | uniformIntDistribution(from, to)(rng); 39 | 40 | // Assert 41 | expect(unsafeUniformIntDistributionInternal).toHaveBeenCalledTimes(1); 42 | expect(unsafeUniformIntDistributionInternal).toHaveBeenCalledWith(to - from + 1, clonedRng); 43 | }) 44 | .beforeEach(clean), 45 | )); 46 | 47 | it('Should offset by "from" the value produced by unsafeUniformIntDistributionInternal', () => 48 | fc.assert( 49 | fc 50 | .property(settingsArbitrary, (settings) => { 51 | // Arrange 52 | const { from, to, rng, outputs } = mockInternals(settings); 53 | 54 | // Act 55 | const [v, _nrng] = uniformIntDistribution(from, to)(rng); 56 | 57 | // Assert 58 | expect(v).toBe(outputs[0] + from); 59 | }) 60 | .beforeEach(clean), 61 | )); 62 | }); 63 | 64 | describe('Large ranges (> 2**32)', () => { 65 | it('Should call unsafeUniformIntDistributionInternal with correct size of range and source rng', () => 66 | fc.assert( 67 | fc 68 | .property(settingsLargeArbitrary, (settings) => { 69 | // Arrange 70 | const { from, to, rng, clonedRng, unsafeUniformArrayIntDistributionInternal } = 71 | mockLargeInternals(settings); 72 | 73 | // Act 74 | uniformIntDistribution(from, to)(rng); 75 | 76 | // Assert 77 | expect(unsafeUniformArrayIntDistributionInternal).toHaveBeenCalledTimes(1); 78 | expect(unsafeUniformArrayIntDistributionInternal).toHaveBeenCalledWith( 79 | expect.any(Array), 80 | expect.any(Array), 81 | clonedRng, 82 | ); 83 | const params = unsafeUniformArrayIntDistributionInternal.mock.calls[0]; 84 | const rangeSize = params[1]; 85 | expect(rangeSize[0] * 2 ** 32 + rangeSize[1]).toBe(to - from + 1); 86 | }) 87 | .beforeEach(clean), 88 | )); 89 | 90 | it('Should offset by "from" the value produced by unsafeUniformIntDistributionInternal', () => 91 | fc.assert( 92 | fc 93 | .property(settingsLargeArbitrary, (settings) => { 94 | // Arrange 95 | const { from, to, rng, outputs } = mockLargeInternals(settings); 96 | 97 | // Act 98 | const [v, _nrng] = uniformIntDistribution(from, to)(rng); 99 | 100 | // Assert 101 | expect(v).toBe(outputs[0][0] * 2 ** 32 + outputs[0][1] + from); 102 | }) 103 | .beforeEach(clean), 104 | )); 105 | }); 106 | 107 | it('Should return the rng passed to unsafeUniformIntDistributionInternal', () => 108 | fc.assert( 109 | fc 110 | .property(settingsArbitrary, (settings) => { 111 | // Arrange 112 | const { from, to, rng, clonedRng, outputs } = mockInternals(settings); 113 | 114 | // Act 115 | const [_v1, nrng] = uniformIntDistribution(from, to)(rng); 116 | 117 | // Assert 118 | expect(nrng).toBe(clonedRng); 119 | }) 120 | .beforeEach(clean), 121 | )); 122 | 123 | it('Should be equivalent to call the 2-parameter and 3-parameter', () => 124 | fc.assert( 125 | fc 126 | .property(settingsArbitrary, (settings) => { 127 | // Arrange 128 | const { from, to, rng } = mockInternals(settings); 129 | 130 | // Act 131 | const [v1, _nrng1] = uniformIntDistribution(from, to)(rng); 132 | const [v2, _nrng2] = uniformIntDistribution(from, to, rng); 133 | 134 | // Assert 135 | expect(v1).toBe(v2); 136 | }) 137 | .beforeEach(clean), 138 | )); 139 | }); 140 | 141 | // Helpers 142 | 143 | const settingsArbitrary = fc 144 | .record({ 145 | from: fc.maxSafeInteger(), 146 | gap: fc.integer({ min: 0, max: 0xffffffff }), 147 | rangeRandom: fc.noShrink(fc.nat()), 148 | ctx: fc.context(), 149 | }) 150 | .filter(({ from, gap }) => Number.isSafeInteger(from + gap)); 151 | 152 | type SettingsType = typeof settingsArbitrary extends fc.Arbitrary ? U : never; 153 | 154 | function mockInternals(settings: SettingsType) { 155 | const { unsafeUniformIntDistributionInternal } = mocked(UnsafeUniformIntDistributionInternalMock); 156 | 157 | const { from, gap, rangeRandom, ctx } = settings; 158 | const to = from + gap; 159 | const clonedRng = buildUniqueRng(); 160 | const rng = buildUniqueRng(clonedRng); 161 | const outputs: number[] = []; 162 | unsafeUniformIntDistributionInternal.mockImplementation((rangeSize) => { 163 | const out = rangeRandom % rangeSize; 164 | ctx.log(`unsafeUniformIntDistributionInternal(${rangeSize}) -> ${out}`); 165 | outputs.push(out); 166 | return out; 167 | }); 168 | 169 | return { from, to, rng, clonedRng, outputs, unsafeUniformIntDistributionInternal }; 170 | } 171 | 172 | const settingsLargeArbitrary = fc 173 | .record({ 174 | from: fc.maxSafeInteger(), 175 | gap: fc.integer({ min: 0x100000000, max: Number.MAX_SAFE_INTEGER }), 176 | rangeRandom: fc.noShrink(fc.nat()), 177 | ctx: fc.context(), 178 | }) 179 | .filter(({ from, gap }) => Number.isSafeInteger(from + gap)); 180 | 181 | type SettingsLargeType = typeof settingsLargeArbitrary extends fc.Arbitrary ? U : never; 182 | 183 | function mockLargeInternals(settings: SettingsLargeType) { 184 | const { unsafeUniformArrayIntDistributionInternal } = mocked(UnsafeUniformArrayIntDistributionInternalMock); 185 | 186 | const { from, gap, rangeRandom, ctx } = settings; 187 | const to = from + gap; 188 | const clonedRng = buildUniqueRng(); 189 | const rng = buildUniqueRng(clonedRng); 190 | const outputs: number[][] = []; 191 | unsafeUniformArrayIntDistributionInternal.mockImplementation((rangeSize) => { 192 | const out = rangeSize.map((r) => rangeRandom % (r || 1)); 193 | ctx.log(`unsafeUniformArrayIntDistributionInternal(${JSON.stringify(rangeSize)}) -> ${JSON.stringify(out)}`); 194 | outputs.push(out); 195 | return out; 196 | }); 197 | 198 | return { from, to, rng, clonedRng, outputs, unsafeUniformArrayIntDistributionInternal }; 199 | } 200 | -------------------------------------------------------------------------------- /perf/benchmark.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // This file is a sample snippet to run benchmark across versions 3 | // Run it: 4 | // $: yarn build:bench:old 5 | // $: yarn build:bench:new 6 | // $: yarn bench 7 | // 8 | // Or against another generator: 9 | // $: yarn bench mersenne 10 | // 11 | // Or only benchmark generators against each others on new: 12 | // $: yarn bench self 13 | // 14 | // Or only benchmarks on new against default generator: 15 | // $: yarn bench alone 16 | 17 | const { Bench } = require('tinybench'); 18 | 19 | const PROF_GEN = process.argv[2] || 'xoroshiro128plus'; 20 | const NUM_RUNS = process.argv[3] || ''; 21 | const NUM_INTS = process.argv[4] || ''; 22 | 23 | const numIterations = Number.isNaN(+NUM_RUNS) ? 1_000 : +NUM_RUNS; 24 | const numInts = Number.isNaN(+NUM_INTS) ? 100 : +NUM_INTS; 25 | 26 | console.info(`Generator : ${PROF_GEN}`); 27 | console.info(`Iterations : ${numIterations}`); 28 | console.info(`Numbers per run: ${numInts}`); 29 | 30 | function builtInNoDistribution(from, to) { 31 | const out = Math.random(); 32 | return from + ~~(out * (to - from + 1)); // ~~ is Math.floor 33 | } 34 | 35 | function noDistribution(from, to, g) { 36 | const out = g.unsafeNext() >>> 0; 37 | return from + (out % (to - from + 1)); 38 | } 39 | 40 | function fillBench(bench) { 41 | let seed = Date.now() | 0; 42 | const nextSeed = () => { 43 | seed = (seed + 1) | 0; 44 | return seed; 45 | }; 46 | const compareMode = PROF_GEN === 'self'; 47 | const configurations = compareMode 48 | ? [ 49 | { libName: 'test', generator: 'congruential32' }, 50 | { libName: 'test', generator: 'mersenne' }, 51 | { libName: 'test', generator: 'xorshift128plus' }, 52 | { libName: 'test', generator: 'xoroshiro128plus' }, 53 | ] 54 | : PROF_GEN === 'alone' 55 | ? [{ libName: 'test', generator: 'xoroshiro128plus' }] 56 | : [ 57 | { libName: 'reference', generator: PROF_GEN }, 58 | { libName: 'test', generator: PROF_GEN }, 59 | ]; 60 | 61 | const safeBench = (details, fn) => { 62 | const name = JSON.stringify(details); 63 | fn(); 64 | bench.add(name, fn); 65 | console.info(`✔️ ${name}`); 66 | }; 67 | 68 | safeBench({ range: 'S' }, () => { 69 | for (let i = 0; i !== numInts; ++i) { 70 | builtInNoDistribution(0, i); 71 | } 72 | }); 73 | safeBench({ range: 'M' }, () => { 74 | for (let i = 0; i !== numInts; ++i) { 75 | builtInNoDistribution(i, 0x7fff_ffff); 76 | } 77 | }); 78 | safeBench({ range: 'L' }, () => { 79 | for (let i = 0; i !== numInts; ++i) { 80 | builtInNoDistribution(-0x8000_0000, i); 81 | } 82 | }); 83 | for (const { libName, generator } of configurations) { 84 | const libReference = require('../lib-' + libName + '/pure-rand-default'); 85 | safeBench({ libName, generator, range: 'S' }, () => { 86 | const g = libReference[generator](nextSeed()); 87 | for (let i = 0; i !== numInts; ++i) { 88 | noDistribution(0, i, g); 89 | } 90 | }); 91 | safeBench({ libName, generator, range: 'M' }, () => { 92 | const g = libReference[generator](nextSeed()); 93 | for (let i = 0; i !== numInts; ++i) { 94 | noDistribution(i, 0x7fff_ffff, g); 95 | } 96 | }); 97 | safeBench({ libName, generator, range: 'L' }, () => { 98 | const g = libReference[generator](nextSeed()); 99 | for (let i = 0; i !== numInts; ++i) { 100 | noDistribution(-0x8000_0000, i, g); 101 | } 102 | }); 103 | if (!compareMode) { 104 | safeBench({ libName, generator, algorithm: 'uniform', range: 'S' }, () => { 105 | const g = libReference[generator](nextSeed()); 106 | for (let i = 0; i !== numInts; ++i) { 107 | libReference.unsafeUniformIntDistribution(0, i, g); 108 | } 109 | }); 110 | safeBench({ libName, generator, algorithm: 'uniform', range: 'M' }, () => { 111 | const g = libReference[generator](nextSeed()); 112 | for (let i = 0; i !== numInts; ++i) { 113 | libReference.unsafeUniformIntDistribution(i, 0x7fff_ffff, g); 114 | } 115 | }); 116 | safeBench({ libName, generator, algorithm: 'uniform', range: 'L' }, () => { 117 | const g = libReference[generator](nextSeed()); 118 | for (let i = 0; i !== numInts; ++i) { 119 | libReference.unsafeUniformIntDistribution(-0x8000_0000, i, g); 120 | } 121 | }); 122 | safeBench({ libName, generator, algorithm: 'uniform', range: 'XL' }, () => { 123 | const g = libReference[generator](nextSeed()); 124 | for (let i = 0; i !== numInts; ++i) { 125 | libReference.unsafeUniformIntDistribution(-0x1_0000_0000, i, g); 126 | } 127 | }); 128 | safeBench({ libName, generator, algorithm: 'array uniform', range: 'S' }, () => { 129 | const g = libReference[generator](nextSeed()); 130 | for (let i = 0; i !== numInts; ++i) { 131 | libReference.unsafeUniformArrayIntDistribution({ sign: 1, data: [0] }, { sign: 1, data: [i] }, g); 132 | } 133 | }); 134 | safeBench({ libName, generator, algorithm: 'array uniform', range: 'M' }, () => { 135 | const g = libReference[generator](nextSeed()); 136 | for (let i = 0; i !== numInts; ++i) { 137 | libReference.unsafeUniformArrayIntDistribution({ sign: 1, data: [i] }, { sign: 1, data: [0x7fff_ffff] }, g); 138 | } 139 | }); 140 | safeBench({ libName, generator, algorithm: 'array uniform', range: 'L' }, () => { 141 | const g = libReference[generator](nextSeed()); 142 | for (let i = 0; i !== numInts; ++i) { 143 | libReference.unsafeUniformArrayIntDistribution({ sign: -1, data: [0x8000_0000] }, { sign: 1, data: [i] }, g); 144 | } 145 | }); 146 | safeBench({ libName, generator, algorithm: 'array uniform', range: 'XL' }, () => { 147 | const g = libReference[generator](nextSeed()); 148 | for (let i = 0; i !== numInts; ++i) { 149 | libReference.unsafeUniformArrayIntDistribution({ sign: -1, data: [1, 0] }, { sign: 1, data: [0, i] }, g); 150 | } 151 | }); 152 | safeBench({ libName, generator, algorithm: 'bigint uniform', range: 'S' }, () => { 153 | const g = libReference[generator](nextSeed()); 154 | for (let i = 0; i !== numInts; ++i) { 155 | libReference.unsafeUniformBigIntDistribution(BigInt(0), BigInt(i), g); 156 | } 157 | }); 158 | safeBench({ libName, generator, algorithm: 'bigint uniform', range: 'M' }, () => { 159 | const g = libReference[generator](nextSeed()); 160 | for (let i = 0; i !== numInts; ++i) { 161 | libReference.unsafeUniformBigIntDistribution(BigInt(i), BigInt(0x7fff_ffff), g); 162 | } 163 | }); 164 | safeBench({ libName, generator, algorithm: 'bigint uniform', range: 'L' }, () => { 165 | const g = libReference[generator](nextSeed()); 166 | for (let i = 0; i !== numInts; ++i) { 167 | libReference.unsafeUniformBigIntDistribution(BigInt(-0x8000_0000), BigInt(i), g); 168 | } 169 | }); 170 | safeBench({ libName, generator, algorithm: 'bigint uniform', range: 'XL' }, () => { 171 | const g = libReference[generator](nextSeed()); 172 | for (let i = 0; i !== numInts; ++i) { 173 | libReference.unsafeUniformBigIntDistribution(BigInt(-0x1_0000_0000), BigInt(i), g); 174 | } 175 | }); 176 | safeBench({ libName, generator, algorithm: 'jump' }, () => { 177 | const g = libReference[generator](nextSeed()); 178 | for (let i = 0; i !== numInts; ++i) { 179 | g.unsafeJump(); 180 | } 181 | }); 182 | } 183 | } 184 | console.info(''); 185 | } 186 | 187 | async function runner() { 188 | const bench = new Bench({ warmupIterations: Math.ceil(numIterations / 20), iterations: numIterations }); 189 | fillBench(bench); 190 | await bench.warmup(); 191 | await bench.run(); 192 | 193 | console.table( 194 | bench.tasks.map(({ name, result }) => { 195 | const { libName, generator, algorithm, range } = JSON.parse(name); 196 | return { 197 | Library: libName ?? '—', 198 | RNG: generator ?? '—', 199 | Algorithm: algorithm ?? '—', 200 | Range: range ?? '—', 201 | Mean: result?.mean, 202 | P75: result?.p75, 203 | P99: result?.p99, 204 | RME: result?.rme, 205 | }; 206 | }), 207 | ); 208 | } 209 | runner(); 210 | -------------------------------------------------------------------------------- /test/unit/distribution/internals/UnsafeUniformArrayIntDistributionInternal.spec.ts: -------------------------------------------------------------------------------- 1 | import * as fc from 'fast-check'; 2 | import { mocked } from '../../../__test-helpers__/mocked'; 3 | 4 | import { unsafeUniformArrayIntDistributionInternal } from '../../../../src/distribution/internals/UnsafeUniformArrayIntDistributionInternal'; 5 | import { ArrayInt } from '../../../../src/distribution/internals/ArrayInt'; 6 | import { RandomGenerator } from '../../../../src/types/RandomGenerator'; 7 | 8 | import * as UnsafeUniformIntDistributionInternalMock from '../../../../src/distribution/internals/UnsafeUniformIntDistributionInternal'; 9 | jest.mock('../../../../src/distribution/internals/UnsafeUniformIntDistributionInternal'); 10 | 11 | function buildUniqueRng(clonedRng?: RandomGenerator) { 12 | if (clonedRng !== undefined) { 13 | return { 14 | clone() { 15 | return clonedRng; 16 | }, 17 | } as RandomGenerator; 18 | } 19 | return {} as RandomGenerator; 20 | } 21 | function clean() { 22 | jest.resetAllMocks(); 23 | jest.clearAllMocks(); 24 | } 25 | 26 | beforeEach(clean); 27 | describe('unsafeUniformArrayIntDistributionInternal', () => { 28 | it.each` 29 | rangeSize | resultingArrayInt | description 30 | ${[10, 20, 30]} | ${[1, 1, 1]} | ${'all generated values are smaller'} 31 | ${[10, 20, 30]} | ${[8, 520, 1000]} | ${'some generated values are greater but resulting array is smaller'} 32 | ${[10, 20, 30]} | ${[10, 20, 29]} | ${'resulting array is rangeSize minus one'} 33 | ${[1]} | ${[0]} | ${'smallest possible rangeSize'} 34 | ${[0, 0, 1, 0, 1, 0]} | ${[0, 0, 1, 0, 0, 1]} | ${'rangeSize starting by and including zeros'} 35 | `('Should only call the rangeSize.length times when $description', ({ rangeSize, resultingArrayInt }) => { 36 | // Arrange 37 | const { unsafeUniformIntDistributionInternal } = mocked(UnsafeUniformIntDistributionInternalMock); 38 | const initialRng = buildUniqueRng(); 39 | for (let idx = 0; idx !== resultingArrayInt.length; ++idx) { 40 | // In terms of calls, the expectedRangeSize is: 41 | // [ rangeSize[0] + 1 , 0x1_00000000 , ... ] 42 | // In other words, we can generate (with the same probability) any number in: 43 | // [ between 0 (inc) and rangeSize[0] (inc) , between 0 (inc) and 0xffffffff (inc) , ... ] 44 | // Values outside greater or equal to rangeSize will be rejected. We will retry until we get a valid value. 45 | const expectedRangeSize = idx === 0 ? rangeSize[0] + 1 : 0x100000000; 46 | const generatedItem = resultingArrayInt[idx]; 47 | unsafeUniformIntDistributionInternal.mockImplementationOnce((askedRangeSize, rng) => { 48 | expect(askedRangeSize).toBe(expectedRangeSize); 49 | expect(rng).toBe(initialRng); 50 | return generatedItem; 51 | }); 52 | } 53 | 54 | // Act 55 | const g = unsafeUniformArrayIntDistributionInternal(arrayIntBuffer(rangeSize.length).data, rangeSize, initialRng); 56 | 57 | // Assert 58 | expect(g).toEqual(resultingArrayInt); 59 | expect(unsafeUniformIntDistributionInternal).toHaveBeenCalledTimes(resultingArrayInt.length); 60 | }); 61 | 62 | it.each` 63 | rangeSize | rejections | resultingArrayInt | description 64 | ${[10, 20, 30]} | ${[10, 20, 30]} | ${[1, 1, 1]} | ${'first generated value is the rangeSize itself'} 65 | ${[10, 20, 30]} | ${[10, 50, 0]} | ${[1, 1, 1]} | ${'first generated value is greater than the rangeSize due to middle item'} 66 | ${[10, 20, 30]} | ${[10, 20, 50]} | ${[1, 1, 1]} | ${'first generated value is greater than the rangeSize due to last item'} 67 | ${[10, 20, 30]} | ${[10, 100, 1000, 10, 100, 1000, 10, 100, 1000]} | ${[1, 1, 1]} | ${'multiple rejections in a row'} 68 | `('Should retry until we get a valid value when $description', ({ rangeSize, rejections, resultingArrayInt }) => { 69 | // Arrange 70 | const { unsafeUniformIntDistributionInternal } = mocked(UnsafeUniformIntDistributionInternalMock); 71 | const initialRng = buildUniqueRng(); 72 | for (let idx = 0; idx !== rejections.length; ++idx) { 73 | // Rq: We check on `idx % rangeSize.length === 0` as our surrent implementation does not quit earlier. 74 | // It will generate a full value before rejecting it. 75 | const expectedRangeSize = idx % rangeSize.length === 0 ? rangeSize[0] + 1 : 0x100000000; 76 | const generatedItem = rejections[idx]; 77 | unsafeUniformIntDistributionInternal.mockImplementationOnce((askedRangeSize, rng) => { 78 | expect(askedRangeSize).toBe(expectedRangeSize); 79 | expect(rng).toBe(initialRng); 80 | return generatedItem; 81 | }); 82 | } 83 | for (let idx = 0; idx !== resultingArrayInt.length; ++idx) { 84 | const expectedRangeSize = idx === 0 ? rangeSize[0] + 1 : 0x100000000; 85 | const generatedItem = resultingArrayInt[idx]; 86 | unsafeUniformIntDistributionInternal.mockImplementationOnce((askedRangeSize, rng) => { 87 | expect(askedRangeSize).toBe(expectedRangeSize); 88 | expect(rng).toBe(initialRng); 89 | return generatedItem; 90 | }); 91 | } 92 | 93 | // Act 94 | const g = unsafeUniformArrayIntDistributionInternal(arrayIntBuffer(rangeSize.length).data, rangeSize, initialRng); 95 | 96 | // Assert 97 | expect(g).toEqual(resultingArrayInt); 98 | expect(unsafeUniformIntDistributionInternal).toHaveBeenCalledTimes(rejections.length + resultingArrayInt.length); 99 | }); 100 | 101 | it('Should call unsafeUniformIntDistributionInternal until it produces a valid ArrayInt', () => 102 | // Identical to the test above "Should retry until we get a valid value when $description" but with property based testing 103 | fc.assert( 104 | fc 105 | .property( 106 | fc 107 | .bigInt({ min: BigInt(1) }) 108 | .chain((rangeSize) => 109 | fc.record({ 110 | rangeSize: fc.constant(rangeSize), 111 | rejectedValues: fc.array(fc.bigInt({ min: rangeSize })), 112 | validValue: fc.bigInt({ min: BigInt(0), max: rangeSize - BigInt(1) }), 113 | }), 114 | ) 115 | .map(({ rangeSize, rejectedValues, validValue }) => { 116 | let rangeSizeArrayIntData = fromBigUintToArrayIntData(rangeSize); 117 | let validValueArrayIntData = fromBigUintToArrayIntData(validValue); 118 | let rejectedValuesArrayIntData = rejectedValues.map(fromBigUintToArrayIntData); 119 | const maxDataLength = [ 120 | rangeSizeArrayIntData, 121 | validValueArrayIntData, 122 | ...rejectedValuesArrayIntData, 123 | ].reduce((acc, data) => Math.max(acc, data.length), 0); 124 | rangeSizeArrayIntData = padDataOfArrayInt(rangeSizeArrayIntData, maxDataLength); 125 | validValueArrayIntData = padDataOfArrayInt(validValueArrayIntData, maxDataLength); 126 | rejectedValuesArrayIntData = rejectedValuesArrayIntData.map((v) => padDataOfArrayInt(v, maxDataLength)); 127 | return { 128 | rangeSize: rangeSizeArrayIntData, 129 | rejectedValues: rejectedValuesArrayIntData, 130 | validValue: validValueArrayIntData, 131 | }; 132 | }), 133 | fc.context(), 134 | ({ rangeSize, rejectedValues, validValue }, ctx) => { 135 | // Arrange 136 | const initialRng = buildUniqueRng(); 137 | for (const rejected of rejectedValues) { 138 | // Our mock for unsafeUniformIntDistributionInternal starts by producing invalid values 139 | // (too large for the requested range). 140 | // All those values should be rejected by unsafeUniformArrayIntDistributionInternal. 141 | // Internally unsafeUniformArrayIntDistributionInternal do not optimize calls (for the moment) 142 | // it always queries for all the data then checks if the data is ok or not given rangeSize. 143 | // In other words, if rangeSize = [1,1,1], the algorithm will query for at least three entries 144 | // even if it can stop earlier [1,2,1,...] (when receiving the 2, we know it will not fit our needs). 145 | mockResponse(rejected, initialRng, ctx); 146 | } 147 | mockResponse(validValue, initialRng, ctx); 148 | mockRejectNextCalls(ctx); 149 | 150 | // Act 151 | const g = unsafeUniformArrayIntDistributionInternal( 152 | arrayIntBuffer(rangeSize.length).data, 153 | rangeSize, 154 | initialRng, 155 | ); 156 | 157 | // Assert 158 | expect(g).toEqual(validValue); 159 | }, 160 | ) 161 | .beforeEach(clean), 162 | )); 163 | }); 164 | 165 | // Helpers 166 | 167 | function arrayIntBuffer(size: number): ArrayInt { 168 | return { sign: 1, data: Array(size).fill(0) }; 169 | } 170 | 171 | function fromBigUintToArrayIntData(n: bigint): ArrayInt['data'] { 172 | const data: number[] = []; 173 | const repr = n.toString(16); 174 | for (let sectionEnd = repr.length; sectionEnd > 0; sectionEnd -= 8) { 175 | data.push(parseInt(repr.substring(sectionEnd - 8, sectionEnd), 16)); 176 | } 177 | return data.reverse(); 178 | } 179 | 180 | function padDataOfArrayInt(arrayIntData: ArrayInt['data'], length: number): ArrayInt['data'] { 181 | return [...Array(length - arrayIntData.length).fill(0), ...arrayIntData]; 182 | } 183 | 184 | function mockResponse(arrayIntData: ArrayInt['data'], clonedRng: RandomGenerator, ctx: fc.ContextValue) { 185 | const { unsafeUniformIntDistributionInternal } = mocked(UnsafeUniformIntDistributionInternalMock); 186 | for (const item of arrayIntData) { 187 | unsafeUniformIntDistributionInternal.mockImplementationOnce((rangeSize, rng) => { 188 | expect(rng).toBe(clonedRng); 189 | ctx.log(`unsafeUniformIntDistributionInternal(${rangeSize}) -> ${item}`); 190 | return item; 191 | }); 192 | } 193 | } 194 | 195 | function mockRejectNextCalls(ctx: fc.ContextValue) { 196 | const { unsafeUniformIntDistributionInternal } = mocked(UnsafeUniformIntDistributionInternalMock); 197 | unsafeUniformIntDistributionInternal.mockImplementationOnce((rangeSize, _rng) => { 198 | ctx.log(`unsafeUniformIntDistributionInternal(${rangeSize}) -> [..., ]`); 199 | throw new Error('No more calls expected'); 200 | }); 201 | } 202 | -------------------------------------------------------------------------------- /test/unit/distribution/__snapshots__/UniformArrayIntDistribution.noreg.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`uniformArrayIntDistribution [non regression] Should not change its output in asked range except for major bumps (3 states [-1, 0, 1]) 1`] = ` 4 | [ 5 | { 6 | "data": [ 7 | 1, 8 | ], 9 | "sign": -1, 10 | }, 11 | { 12 | "data": [ 13 | 0, 14 | ], 15 | "sign": 1, 16 | }, 17 | { 18 | "data": [ 19 | 1, 20 | ], 21 | "sign": -1, 22 | }, 23 | { 24 | "data": [ 25 | 0, 26 | ], 27 | "sign": 1, 28 | }, 29 | { 30 | "data": [ 31 | 0, 32 | ], 33 | "sign": 1, 34 | }, 35 | { 36 | "data": [ 37 | 1, 38 | ], 39 | "sign": 1, 40 | }, 41 | { 42 | "data": [ 43 | 1, 44 | ], 45 | "sign": -1, 46 | }, 47 | { 48 | "data": [ 49 | 1, 50 | ], 51 | "sign": 1, 52 | }, 53 | { 54 | "data": [ 55 | 1, 56 | ], 57 | "sign": -1, 58 | }, 59 | { 60 | "data": [ 61 | 1, 62 | ], 63 | "sign": -1, 64 | }, 65 | ] 66 | `; 67 | 68 | exports[`uniformArrayIntDistribution [non regression] Should not change its output in asked range except for major bumps (4 states [-2, -1, 0, 1]) 1`] = ` 69 | [ 70 | { 71 | "data": [ 72 | 1, 73 | ], 74 | "sign": -1, 75 | }, 76 | { 77 | "data": [ 78 | 1, 79 | ], 80 | "sign": -1, 81 | }, 82 | { 83 | "data": [ 84 | 2, 85 | ], 86 | "sign": -1, 87 | }, 88 | { 89 | "data": [ 90 | 0, 91 | ], 92 | "sign": 1, 93 | }, 94 | { 95 | "data": [ 96 | 2, 97 | ], 98 | "sign": -1, 99 | }, 100 | { 101 | "data": [ 102 | 1, 103 | ], 104 | "sign": -1, 105 | }, 106 | { 107 | "data": [ 108 | 2, 109 | ], 110 | "sign": -1, 111 | }, 112 | { 113 | "data": [ 114 | 2, 115 | ], 116 | "sign": -1, 117 | }, 118 | { 119 | "data": [ 120 | 2, 121 | ], 122 | "sign": -1, 123 | }, 124 | { 125 | "data": [ 126 | 1, 127 | ], 128 | "sign": 1, 129 | }, 130 | ] 131 | `; 132 | 133 | exports[`uniformArrayIntDistribution [non regression] Should not change its output in asked range except for major bumps (32-bit signed) 1`] = ` 134 | [ 135 | { 136 | "data": [ 137 | 1748719057, 138 | ], 139 | "sign": -1, 140 | }, 141 | { 142 | "data": [ 143 | 1277901399, 144 | ], 145 | "sign": 1, 146 | }, 147 | { 148 | "data": [ 149 | 243580376, 150 | ], 151 | "sign": 1, 152 | }, 153 | { 154 | "data": [ 155 | 1171049868, 156 | ], 157 | "sign": 1, 158 | }, 159 | { 160 | "data": [ 161 | 2051556033, 162 | ], 163 | "sign": 1, 164 | }, 165 | { 166 | "data": [ 167 | 806729177, 168 | ], 169 | "sign": -1, 170 | }, 171 | { 172 | "data": [ 173 | 1686997841, 174 | ], 175 | "sign": 1, 176 | }, 177 | { 178 | "data": [ 179 | 602801999, 180 | ], 181 | "sign": 1, 182 | }, 183 | { 184 | "data": [ 185 | 2034131043, 186 | ], 187 | "sign": 1, 188 | }, 189 | { 190 | "data": [ 191 | 855081807, 192 | ], 193 | "sign": -1, 194 | }, 195 | ] 196 | `; 197 | 198 | exports[`uniformArrayIntDistribution [non regression] Should not change its output in asked range except for major bumps (32-bit unsigned) 1`] = ` 199 | [ 200 | { 201 | "data": [ 202 | 398764591, 203 | ], 204 | "sign": 1, 205 | }, 206 | { 207 | "data": [ 208 | 3425385047, 209 | ], 210 | "sign": 1, 211 | }, 212 | { 213 | "data": [ 214 | 2391064024, 215 | ], 216 | "sign": 1, 217 | }, 218 | { 219 | "data": [ 220 | 3318533516, 221 | ], 222 | "sign": 1, 223 | }, 224 | { 225 | "data": [ 226 | 4199039681, 227 | ], 228 | "sign": 1, 229 | }, 230 | { 231 | "data": [ 232 | 1340754471, 233 | ], 234 | "sign": 1, 235 | }, 236 | { 237 | "data": [ 238 | 3834481489, 239 | ], 240 | "sign": 1, 241 | }, 242 | { 243 | "data": [ 244 | 2750285647, 245 | ], 246 | "sign": 1, 247 | }, 248 | { 249 | "data": [ 250 | 4181614691, 251 | ], 252 | "sign": 1, 253 | }, 254 | { 255 | "data": [ 256 | 1292401841, 257 | ], 258 | "sign": 1, 259 | }, 260 | ] 261 | `; 262 | 263 | exports[`uniformArrayIntDistribution [non regression] Should not change its output in asked range except for major bumps (64-bit signed) 1`] = ` 264 | [ 265 | { 266 | "data": [ 267 | 1748719056, 268 | 3370736011, 269 | ], 270 | "sign": -1, 271 | }, 272 | { 273 | "data": [ 274 | 1706118332, 275 | 2757602565, 276 | ], 277 | "sign": -1, 278 | }, 279 | { 280 | "data": [ 281 | 1277901399, 282 | 1682652230, 283 | ], 284 | "sign": 1, 285 | }, 286 | { 287 | "data": [ 288 | 156067239, 289 | 976433780, 290 | ], 291 | "sign": -1, 292 | }, 293 | { 294 | "data": [ 295 | 2051556033, 296 | 1252949478, 297 | ], 298 | "sign": 1, 299 | }, 300 | { 301 | "data": [ 302 | 1686997841, 303 | 1827923621, 304 | ], 305 | "sign": 1, 306 | }, 307 | { 308 | "data": [ 309 | 718892300, 310 | 2331500859, 311 | ], 312 | "sign": -1, 313 | }, 314 | { 315 | "data": [ 316 | 602801999, 317 | 1589190063, 318 | ], 319 | "sign": 1, 320 | }, 321 | { 322 | "data": [ 323 | 91833517, 324 | 113352605, 325 | ], 326 | "sign": -1, 327 | }, 328 | { 329 | "data": [ 330 | 855081806, 331 | 165444877, 332 | ], 333 | "sign": -1, 334 | }, 335 | ] 336 | `; 337 | 338 | exports[`uniformArrayIntDistribution [non regression] Should not change its output in asked range except for major bumps (64-bit unsigned) 1`] = ` 339 | [ 340 | { 341 | "data": [ 342 | 398764591, 343 | 924231285, 344 | ], 345 | "sign": 1, 346 | }, 347 | { 348 | "data": [ 349 | 441365315, 350 | 1537364731, 351 | ], 352 | "sign": 1, 353 | }, 354 | { 355 | "data": [ 356 | 3425385047, 357 | 1682652230, 358 | ], 359 | "sign": 1, 360 | }, 361 | { 362 | "data": [ 363 | 1991416408, 364 | 3318533516, 365 | ], 366 | "sign": 1, 367 | }, 368 | { 369 | "data": [ 370 | 4199039681, 371 | 1252949478, 372 | ], 373 | "sign": 1, 374 | }, 375 | { 376 | "data": [ 377 | 3834481489, 378 | 1827923621, 379 | ], 380 | "sign": 1, 381 | }, 382 | { 383 | "data": [ 384 | 1428591347, 385 | 1963466437, 386 | ], 387 | "sign": 1, 388 | }, 389 | { 390 | "data": [ 391 | 2750285647, 392 | 1589190063, 393 | ], 394 | "sign": 1, 395 | }, 396 | { 397 | "data": [ 398 | 2055650130, 399 | 4181614691, 400 | ], 401 | "sign": 1, 402 | }, 403 | { 404 | "data": [ 405 | 1292401841, 406 | 4129522419, 407 | ], 408 | "sign": 1, 409 | }, 410 | ] 411 | `; 412 | 413 | exports[`uniformArrayIntDistribution [non regression] Should not change its output in asked range except for major bumps (128-bit signed) 1`] = ` 414 | [ 415 | { 416 | "data": [ 417 | 1748719056, 418 | 3370736010, 419 | 2816357183, 420 | 3853601981, 421 | ], 422 | "sign": -1, 423 | }, 424 | { 425 | "data": [ 426 | 156067239, 427 | 976433779, 428 | 500614853, 429 | 95927615, 430 | ], 431 | "sign": -1, 432 | }, 433 | { 434 | "data": [ 435 | 806729176, 436 | 4170864552, 437 | 85997393, 438 | 4002718120, 439 | ], 440 | "sign": -1, 441 | }, 442 | { 443 | "data": [ 444 | 2034131043, 445 | 1284876248, 446 | 1292401841, 447 | 4129522419, 448 | ], 449 | "sign": 1, 450 | }, 451 | { 452 | "data": [ 453 | 1795211326, 454 | 1531785974, 455 | 4134450502, 456 | 2385128833, 457 | ], 458 | "sign": -1, 459 | }, 460 | { 461 | "data": [ 462 | 2053645792, 463 | 1692613941, 464 | 366524171, 465 | 113385321, 466 | ], 467 | "sign": -1, 468 | }, 469 | { 470 | "data": [ 471 | 1853562077, 472 | 1566726015, 473 | 2066782079, 474 | 755308636, 475 | ], 476 | "sign": -1, 477 | }, 478 | { 479 | "data": [ 480 | 1661879776, 481 | 270445703, 482 | 2566893310, 483 | 3446147775, 484 | ], 485 | "sign": -1, 486 | }, 487 | { 488 | "data": [ 489 | 1354754446, 490 | 463129187, 491 | 3709609525, 492 | 3543550860, 493 | ], 494 | "sign": 1, 495 | }, 496 | { 497 | "data": [ 498 | 438279108, 499 | 656229423, 500 | 3044602495, 501 | 580073460, 502 | ], 503 | "sign": 1, 504 | }, 505 | ] 506 | `; 507 | 508 | exports[`uniformArrayIntDistribution [non regression] Should not change its output in asked range except for major bumps (fuzzy) 1`] = ` 509 | [ 510 | { 511 | "data": [ 512 | 515072292, 513 | 2825943070, 514 | ], 515 | "sign": 1, 516 | }, 517 | { 518 | "data": [ 519 | 1229651181, 520 | 3905788591, 521 | ], 522 | "sign": 1, 523 | }, 524 | { 525 | "data": [ 526 | 746785211, 527 | 3964543210, 528 | ], 529 | "sign": 1, 530 | }, 531 | { 532 | "data": [ 533 | 498191675, 534 | 3918613334, 535 | ], 536 | "sign": 1, 537 | }, 538 | { 539 | "data": [ 540 | 4272487041, 541 | 2957880514, 542 | ], 543 | "sign": 1, 544 | }, 545 | { 546 | "data": [ 547 | 932030350, 548 | 1930601697, 549 | ], 550 | "sign": 1, 551 | }, 552 | { 553 | "data": [ 554 | 3730804943, 555 | 4109830709, 556 | ], 557 | "sign": 1, 558 | }, 559 | { 560 | "data": [ 561 | 2696483921, 562 | 123627591, 563 | ], 564 | "sign": 1, 565 | }, 566 | { 567 | "data": [ 568 | 3623953413, 569 | 1926563625, 570 | ], 571 | "sign": 1, 572 | }, 573 | { 574 | "data": [ 575 | 1558369374, 576 | 3767932950, 577 | ], 578 | "sign": 1, 579 | }, 580 | ] 581 | `; 582 | 583 | exports[`uniformArrayIntDistribution [non regression] Should not change its output in asked range except for major bumps (trailing zeros) 1`] = ` 584 | [ 585 | { 586 | "data": [ 587 | 2, 588 | ], 589 | "sign": 1, 590 | }, 591 | { 592 | "data": [ 593 | 1, 594 | ], 595 | "sign": 1, 596 | }, 597 | { 598 | "data": [ 599 | 5, 600 | ], 601 | "sign": 1, 602 | }, 603 | { 604 | "data": [ 605 | 0, 606 | ], 607 | "sign": 1, 608 | }, 609 | { 610 | "data": [ 611 | 0, 612 | ], 613 | "sign": 1, 614 | }, 615 | { 616 | "data": [ 617 | 4, 618 | ], 619 | "sign": 1, 620 | }, 621 | { 622 | "data": [ 623 | 1, 624 | ], 625 | "sign": 1, 626 | }, 627 | { 628 | "data": [ 629 | 3, 630 | ], 631 | "sign": 1, 632 | }, 633 | { 634 | "data": [ 635 | 0, 636 | ], 637 | "sign": 1, 638 | }, 639 | { 640 | "data": [ 641 | 2, 642 | ], 643 | "sign": 1, 644 | }, 645 | ] 646 | `; 647 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | pure-rand logo 3 |

4 | 5 | Fast Pseudorandom number generators (aka PRNG) with purity in mind! 6 | 7 | [![Build Status](https://github.com/dubzzz/pure-rand/workflows/Build%20Status/badge.svg?branch=main)](https://github.com/dubzzz/pure-rand/actions) 8 | [![NPM Version](https://badge.fury.io/js/pure-rand.svg)](https://badge.fury.io/js/pure-rand) 9 | [![Monthly Downloads](https://img.shields.io/npm/dm/pure-rand)](https://www.npmjs.com/package/pure-rand) 10 | 11 | [![Codecov](https://codecov.io/gh/dubzzz/pure-rand/branch/main/graph/badge.svg)](https://codecov.io/gh/dubzzz/pure-rand) 12 | [![Package Quality](https://packagequality.com/shield/pure-rand.svg)](https://packagequality.com/#?package=pure-rand) 13 | [![Snyk Package Quality](https://snyk.io/advisor/npm-package/pure-rand/badge.svg)](https://snyk.io/advisor/npm-package/pure-rand) 14 | [![Tested with fast-check](https://img.shields.io/badge/tested%20with-fast%E2%80%91check%20%F0%9F%90%92-%23282ea9?style=flat&logoSize=auto&labelColor=%231b1b1d)](https://fast-check.dev/) 15 | 16 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/dubzzz/pure-rand/labels/good%20first%20issue) 17 | [![License](https://img.shields.io/npm/l/pure-rand.svg)](https://github.com/dubzzz/pure-rand/blob/main/LICENSE) 18 | [![Twitter](https://img.shields.io/twitter/url/https/github.com/dubzzz/pure-rand.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20pure-rand%20by%20%40ndubien%20https%3A%2F%2Fgithub.com%2Fdubzzz%2Fpure-rand%20%F0%9F%91%8D) 19 | 20 | ## Getting started 21 | 22 | **Install it in node via:** 23 | 24 | `npm install pure-rand` or `yarn add pure-rand` 25 | 26 | **Use it in browser by doing:** 27 | 28 | `import * as prand from 'https://unpkg.com/pure-rand/lib/esm/pure-rand.js';` 29 | 30 | ## Usage 31 | 32 | **Simple usage** 33 | 34 | ```javascript 35 | import prand from 'pure-rand'; 36 | 37 | const seed = 42; 38 | const rng = prand.xoroshiro128plus(seed); 39 | const firstDiceValue = prand.unsafeUniformIntDistribution(1, 6, rng); // value in {1..6}, here: 2 40 | const secondDiceValue = prand.unsafeUniformIntDistribution(1, 6, rng); // value in {1..6}, here: 4 41 | const thirdDiceValue = prand.unsafeUniformIntDistribution(1, 6, rng); // value in {1..6}, here: 6 42 | ``` 43 | 44 | **Pure usage** 45 | 46 | Pure means that the instance `rng` will never be altered in-place. It can be called again and again and it will always return the same value. But it will also return the next `rng`. Here is an example showing how the code above can be translated into its pure version: 47 | 48 | ```javascript 49 | import prand from 'pure-rand'; 50 | 51 | const seed = 42; 52 | const rng1 = prand.xoroshiro128plus(seed); 53 | const [firstDiceValue, rng2] = prand.uniformIntDistribution(1, 6, rng1); // value in {1..6}, here: 2 54 | const [secondDiceValue, rng3] = prand.uniformIntDistribution(1, 6, rng2); // value in {1..6}, here: 4 55 | const [thirdDiceValue, rng4] = prand.uniformIntDistribution(1, 6, rng3); // value in {1..6}, here: 6 56 | 57 | // You can call: prand.uniformIntDistribution(1, 6, rng1); 58 | // over and over it will always give you back the same value along with a new rng (always producing the same values too). 59 | ``` 60 | 61 | **Independent simulations** 62 | 63 | In order to produce independent simulations it can be tempting to instanciate several PRNG based on totally different seeds. While it would produce distinct set of values, the best way to ensure fully unrelated sequences is rather to use jumps. Jump just consists into moving far away from the current position in the generator (eg.: jumping in Xoroshiro 128+ will move you 264 generations away from the current one on a generator having a sequence of 2128 elements). 64 | 65 | ```javascript 66 | import prand from 'pure-rand'; 67 | 68 | const seed = 42; 69 | const rngSimulation1 = prand.xoroshiro128plus(seed); 70 | const rngSimulation2 = rngSimulation1.jump(); // not in-place, creates a new instance 71 | const rngSimulation3 = rngSimulation2.jump(); // not in-place, creates a new instance 72 | 73 | const diceSim1Value = prand.unsafeUniformIntDistribution(1, 6, rngSimulation1); // value in {1..6}, here: 2 74 | const diceSim2Value = prand.unsafeUniformIntDistribution(1, 6, rngSimulation2); // value in {1..6}, here: 5 75 | const diceSim3Value = prand.unsafeUniformIntDistribution(1, 6, rngSimulation3); // value in {1..6}, here: 6 76 | ``` 77 | 78 | **Non-uniform usage** 79 | 80 | While not recommended as non-uniform distribution implies that one or several values from the range will be more likely than others, it might be tempting for people wanting to maximize the throughput. 81 | 82 | ```javascript 83 | import prand from 'pure-rand'; 84 | 85 | const seed = 42; 86 | const rng = prand.xoroshiro128plus(seed); 87 | const rand = (min, max) => { 88 | const out = (rng.unsafeNext() >>> 0) / 0x100000000; 89 | return min + Math.floor(out * (max - min + 1)); 90 | }; 91 | const firstDiceValue = rand(1, 6); // value in {1..6}, here: 6 92 | ``` 93 | 94 | **Select your seed** 95 | 96 | While not perfect, here is a rather simple way to generate a seed for your PNRG. 97 | 98 | ```javascript 99 | const seed = Date.now() ^ (Math.random() * 0x100000000); 100 | ``` 101 | 102 | ## Documentation 103 | 104 | ### Pseudorandom number generators 105 | 106 | In computer science most random number generators(1) are [pseudorandom number generators](https://en.wikipedia.org/wiki/Pseudorandom_number_generator) (abbreviated: PRNG). In other words, they are fully deterministic and given the original seed one can rebuild the whole sequence. 107 | 108 | Each PRNG algorithm has to deal with tradeoffs in terms of randomness quality, speed, length of the sequence(2)... In other words, it's important to compare relative speed of libraries with that in mind. Indeed, a Mersenne Twister PRNG will not have the same strenghts and weaknesses as a Xoroshiro PRNG, so depending on what you need exactly you might prefer one PRNG over another even if it will be slower. 109 | 110 | 4 PRNGs come with pure-rand: 111 | 112 | - `congruential32`: Linear Congruential generator — \[[more](https://en.wikipedia.org/wiki/Linear_congruential_generator)\] 113 | - `mersenne`: Mersenne Twister generator — \[[more](https://en.wikipedia.org/wiki/Mersenne_Twister)\] 114 | - `xorshift128plus`: Xorshift 128+ generator — \[[more](https://en.wikipedia.org/wiki/Xorshift)\] 115 | - `xoroshiro128plus`: Xoroshiro 128+ generator — \[[more](https://en.wikipedia.org/wiki/Xorshift)\] 116 | 117 | Our recommendation is `xoroshiro128plus`. But if you want to use another one, you can replace it by any other PRNG provided by pure-rand in the examples above. 118 | 119 | ### Distributions 120 | 121 | Once you are able to generate random values, next step is to scale them into the range you want. Indeed, you probably don't want a floating point value between 0 (included) and 1 (excluded) but rather an integer value between 1 and 6 if you emulate a dice or any other range based on your needs. 122 | 123 | At this point, simple way would be to do `min + floor(random() * (max - min + 1))` but actually it will not generate the values with equal probabilities even if you use the best PRNG in the world to back `random()`. In order to have equal probabilities you need to rely on uniform distributions(3) which comes built-in in some PNRG libraries. 124 | 125 | pure-rand provides 3 built-in functions for uniform distributions of values: 126 | 127 | - `uniformIntDistribution(min, max, rng)` 128 | - `uniformBigIntDistribution(min, max, rng)` - with `min` and `max` being `bigint` 129 | - `uniformArrayIntDistribution(min, max, rng)` - with `min` and `max` being instances of `ArrayInt = {sign, data}` ie. sign either 1 or -1 and data an array of numbers between 0 (included) and 0xffffffff (included) 130 | 131 | And their unsafe equivalents to change the PRNG in-place. 132 | 133 | ### Extra helpers 134 | 135 | Some helpers are also provided in order to ease the use of `RandomGenerator` instances: 136 | 137 | - `prand.generateN(rng: RandomGenerator, num: number): [number[], RandomGenerator]`: generates `num` random values using `rng` and return the next `RandomGenerator` 138 | - `prand.skipN(rng: RandomGenerator, num: number): RandomGenerator`: skips `num` random values and return the next `RandomGenerator` 139 | 140 | ## Comparison 141 | 142 | ### Summary 143 | 144 | The chart has been split into three sections: 145 | 146 | - section 1: native `Math.random()` 147 | - section 2: without uniform distribution of values 148 | - section 3: with uniform distribution of values (not supported by all libraries) 149 | 150 | Comparison against other libraries 151 | 152 | ### Process 153 | 154 | In order to compare the performance of the libraries, we aked them to shuffle an array containing 1,000,000 items (see [code](https://github.com/dubzzz/pure-rand/blob/556ec331c68091c5d56e9da1266112e8ea222b2e/perf/compare.cjs)). 155 | 156 | We then split the measurements into two sections: 157 | 158 | - one for non-uniform distributions — _known to be slower as it implies re-asking for other values to the PRNG until the produced value fall into the acceptable range of values_ 159 | - one for uniform distributions 160 | 161 | The recommended setup for pure-rand is to rely on our Xoroshiro128+. It provides a long enough sequence of random values, has built-in support for jump, is really efficient while providing a very good quality of randomness. 162 | 163 | ### Performance 164 | 165 | **Non-Uniform** 166 | 167 | | Library | Algorithm | Mean time (ms) | Compared to pure-rand | 168 | | ------------------------ | ----------------- | -------------- | --------------------- | 169 | | native \(node 16.19.1\) | Xorshift128+ | 33.3 | 1.4x slower | 170 | | **pure-rand _@6.0.0_** | **Xoroshiro128+** | **24.5** | **reference** | 171 | | pure-rand _@6.0.0_ | Xorshift128+ | 25.0 | similar | 172 | | pure-rand _@6.0.0_ | Mersenne Twister | 30.8 | 1.3x slower | 173 | | pure-rand _@6.0.0_ | Congruential‍ | 22.6 | 1.1x faster | 174 | | seedrandom _@3.0.5_ | Alea | 28.1 | 1.1x slower | 175 | | seedrandom _@3.0.5_ | Xorshift128 | 28.8 | 1.2x slower | 176 | | seedrandom _@3.0.5_ | Tyche-i | 28.6 | 1.2x slower | 177 | | seedrandom _@3.0.5_ | Xorwow | 32.0 | 1.3x slower | 178 | | seedrandom _@3.0.5_ | Xor4096 | 32.2 | 1.3x slower | 179 | | seedrandom _@3.0.5_ | Xorshift7 | 33.5 | 1.4x slower | 180 | | @faker-js/faker _@7.6.0_ | Mersenne Twister | 109.1 | 4.5x slower | 181 | | chance _@1.1.10_ | Mersenne Twister | 142.9 | 5.8x slower | 182 | 183 | **Uniform** 184 | 185 | | Library | Algorithm | Mean time (ms) | Compared to pure-rand | 186 | | ---------------------- | ----------------- | -------------- | --------------------- | 187 | | **pure-rand _@6.0.0_** | **Xoroshiro128+** | **53.5** | **reference** | 188 | | pure-rand _@6.0.0_ | Xorshift128+ | 52.2 | similar | 189 | | pure-rand _@6.0.0_ | Mersenne Twister | 61.6 | 1.2x slower | 190 | | pure-rand _@6.0.0_ | Congruential‍ | 57.6 | 1.1x slower | 191 | | random-js @2.1.0 | Mersenne Twister | 119.6 | 2.2x slower | 192 | 193 | > System details: 194 | > 195 | > - OS: Linux 5.15 Ubuntu 22.04.2 LTS 22.04.2 LTS (Jammy Jellyfish) 196 | > - CPU: (2) x64 Intel(R) Xeon(R) Platinum 8272CL CPU @ 2.60GHz 197 | > - Memory: 5.88 GB / 6.78 GB 198 | > - Container: Yes 199 | > - Node: 16.19.1 - /opt/hostedtoolcache/node/16.19.1/x64/bin/node 200 | > 201 | > _Executed on default runners provided by GitHub Actions_ 202 | 203 | --- 204 | 205 | (1) — Not all as there are also [hardware-based random number generator](https://en.wikipedia.org/wiki/Hardware_random_number_generator). 206 | 207 | (2) — How long it takes to reapeat itself? 208 | 209 | (3) — While most users don't really think of it, uniform distribution is key! Without it entries might be biased towards some values and make some others less probable. The naive `rand() % numValues` is a good example of biased version as if `rand()` is uniform in `0, 1, 2` and `numValues` is `2`, the probabilities are: `P(0) = 67%`, `P(1) = 33%` causing `1` to be less probable than `0` 210 | 211 | ## Advanced patterns 212 | 213 | ### Generate 32-bit floating point numbers 214 | 215 | The following snippet is responsible for generating 32-bit floating point numbers that spread uniformly between 0 (included) and 1 (excluded). 216 | 217 | ```js 218 | import prand from 'pure-rand'; 219 | 220 | function generateFloat32(rng) { 221 | const g1 = prand.unsafeUniformIntDistribution(0, (1 << 24) - 1, rng); 222 | const value = g1 / (1 << 24); 223 | return value; 224 | } 225 | 226 | const seed = 42; 227 | const rng = prand.xoroshiro128plus(seed); 228 | 229 | const float32Bits1 = generateFloat32(rng); 230 | const float32Bits2 = generateFloat32(rng); 231 | ``` 232 | 233 | ### Generate 64-bit floating point numbers 234 | 235 | The following snippet is responsible for generating 64-bit floating point numbers that spread uniformly between 0 (included) and 1 (excluded). 236 | 237 | ```js 238 | import prand from 'pure-rand'; 239 | 240 | function generateFloat64(rng) { 241 | const g1 = prand.unsafeUniformIntDistribution(0, (1 << 26) - 1, rng); 242 | const g2 = prand.unsafeUniformIntDistribution(0, (1 << 27) - 1, rng); 243 | const value = (g1 * Math.pow(2, 27) + g2) * Math.pow(2, -53); 244 | return value; 245 | } 246 | 247 | const seed = 42; 248 | const rng = prand.xoroshiro128plus(seed); 249 | 250 | const float64Bits1 = generateFloat64(rng); 251 | const float64Bits2 = generateFloat64(rng); 252 | ``` 253 | -------------------------------------------------------------------------------- /test/unit/generator/LinearCongruencial.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as fc from 'fast-check'; 3 | 4 | import { congruential32 } from '../../../src/generator/LinearCongruential'; 5 | import * as p from './RandomGenerator.properties'; 6 | 7 | describe('congruential32', () => { 8 | it('Should produce the right sequence for seed=42', () => { 9 | let g = congruential32(42); 10 | const data = []; 11 | for (let idx = 0; idx !== 1000; ++idx) { 12 | const [v, nextG] = g.next(); 13 | data.push(v); 14 | g = nextG; 15 | } 16 | assert.deepEqual( 17 | data, 18 | [ 19 | 3234350541, 527020623, 250494401, 2135749886, 3453847840, 1768043920, 3865977547, 3120260103, 2378176704, 20 | 3334114947, 2265379032, 1332836779, 2062426087, 4121359587, 3757575956, 3358259829, 3704101252, 3758918454, 21 | 4194525353, 2542924373, 217440218, 3519482194, 1726011385, 1653505356, 1852543577, 1929301549, 2169742874, 22 | 3308492120, 3155389883, 78406889, 3018048146, 1703776674, 1963946594, 1026705694, 1546700628, 26198758, 23 | 3433100529, 1126200938, 15228153, 3324074273, 2746283304, 1678446390, 1885156162, 1565458049, 2985449221, 24 | 93300426, 1035813326, 26530696, 3836336663, 778959507, 1927728407, 3293427842, 1494543140, 2781390515, 25 | 3973999538, 2504233002, 2020420606, 3330449866, 2028210119, 2036000663, 462441890, 3300658178, 3895335118, 26 | 4267618156, 2085001362, 57843040, 1882689931, 2582966847, 1512488819, 2017247049, 230440523, 2175087689, 27 | 1495128042, 2066324818, 2152402783, 2853038158, 3214120370, 2159950156, 3565128665, 4213908943, 1513268225, 28 | 2618609295, 2463426693, 1741327470, 726415645, 1192726662, 3374055210, 1981087905, 1809858617, 3470987387, 29 | 2224202242, 658550170, 1145091476, 574674596, 849053558, 1280997493, 39522516, 1615264365, 2114931230, 30 | 1983369379, 4087044511, 1695860230, 2671130871, 243468693, 678576763, 4071797039, 1952281574, 1206528429, 31 | 2273207783, 3020898115, 1493897325, 3010157153, 1984823738, 941395074, 3520676523, 2831410608, 1036537019, 32 | 3809416867, 1122322073, 268365001, 2956018304, 3015412889, 1189807491, 2049065977, 1993376138, 1528115621, 33 | 2633511876, 3606880213, 221812641, 2211028892, 1120133829, 4128604073, 2418661485, 3355669625, 4053975863, 34 | 1622573343, 639277654, 859977509, 1411257231, 3139406131, 660501916, 241074053, 1781298882, 4207898198, 35 | 3421004217, 2476879300, 4060098370, 3757346784, 1152699638, 151796962, 3414653495, 2147190459, 2261518923, 36 | 2661907581, 1819838210, 3476740941, 770453884, 253083102, 1527793390, 4156714149, 3167407473, 3759718470, 37 | 90214712, 1799976169, 3774805204, 1097164921, 3947681602, 1044141823, 3229821168, 3883143052, 4045137170, 38 | 971645306, 3068106441, 2693231851, 1353574928, 869593798, 3354236535, 29433773, 4283844096, 404368023, 39 | 3996976327, 1785314074, 3192739389, 1963463822, 3407696659, 3142007421, 2882027319, 902232109, 2654923105, 40 | 1986598, 179269166, 3659938300, 2331915963, 693044163, 3606236975, 2576316924, 289803213, 3549819872, 41 | 1552020303, 4279196491, 3222772676, 2078609083, 2799706919, 1820778777, 2276376420, 2640727628, 2136807823, 42 | 1391889610, 2974406251, 3616949140, 2414273182, 233843051, 2727273253, 1518664997, 1619237601, 807465050, 43 | 4069506128, 4147221745, 1164249818, 2936140593, 4134891826, 2450190679, 866212539, 2074441459, 1270758391, 44 | 2508785003, 1384438371, 916696185, 3324780111, 3807485060, 2139888024, 221016605, 3343090760, 523271435, 45 | 3673738374, 3979315880, 2776041832, 3654774402, 2466592888, 514946425, 2833857550, 2889641682, 1136534740, 46 | 1882346487, 2211740426, 246981337, 2708835420, 1145112632, 210815353, 3255094835, 2212235939, 2921887985, 47 | 3745166566, 1253531207, 129155533, 3954878112, 1828137374, 484627605, 3710104714, 1782775288, 3738980691, 48 | 4124351550, 2025410644, 613323661, 2457098228, 2180126420, 3169887207, 3357717695, 367309515, 2360765539, 49 | 309526017, 796981739, 1773618647, 1490053823, 82309211, 1604077008, 3996779371, 3730931727, 1517318886, 50 | 2523225663, 1116430855, 1297882265, 1001923402, 410388998, 3141256310, 1375700944, 1655626907, 2346934301, 51 | 915746856, 4185388187, 635151198, 3458708498, 4102254033, 3030048998, 2522096059, 3543354289, 3088347201, 52 | 3603303534, 2253664258, 2073741815, 2560892293, 1323242184, 2926650723, 803306297, 1218467391, 2020439219, 53 | 2272292073, 1860923024, 3085752130, 1121688735, 3468630712, 1569261703, 2548814193, 2362714749, 193363335, 54 | 86473479, 656659417, 2982154952, 2321737823, 1335512705, 176448524, 2808372435, 1706537109, 772728925, 55 | 1514954944, 1772063828, 3500776453, 957725347, 2828012199, 1380516401, 4205820816, 2010351848, 3881881488, 56 | 2603507849, 1103949271, 1548643962, 881832894, 1747992069, 157517368, 1658458574, 1897362823, 2541892705, 57 | 732812296, 3802706216, 3927852848, 906532405, 2614232595, 3178755212, 3114253064, 1440012296, 781788752, 58 | 3160158437, 3836755395, 1690597526, 2680465897, 129239156, 493075224, 3242194016, 2315604330, 1211208826, 59 | 2924726429, 2870382708, 2811155783, 2668233768, 4123456225, 1316948094, 290560659, 3276037318, 1987217611, 60 | 1194650639, 1389194238, 1915045666, 1676017969, 2578593580, 3364418320, 12588603, 4035892924, 2157310490, 61 | 522710436, 2091086638, 2203776506, 1276732519, 3911146406, 3919927904, 453286340, 833890108, 2702090512, 62 | 1255429612, 83177938, 165061345, 3467827234, 75735375, 2915807132, 1506427792, 3833450464, 1165599313, 63 | 2978076019, 1739518334, 197915345, 2235491126, 4046742494, 3063289895, 3048837621, 92863013, 3565568313, 64 | 2831665369, 1721520135, 617286103, 1017706380, 1712833667, 1376954611, 2951104388, 1325474985, 2221570239, 65 | 3183497626, 1089516042, 2641246344, 273628188, 1084287940, 1495950943, 1378721254, 868275632, 3573434981, 66 | 2215066937, 2774739227, 3640695773, 1393017364, 4049542650, 37677371, 2274866787, 168214984, 1421488865, 67 | 566278001, 2574372774, 3459559380, 4156582510, 1092755607, 1176355136, 1899185956, 2378592789, 3118007576, 68 | 3794372554, 4213141689, 1987843622, 557779722, 1352829414, 2588431714, 1035645692, 4124622471, 78773904, 69 | 1602724796, 2255659876, 3224927307, 291110565, 3174319723, 4005091408, 2911306387, 3847589299, 1589216921, 70 | 2599749052, 1235797044, 823567875, 1322210991, 3056734431, 3741604345, 350393316, 3259679059, 3582471061, 71 | 1642507842, 353414201, 1033549985, 3433908492, 3985325786, 4066912837, 3940139436, 2711738285, 2025885975, 72 | 1688191157, 2525214775, 763391053, 2284860472, 351807982, 3905889967, 2557037204, 2218913274, 3606954904, 73 | 3516952172, 4271109804, 3097685371, 2103771498, 4246715433, 2893593645, 1668063316, 151536439, 3337618624, 74 | 3223541320, 434800113, 1342528057, 2181498409, 2644288819, 1761549618, 1526516124, 1260259509, 75209940, 75 | 925583431, 1450151501, 1003347078, 1955780784, 2385625800, 3594287620, 4097724241, 1874559253, 3529541139, 76 | 1228795279, 2441320023, 2261413253, 2373269482, 1527348832, 3331254433, 2140450918, 2468478796, 2714456921, 77 | 1841612155, 1109784552, 624286611, 1447511255, 1228350258, 1527345205, 3714046415, 4092922876, 1198916112, 78 | 12128991, 3264791649, 3208514880, 1124736920, 3928887173, 1344579802, 2413496890, 3532080638, 1686813127, 79 | 2487078932, 1565869739, 524579817, 3052054636, 2891285649, 2879352011, 414242923, 2068967890, 3305063188, 80 | 3587652823, 2051727168, 3484922261, 3815846270, 354669530, 3124087110, 1782072194, 2662332394, 3249575354, 81 | 3597569341, 23060413, 240146443, 1808696966, 379878591, 3784967217, 2497584836, 3408928511, 950692225, 79714104, 82 | 2347027464, 1648879716, 3717351115, 415625099, 2005994740, 1405479809, 2908783413, 59256171, 3355173930, 83 | 3359096570, 1197610659, 1371214096, 2383092848, 566509779, 3038838915, 3740809809, 3925664136, 2617952067, 84 | 905851460, 3840671125, 428317521, 3592443066, 3647185433, 4288887289, 3953616670, 615357231, 3181117934, 85 | 1425759426, 3220982614, 2855230127, 1766479530, 1831747076, 399901004, 2471552802, 1434151847, 713048723, 86 | 2664984928, 3043680607, 509664879, 3994054441, 2973678934, 1831104371, 2963553975, 398645465, 1170633973, 87 | 2066513965, 1767800480, 2941394611, 3241136355, 2099013101, 1051373356, 3833354424, 426282943, 3228685485, 88 | 2799813886, 877602816, 240210304, 2376994639, 342614357, 563663912, 2652802316, 1771556905, 2083937099, 89 | 242459137, 2070281244, 2861676750, 1020134729, 2767605623, 3938359631, 1612698910, 470837965, 3707723967, 90 | 235572363, 3934289295, 4051894879, 3441732292, 1130045851, 7054525, 4081881041, 3569501906, 494314758, 91 | 3237239856, 2076619446, 1294359427, 3690971949, 2407570540, 4256521508, 1340106474, 3912877480, 1475124286, 92 | 2099064493, 2150561278, 57789153, 952419141, 3267030938, 2777511240, 3623834608, 4054287294, 1612064873, 93 | 1164150479, 4126196921, 1405134740, 4198109577, 164694339, 3729502788, 1382505638, 4044019540, 2744990825, 94 | 3623263068, 4057861211, 3644452428, 893660870, 1295902570, 4063185507, 517895066, 1965416591, 2566375314, 95 | 1948935887, 111760835, 3570517967, 1229837838, 1988390740, 2747122092, 3682116057, 4263546827, 1762864090, 96 | 941821904, 3245784560, 688817217, 875455051, 367013311, 4211322164, 2501498775, 2989092249, 4138450905, 97 | 1073841104, 1005449975, 955675664, 2689038156, 2129106785, 1321533229, 1910491358, 3413203019, 3369330344, 98 | 2029028742, 1567114464, 595455120, 2763396723, 3254604519, 2169086638, 1418403347, 2543857608, 360216876, 99 | 1586571752, 4267226635, 1345489747, 3596094146, 2917053889, 3250338853, 3274413585, 2214620295, 3217121997, 100 | 1052226051, 2803096381, 384227809, 2134306823, 959473399, 3708649339, 450656028, 4098400037, 3125010432, 101 | 4106262851, 1827737097, 3301679135, 608047109, 2574062223, 2734379219, 2436649981, 2402311248, 2517925831, 102 | 45858596, 733704928, 3827673421, 2127009772, 1319504248, 1194352979, 3438963586, 3977853443, 1313115839, 103 | 3699589331, 1859221713, 2645044164, 406802489, 3824977819, 1160287725, 3704552835, 1141045134, 549694388, 104 | 1402240026, 3496951646, 1224985388, 2625285437, 67982121, 2363782055, 2980556005, 285150198, 669899698, 105 | 3272403603, 2815033033, 1581675539, 1904395666, 4118508324, 14682688, 173715006, 2621828775, 2193853779, 106 | 1338164586, 1769742061, 2578351562, 854067585, 3792736954, 2891735270, 2024689165, 3588833568, 1865963216, 107 | 2994521871, 1572863541, 2708559005, 2669281431, 1588384122, 3248715898, 2611223184, 1850761119, 1269326255, 108 | 3434075773, 17673254, 3591726268, 517323323, 3301639561, 4088249037, 4257786079, 2535658874, 2751557390, 109 | 2703727393, 2618676787, 1443127201, 1303832922, 3994038550, 3451539323, 2157076494, 2021780633, 1415260518, 110 | 2280239530, 1992744402, 1117734927, 48835645, 3965160387, 1054673791, 2265469844, 3963024675, 1691937011, 111 | 583910959, 2838059343, 3525835817, 2360183020, 1575842772, 1832268726, 3170442326, 2372199044, 413863110, 112 | 1394826180, 1800168276, 895667341, 2423135369, 1245143932, 2840950358, 772335862, 3918808542, 1933875359, 113 | 1563015244, 2541235746, 3381499405, 125782953, 3056508233, 2952473224, 4145951601, 2582705937, 3450197812, 114 | 1292394123, 3736744772, 160350912, 2790999981, 3421194395, 956474027, 1509072849, 1500930882, 2148007820, 115 | 1902513924, 2293102730, 509833682, 2227369781, 2329647471, 356593818, 4226078784, 2030390093, 549740954, 116 | 1370550677, 1346393947, 1248144513, 2179322832, 1908178860, 1049390093, 367643608, 59385412, 2931004080, 117 | 2937994435, 74506315, 1254003098, 3052302153, 823771163, 3904316064, 2846855350, 937311346, 2870708436, 118 | 1157421959, 1328001992, 231395640, 3909870321, 4266352630, 2788053655, 3424810675, 1963437615, 3814397897, 119 | 1673734669, 4151401028, 3379765673, 2367416980, 549499139, 4059671567, 1243931849, 2128031435, 4258607182, 120 | 2548606395, 313731442, 200931093, 1873078793, 539686248, 3353752426, 2843138540, 1896111140, 2011416817, 121 | 2051111181, 1892683641, 3109677612, 1854141795, 129157423, 2058977710, 4112087644, 679409340, 2321785425, 122 | 3613600419, 2657210853, 782156775, 3265406727, 1247403317, 3015556217, 268382968, 2314427304, 3490829277, 123 | 3942632221, 2896095652, 4248705335, 319431117, 1581168175, 3734377986, 739991954, 1452597840, 3505958581, 124 | 2549115784, 4215439576, 3174646371, 2336959208, 2870812735, 1209575455, 4039032974, 1767176704, 336291401, 125 | 996011350, 1372471048, 3702664001, 2010717589, 2566544503, 1966883851, 4263285829, 825244128, 988743153, 126 | 350946420, 3978756747, 3744962799, 90256980, 978556061, 3692880960, 3442165002, 3251192638, 2292931454, 127 | 100594764, 1754357691, 2940213637, 1822860290, 1822569888, 3692081352, 4126111909, 2548328615, 1559153086, 128 | 2229736813, 3416000363, 1307522393, 2537287304, 1493158878, 3075976674, 3907402638, 433041229, 3014088443, 129 | 3259785341, 1539470501, 1803261200, 518157909, 1135233158, 406515323, 3831259675, 2405854817, 1438184902, 130 | ].map((v) => v | 0), 131 | ); 132 | }); 133 | it('Should return the same sequence given same seeds', () => fc.assert(p.sameSeedSameSequences(congruential32))); 134 | it('Should return the same sequence when built from state', () => 135 | fc.assert(p.clonedFromStateSameSequences(congruential32))); 136 | it('Should return the same sequence if called twice', () => fc.assert(p.sameSequencesIfCallTwice(congruential32))); 137 | it('Should generate values between -2**31 and 2**31 -1', () => fc.assert(p.valuesInRange(congruential32))); 138 | it('Should impact itself with unsafeNext', () => fc.assert(p.changeSelfWithUnsafeNext(congruential32))); 139 | it('Should not impact itself with next', () => fc.assert(p.noChangeSelfWithNext(congruential32))); 140 | it('Should not impact clones when impacting itself on unsafeNext', () => 141 | fc.assert(p.noChangeOnClonedWithUnsafeNext(congruential32))); 142 | }); 143 | -------------------------------------------------------------------------------- /test/unit/generator/MersenneTwister.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as fc from 'fast-check'; 3 | 4 | import { mersenne } from '../../../src/generator/MersenneTwister'; 5 | import * as p from './RandomGenerator.properties'; 6 | 7 | describe('mersenne', () => { 8 | it('Should produce the right sequence for seed=42', () => { 9 | let g = mersenne(42); 10 | const data = []; 11 | for (let idx = 0; idx !== 1000; ++idx) { 12 | const [v, nextG] = g.next(); 13 | data.push(v); 14 | g = nextG; 15 | } 16 | // should be equivalent to the following Python code: 17 | // from numpy.random import MT19937 18 | // rng = MT19937(42) 19 | // rng._legacy_seeding(42) 20 | // print([(rng.random_raw() & 0xffffffff) for v in range(1000)]) 21 | assert.deepEqual( 22 | data, 23 | [ 24 | 1608637542, 3421126067, 4083286876, 787846414, 3143890026, 3348747335, 2571218620, 2563451924, 670094950, 25 | 1914837113, 669991378, 429389014, 249467210, 1972458954, 3720198231, 1433267572, 2581769315, 613608295, 26 | 3041148567, 2795544706, 88409749, 242285876, 4165731073, 3100961111, 3575313899, 4031053213, 911989541, 3344769, 27 | 780932287, 4261516219, 787716372, 2652062880, 1306710475, 2627030329, 2253811733, 30349564, 1855189739, 28 | 99052376, 1250819632, 2253890010, 2627888186, 1717389822, 599121577, 200427519, 1254751707, 4182248123, 29 | 1573512143, 999745294, 1958805693, 389151677, 3372305070, 2655947709, 857592370, 1642661739, 2208620086, 30 | 4222944499, 2544401215, 2004731384, 199502978, 3693415908, 2609385266, 2921898630, 732395540, 1934879560, 31 | 279394470, 56972561, 4075432323, 4046725720, 4147358011, 2419304461, 3472040177, 1655351289, 1308306184, 32 | 68574553, 419498548, 991681409, 2938758483, 1035196507, 1890440558, 2934594491, 524150214, 2619915691, 33 | 2126768636, 3578544903, 147697582, 744595490, 3905501389, 1679592528, 1111451555, 782698033, 2845511527, 34 | 3244252547, 1338788865, 1826030589, 2233675141, 893102645, 2348102761, 2438254339, 793943861, 134489564, 35 | 4164334270, 3617585553, 3329170137, 1931679275, 4035117217, 1697157321, 3843254205, 3979969507, 2567960845, 36 | 3123609438, 3959419695, 1402481934, 380072391, 2450038221, 841739990, 2236966139, 194249720, 4128202429, 37 | 1397283111, 3627245268, 1669356239, 3209715436, 1165435217, 2317960046, 3559400500, 2520077079, 1532243865, 38 | 4145739992, 1206604539, 2607192251, 2330861947, 1185407468, 605264936, 1272485020, 3445409806, 709816108, 39 | 320192576, 67157848, 4238647110, 1818495496, 3316766039, 1696003200, 853477355, 1260522119, 23717335, 60472382, 40 | 3502380170, 854021618, 3035929168, 3055190407, 3131061922, 3393778082, 3312580896, 2602578298, 318019332, 41 | 3978431977, 1539598566, 2796354553, 497653800, 3929721883, 3707000966, 3650887880, 2677045063, 1930375947, 42 | 1421196193, 409783328, 272981039, 1592652278, 1335658902, 2872651325, 1396651735, 2860114724, 3133634658, 43 | 2539604651, 2738288487, 1179921109, 3810549722, 2410522146, 2028147648, 1644658402, 513653348, 4173471662, 44 | 3063363021, 3646057090, 3267546880, 3099804676, 2410667225, 1013547510, 3311278846, 1099805069, 2120835942, 45 | 173660954, 2245120392, 3052273870, 1836274702, 476272473, 109174313, 1886935931, 463390156, 866377394, 46 | 134987326, 3847275359, 2733361894, 2041699568, 1350148659, 2419250172, 2184294495, 2987218819, 3897968349, 47 | 598424036, 1070701974, 2595952870, 1762581228, 2318599822, 3245067434, 872141340, 982680611, 4049525260, 48 | 330626207, 2572107590, 1244473018, 2984078578, 692440149, 3781580571, 3993020993, 2681580201, 3470850604, 49 | 1269737021, 2720448440, 453094388, 3742894725, 1960801051, 3451745307, 938194539, 801312299, 1788896595, 50 | 3833511709, 3793659837, 2316457290, 1393051263, 3467929081, 524363766, 3848682843, 1530287576, 1365814502, 51 | 3894798525, 472669408, 1168799104, 978974072, 2781807898, 1834414013, 2235000, 3513346675, 1514271692, 52 | 3696809704, 1309025538, 29859174, 707191493, 2193642951, 2293896602, 1792766600, 2082328893, 953945764, 53 | 2973990112, 514817842, 1157117161, 1450046123, 1048511127, 4049766350, 722804538, 1388146037, 939585183, 54 | 2228188767, 2397029847, 3019443432, 1734463155, 1561777264, 278710077, 4173772272, 1090558393, 4133679667, 55 | 1060324619, 1081396733, 2990604070, 2135666049, 3059178882, 1292262512, 636028516, 1223380592, 4285262775, 56 | 158428240, 1145815738, 2618058864, 4194529281, 2158989953, 1765390555, 221099573, 141951830, 1196777444, 57 | 1482069727, 3900972256, 2724518272, 1028910482, 2923607677, 622318721, 2280346676, 2102183595, 1923214041, 58 | 4233336479, 2374657733, 1039619487, 2545613046, 2886800195, 347262390, 3271131338, 1587653815, 1020645498, 59 | 1040069008, 3127665406, 3449458981, 1579616535, 2019925828, 2715732851, 4223770209, 2720989381, 1712937941, 60 | 2301134730, 3506548222, 387791599, 3428866191, 3587596896, 647326920, 1377739899, 2182697146, 801090885, 61 | 2988493263, 175127900, 3686622978, 2537865875, 1399982843, 2910116794, 945928099, 71244178, 3054363993, 62 | 2199422914, 3476780542, 972791954, 1497509011, 2770996063, 413075142, 748898099, 4039516648, 2967554976, 63 | 1707558823, 1661015679, 2223725094, 4023224657, 3597937511, 590647936, 2902066954, 1464868827, 3157729208, 64 | 487365080, 897955761, 3971528854, 2325501342, 3768143837, 2988371241, 1107850850, 981614854, 2834609915, 65 | 751425679, 3509942617, 4218380911, 2384569336, 2218934259, 2274831931, 1120252784, 1038747649, 4278877056, 66 | 399873327, 4146444541, 3853512331, 2397852100, 3867266084, 3790894239, 2719150074, 810490870, 1456121864, 67 | 1197743336, 1499843682, 3008013970, 3117955887, 3636381903, 3853059202, 3677884819, 3810007191, 1737349173, 68 | 3349539959, 3812943520, 2757504919, 3654709875, 361378378, 4018521712, 694190023, 3373012387, 3859260837, 69 | 2873282663, 2604592979, 2494030077, 39501026, 1598942319, 435816957, 4037842392, 2849718370, 4181854360, 70 | 21739356, 1219431313, 690665343, 1311527789, 2356793681, 2085695169, 2971667253, 1925967010, 2800152271, 71 | 4271162300, 963229352, 755593187, 3058786464, 77633091, 1018977056, 2121257357, 1397581076, 768037679, 72 | 3206156179, 1573971447, 2790152063, 3196188039, 3647386758, 3096413378, 2824425872, 1323111039, 2440866848, 73 | 2330192559, 402330059, 2185339834, 1579327346, 2733027797, 1139035510, 1075725333, 1047927533, 2533475997, 74 | 4179048485, 4204312805, 1688341868, 2090541618, 3831310773, 3891664647, 2710719770, 1865709594, 3413688545, 75 | 1503575316, 2158809885, 2770697824, 2477783323, 2873006958, 2115347391, 3711571424, 838562244, 988638203, 76 | 3102908220, 2144019247, 1205908114, 2456739331, 104436258, 3300914367, 2772282416, 187276799, 760684560, 77 | 4271561899, 4039238875, 2018396317, 4097092060, 1200702509, 3929312628, 3794577925, 1589819490, 3211427707, 78 | 66385640, 4093412388, 3987097879, 1420561756, 1839036912, 2374107437, 4151750836, 2457977438, 4138716258, 79 | 4210492091, 3663647712, 323609715, 1264648382, 1312958716, 1653982164, 819956639, 3655604182, 1153090720, 80 | 1361169663, 2084261186, 727965823, 1600677905, 2391443224, 1695186921, 4020754142, 3625867829, 2989425209, 81 | 3994391874, 2448394087, 302434989, 417369879, 897299057, 2641435935, 2882539438, 4252248917, 1540376214, 82 | 601656257, 1091624574, 2226208922, 1268263401, 3768288641, 1385344985, 3181576976, 3645008999, 2993659808, 83 | 586784136, 3017146154, 3044749557, 1544002735, 2374343718, 1260967388, 1273501353, 3476179702, 1802945049, 84 | 3479410530, 1100400433, 3724047256, 2626431383, 3922338316, 350444347, 2196198905, 22268806, 2153996088, 85 | 2696785955, 3428651692, 834400275, 2791573824, 304688903, 3014924799, 1704173549, 3417903503, 218049165, 86 | 3822543819, 3807991666, 1451678166, 118613134, 1613116507, 2486205793, 403649345, 1883232031, 2483694294, 87 | 2886330302, 154370901, 1409404978, 1999728280, 665898681, 2330640958, 4216974525, 1230685308, 3603191957, 88 | 2537609529, 3695409721, 130997589, 1074821424, 160409267, 166793897, 3533042501, 1302515470, 1547007029, 89 | 2306751466, 545720763, 1402956388, 2243017696, 3555670279, 3307097140, 1166267963, 926944235, 4145725035, 90 | 2675294212, 1963938918, 366564547, 3616461573, 221971308, 834855903, 2282150771, 1766751581, 2322010158, 91 | 3004382050, 2737740598, 594221991, 3118538547, 570137231, 4191252748, 4164129137, 2217493115, 3069162601, 92 | 1387087517, 176383634, 3415298704, 1712922743, 1163215678, 1861957394, 1885367899, 3195638841, 336967606, 93 | 1077437785, 108880612, 791707097, 4134543476, 347346749, 3590507286, 1839596670, 2989186440, 2957084555, 94 | 1756439540, 249939584, 744293433, 3930813049, 671891968, 1899888366, 1074785057, 1029878879, 2358910589, 95 | 403182709, 3069166127, 785403480, 2835526119, 4014136556, 1202306932, 2741351296, 4101115151, 2219193532, 96 | 3169243110, 2822271679, 2380932542, 1871200836, 2627320597, 3135495004, 1802168566, 204939202, 1063996491, 97 | 2431111321, 1528891023, 681381298, 3254924260, 516103253, 61819576, 1468362012, 498528205, 394274011, 197579844, 98 | 404401198, 174928880, 1337509981, 3674175213, 4206965693, 3022187507, 753037764, 2036561099, 73706383, 99 | 420194521, 3278625241, 2111474095, 3465664852, 2033545766, 1487365731, 743896352, 1995758845, 1863378653, 100 | 2790756708, 1711564822, 206411513, 2645056009, 4076549877, 2727706436, 3808263267, 194579233, 1120529587, 101 | 1608948958, 65732489, 2688047865, 4009078418, 2160953785, 2151949919, 3678595840, 2316608503, 2829067588, 102 | 2937602028, 699798019, 2645060623, 303090455, 4053983567, 2759169782, 4055529729, 113865200, 3724591067, 103 | 2515886975, 2733332639, 4038258138, 3440051027, 2471642775, 2908415883, 1667177137, 2462592674, 2762901883, 104 | 551904800, 1968181152, 3484095420, 2343406258, 3524619690, 4043560548, 2688390411, 1658298178, 3523706432, 105 | 4128282016, 2798105767, 3888451401, 887702570, 840916523, 1176654108, 297904524, 921653259, 432838238, 106 | 1620315437, 78262145, 167345404, 405629426, 2655380209, 2933491746, 1445489366, 305752912, 2816307289, 107 | 1369989895, 1655265884, 3628711833, 2927508425, 99952217, 1462971389, 3498115489, 1119674482, 1210557032, 108 | 2130464646, 507514051, 2975941433, 2992463348, 1496094321, 2701288940, 4022873162, 3768713628, 168303991, 109 | 3157106083, 1795064536, 3450924308, 4155726844, 1211329272, 2353521315, 762097033, 1818793828, 3223865800, 110 | 2441776023, 3465328831, 2473577186, 4254187184, 3142403170, 1772179439, 548423203, 1597805521, 1073812464, 111 | 3334668262, 2493417567, 1463740055, 3724237462, 3997572285, 2413199071, 3686854692, 1024765707, 1842515312, 112 | 2919911094, 3224966668, 3177883926, 3240736984, 1023216482, 442913640, 1622333213, 3876435216, 2294919024, 113 | 2170042429, 2132714067, 3549607791, 1673396952, 1374602581, 1278333324, 3846243002, 429431826, 1671608496, 114 | 229717603, 46547339, 4116904380, 3888585960, 3638452114, 392073291, 1524306184, 1371441606, 4109428507, 115 | 4080485069, 2906704606, 4082826623, 2072411699, 2462896978, 2117529081, 2713720174, 357703823, 1926058868, 116 | 393866308, 1259330658, 2587464075, 1411603480, 2378136499, 2888444794, 913659348, 3231423971, 4063874612, 117 | 3399806102, 3355640975, 3391384120, 487326758, 391727212, 3998310068, 2123519017, 4184364217, 247212966, 118 | 4277492109, 2360208606, 239964793, 1896359044, 3165543586, 3812660438, 2344690396, 1507168490, 3031523146, 119 | 502798993, 4160328430, 614144587, 2955066376, 3270663241, 3594732473, 2655226347, 3723172752, 434318588, 120 | 3601247461, 361235974, 1830048941, 3010639500, 955958439, 312514729, 1703605640, 3529862065, 3830668058, 121 | 3033287289, 629662913, 349390340, 2204719132, 364375204, 1001706917, 4237584722, 2496687751, 1607480815, 122 | 3707151724, 1591895872, 3781117269, 3490947571, 1016555152, 4068401662, 3898514758, 4234842328, 2542142764, 123 | 3235734659, 1504175907, 1616022597, 3041615099, 358632862, 2068743954, 3337820604, 1623444331, 2398327989, 124 | 3028314278, 1822019674, 1068262089, 3892762457, 1418423826, 477589542, 1865956477, 2115808706, 1089556652, 125 | 48763532, 1740329713, 2012882139, 2454457981, 241820729, 3182458515, 510319067, 3295076807, 504771388, 126 | 3533857662, 2788337000, 3196364710, 3204238336, 2925042050, 2505549772, 1020082523, 4132499645, 1718944248, 127 | 1610056894, 2051769851, 1227124080, 356015512, 3730604855, 2269332384, 960336805, 1874048116, 4137009285, 128 | 3445033017, 52203051, 4200051372, 4165597855, 2388023468, 185370428, 1385927470, 3827430548, 186404943, 129 | 2266458997, 3971312852, 4264751306, 3947571963, 316953828, 1086584483, 2378786018, 2986768018, 4163122707, 130 | 323988920, 2246688145, 713889838, 2703246568, 931188016, 2988217869, 1264841870, 1952239006, 4277063168, 131 | 2695341428, 2993270325, 2509610869, 1650134274, 3870444175, 3165823446, 195190712, 3930987466, 1206727691, 132 | 4117595465, 4081986271, 248523535, 3823653831, 1694458213, 1957030859, 458513815, 2663449243, 1441649012, 133 | 1191343111, 728769657, 807974235, 2778192547, 1991569497, 1667600007, 1517636281, 985242939, 2506783921, 134 | 1142194715, 333867739, 1547670372, 4184993832, 1116476131, 4235742911, 1946654618, 135 | ].map((v) => v | 0), 136 | ); 137 | }); 138 | it('Should return the same sequence given same seeds', () => fc.assert(p.sameSeedSameSequences(mersenne))); 139 | it('Should return the same sequence when built from state', () => 140 | fc.assert(p.clonedFromStateSameSequences(mersenne))); 141 | it('Should return the same sequence if called twice', () => fc.assert(p.sameSequencesIfCallTwice(mersenne))); 142 | it('Should generate values between -2**31 and 2**31 -1', () => fc.assert(p.valuesInRange(mersenne))); 143 | it('Should impact itself with unsafeNext', () => fc.assert(p.changeSelfWithUnsafeNext(mersenne))); 144 | it('Should not impact itself with next', () => fc.assert(p.noChangeSelfWithNext(mersenne))); 145 | it('Should not impact clones when impacting itself on unsafeNext', () => 146 | fc.assert(p.noChangeOnClonedWithUnsafeNext(mersenne))); 147 | }); 148 | --------------------------------------------------------------------------------