├── .github
└── workflows
│ ├── build.yml
│ └── deploy.yml
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── package.json
└── spec.emu
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build spec
2 |
3 | on: [pull_request, push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | - uses: actions/checkout@v3
11 | - uses: ljharb/actions/node/install@main
12 | name: 'nvm install lts/* && npm install'
13 | with:
14 | node-version: lts/*
15 | - run: npm run build
16 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy gh-pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | deploy:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v3
14 | - uses: ljharb/actions/node/install@main
15 | name: 'nvm install lts/* && npm install'
16 | with:
17 | node-version: lts/*
18 | - run: npm run build
19 | - uses: JamesIves/github-pages-deploy-action@v4.3.3
20 | with:
21 | branch: gh-pages
22 | folder: build
23 | clean: true
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
39 | # Only apps should have lockfiles
40 | yarn.lock
41 | package-lock.json
42 | npm-shrinkwrap.json
43 | pnpm-lock.yaml
44 |
45 | # Build directory
46 | build
47 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 ECMA TC39 and contributors
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Random Namespace and Functions
2 |
3 | * Stage 0
4 | * Authors: WhosyVox and Tab Atkins-Bittner
5 | * Champions: Tab Atkins-Bittner
6 | * Spec Text: currently this README
7 |
8 | -----
9 |
10 | Historically, JS has offered a very simple API for randomness: a `Math.random()` function that returns a random value from a uniform distribution in the range [0,1), with no further details. This is a perfectly adequate primitive, but most actual applications end up having to wrap this into other functions to do something useful, such as obtaining random *integers* in a particular range, or sampling a *normal* distribution. While it's not hard to write many of these utility functions, doing so *correctly* can be non-trivial: off-by-one errors are easy to hit even in simple "simulate a die roll" cases.
11 |
12 | This proposal aims to introduces a number of convenient functions for dealing with random values, to make common random-related code both easier to use and less suseptable to errors.
13 |
14 | Goals:
15 | * To introduce at least the lowest common set of methods for properly generating random numbers
16 | * To build a consistent API between the pre-seeded and seeded PRNG proposal
17 | * To use readily understood method names, in line with what has been done in other areas of the language (e.g. `Array.toSorted`, `Array.at`, `Object.fromEntries`)
18 |
19 | The proposal is split across four somewhat-independent parts:
20 |
21 | 1. Creating a new `Random` namespace object to host the methods.
22 | 2. Defining several new families of random functions.
23 | 3. Specifying the PRNG algorithm.
24 | 4. Applying all the functions to `SeededPRNG` as well.
25 |
26 | # Part 1: The `Random` Namespace
27 |
28 | The current `Math.random()` method is on the `Math` namespace object,
29 | presumably just because it had to live *somewhere*
30 | and since it returns a number `Math` seemed vaguely appropriate.
31 |
32 | We propose that a new `Random` namespace object be created,
33 | to host all of the proposed methods of this proposal.
34 | A new `Random` namespace obviates the need to put "random" into the names of all the new methods.
35 | Several of the proposed new methods also do not directly produce numbers,
36 | so putting them on `Math` seems inappropriate.
37 |
38 | The existing `Math.random()` method will be left as-is (back compat requires that),
39 | but will also be duplicated into `Random`, as `Random.random()`, for consistency.
40 |
41 | We do not propose doing anything with the existing `Crypto` namespace methods that deal with randomness.
42 | They are specifically generating *cryptographically strong* randomness,
43 | which is not a concern of these methods in general,
44 | and their API shapes are geared towards cryptographic use-cases,
45 | which are not necessarily reasonable for JS API design in general.
46 |
47 | # Part 2: The New Random Functions
48 |
49 | There is a very large family of functions we could *potentially* include.
50 | The list here is, at this point, intentionally a bit large to show off the possibilities;
51 | it is expected that the committee's feedback will reduce them to a smaller set.
52 |
53 | ## Random Numbers
54 |
55 | * `random()`: Identical to the existing `Math.random()`. Takes no arguments, returns a random Number in the range `[0, 1)` (that is, containing 0 but not 1), with a uniform distribution.
56 | * `number(lo, hi, step?)`: Returns a random `Number` in the range `[lo, hi)` (that is, containing `lo` but not `hi`), with a uniform distribution. Similar to the `Iterator.range()` proposal, third argument can be a step value, or an options bag specifying the step value and whether or not to include the endpoint.
57 | * If `step` is defined, instead generates a random integer `R` and returns `lo + step*R`, with `R` ranging from 0 to a maximum that ensures the return value is less than or equal to `hi`. (Details below.)
58 | * All arguments must be `Number`s, or else `TypeError`.
59 | * `int(lo, hi, step?)`: Returns a random integral `Number` in the range `[lo, hi]` (that is, containing both `lo` and `hi`), with a uniform distribution. `step` works the same as in `number()`.
60 | * All arguments must be integers, or else `TypeError`.
61 | * `bigint(lo, hi, step?)`: Identical to `int()`, but returns a `BigInt` instead. All arguments must be `BigInt`s, or else `TypeError`.
62 |
63 | > [!NOTE]
64 | > Do we want to enforce an ordering for `lo` and `hi`, or allow them to be out of order? I lean towards allowing them in either order, especially since for `number()` the range is asymmetric; whether you want `[-2, -5)` or `(-2, -5]` can be application-specific. Also, which value is "low" when negatives are used is ambiguous anyway; `Random.number(-2, -5)` and `Random.number(-5, -2)` both potentially look correct.
65 |
66 | > [!NOTE]
67 | > The `step` behavior for these methods is taken directly from [CSS's `random()` function](https://drafts.csswg.org/css-values-5/#random).
68 | > It's also [being proposed for `Iterator.range()`](https://github.com/tc39/proposal-iterator.range/issues/64#issuecomment-2881243363).
69 |
70 | > [!NOTE]
71 | > It's probably generally a good thing to match [`Iterator.range()`](https://github.com/tc39/proposal-iterator.range/) in the signatures, which would mean including `inclusive` as an options-bag argument, and defaulting `int()` and `bigint()` (and `number()` using a `step`) to exclude their `hi` value. That would mean, to simulate a d6, you'd need to write either `Random.int(1, 7)` or `Random.int(1, 6, {inclusive:true})`. I'm unsure if the consistency is worth it, tho...
72 |
73 | ## Collection Methods
74 |
75 | * `shuffle(arr)`: Shuffles an `Array` in-place. Argument must be an `Array` or Array-like.
76 | * `toShuffled(coll)`: Returns a fresh `Array` containing the shuffled values from `coll`, which can be any iterable.
77 | * `take(coll, n, {replace, counts, weights})`: Returns an `Array` containing `n` randomly-selected entries from `coll` (which must be an iterable). Optional arguments are:
78 | * `replace`, a boolean: if `false` (the default), takes without replacement; every value in the returned Array will be from a unique index in the original collection. (If `n` is larger than the length of `coll`, throw a `RangeError`?) If `true`, takes with replacement; values in the returned Array can be repeats (and you can take any amount of them without error).
79 | * `counts`, an iterable of non-negative integers: provides the "multiplicity" of the entries in the matching index from `coll`. `Random.take(["a", "b"], 2, {counts:[2, 3]})` is identical to `Random.take(["a", "a", "b", "b", "b"], 2)`. (In particular, this example could return "a" or "b" multiple times, even tho it's not using replacement, just like the desugared example can.) If omitted, all counts are `1`. If the iterable is too short, missing entries are treated as `0`; if too long, excess entries are ignored.
80 | * `weights`, an iterable of non-negative numbers: provides the "weight" for the entries in the matching index from `coll`, allowing some entries to be more likely to be selected than others. If omitted, all weights are `1`. If the iterable is too short, missing entries are treated as `0`; if too long, excess entries are ignored.
81 | * `counts` and `weights` can be used together; the specified weight for an entry is treated as applying to each of the multiple implied entries (not divided between them). That is, `Random.take(["a", "b"], 2, {counts: [2, 3], weights:[1, 2]})` is equivalent to `Random.take(["a", "a", "b", "b", "b"], 2, {weights: [1, 1, 2, 2, 2]})`.
82 | * `sample(coll, {counts, weights})`: Returns one item from the collection. Options are identical to `take()`. `Random.sample(coll, options)` is identical to `Random.take(coll, 1, options)[0]`, just without constructing the intermediate `Array`.
83 |
84 | ## Distribution Methods
85 |
86 | [Python includes a decent selection of distributions.](https://docs.python.org/3/library/random.html#discrete-distributions)
87 | We should probably *at least* include the normal/gaussian distribution, given its high degree of usefulness. Should we include more? All of Python's distributions? Other distributions?
88 |
89 | * `normal(mean=0, stdev=1)`: the gaussian/normal distribution
90 | * `lognormal(mean=0, stdev=1)`: the [lognormal distribution](https://en.wikipedia.org/wiki/Log-normal_distribution) - a distribution whose *log* has a `normal(mean,stdev)` distribution
91 | * `vonmisse(mean=0, kappa=0)`: the [von Misse distribution](https://en.wikipedia.org/wiki/Von_Mises_distribution), basically a gaussian over a circle
92 | * `triangular(lo=0, hi=1, mode=(lo+hi)/2)`: a [triangular distribution](https://en.wikipedia.org/wiki/Triangular_distribution) with a high point of `mode`.
93 | * `exponential(lambda=1)`: an [exponential distribution](https://en.wikipedia.org/wiki/Exponential_distribution) (from 0 to infinity). (The mean is `1/lambda`.)
94 | * `binomial(n, p=0.5)`: the [binomial distribution](https://en.wikipedia.org/wiki/Binomial_distribution) - how many successes in N trials with P chance of success? (sampling with replacement)
95 | * `geometric(p=.5)`: the [geometric distribution](https://en.wikipedia.org/wiki/Geometric_distribution) - how many failures before the first success, with P chance of success? (sampling with replacement)
96 | * `hypergeometric(n, N, K)`: the [hyper-geometric distribution](https://en.wikipedia.org/wiki/Hypergeometric_distribution) - how many successes in n trials if exactly K of the N possible outcomes are a success? (sampling *without* replacement)
97 | * `beta(alpha, beta)`: the [beta distribution](https://en.wikipedia.org/wiki/Beta_distribution)
98 | * `gamma(alpha, beta)`: the [gamma distribution](https://en.wikipedia.org/wiki/Gamma_distribution)
99 | * `pareto(alpha)`: the [Pareto distribution](https://en.wikipedia.org/wiki/Pareto_distribution)
100 | * `weibull(alpha, beta)`: the [Weibull distribution](https://en.wikipedia.org/wiki/Weibull_distribution)
101 |
102 | ## Byte Methods
103 |
104 | * `bytes(n)`: Returns a `Uint8Array` of the specified length, filled with random bytes.
105 | * `fillBytes(buffer)`: Fills the passed typed array/view/etc with random bytes. (Pass views to fill only a chunk of an array.)
106 |
107 | ## Other Value Methods
108 |
109 | * `boolean(p=0.5)`: Returns a bool, with propability `p` of returning `true`. (Exactly equivalent to `Random.random() < p`, so possibly not worth it?)
110 |
111 | # Part 3: Specifying The PRNG
112 |
113 | `Math.random()` was historically unspecified, and because some benchmarks unintentionally depended on it, has been driven toward a fast but terrible PRNG algorithm in implementations.
114 |
115 | We propose that the `Random` methods use a *specified* PRNG algorithm, specifically the same one used by the [Seeded Random proposal](https://github.com/tc39/proposal-seeded-random/) - ChaCha12. This algorithm produces high-quality pseudo-randomness (but intentionally not cryptographic quality), and has good performance characteristics relative to PRNGs of similar quality. Plus, it would mean a shared implementation between the two very similar proposals, which is likely appealing to implementations, since all of the `Random` methods are intended to appear on `SeededPRNG` as well.
116 |
117 | We will probably leave the choice of initial PRNG state to still be up to the UA.
118 |
119 | # Part 4: Interaction with `SeededPRNG`
120 |
121 | It is intended that this proposal and the [Seeded Random proposal](https://github.com/tc39/proposal-seeded-random/) expose the same APIs; every method on `Random` should exist on `SeededPRNG` objects as well. Either proposal can advance ahead of the other, however. This proposal is intentionally not touching seeded randomness, instead focusing on functions that are agnostic as to their random source.
122 |
123 | # Prior Art
124 |
125 | * [Python's `random` module](https://docs.python.org/3/library/random.html)
126 | * random float, with any min/max bounds (and any step)
127 | * random ints, with any min/max bounds (and any step)
128 | * random bytes
129 | * random selection (or N selections with replacement) from a list, with optional weights
130 | * random sampling (or N samples, *without* replacement) from a list, with optional counts
131 | * randomly shuffle an array
132 | * sample various random distributions: binomal, triangular, beta, exponential, gamma, normal, log-normal, von Mises, Pareto, Weibull
133 | * [.Net's `Random` class](https://learn.microsoft.com/en-us/dotnet/api/system.random?view=net-8.0)
134 | * random ints, with any min/max bounds
135 | * random floats, with any min/max bounds
136 | * random bytes
137 | * randomly shuffle an array
138 | * [Haskell's `RandomGen` interface](https://hackage.haskell.org/package/random-1.2.1.2/docs/System-Random.html)
139 | * random u8/u16/u32/u64, either across the full range or between 0 and max
140 | * generate two RNGs from the starting RNG (seeded-random use-case)
141 | * random from any type with a range (like all the numeric types, enums, etc), or tuples of such
142 | * random bytes
143 | * random floats between 0 and 1
144 | * [Ruby's `Random` class](https://ruby-doc.org/core-2.4.0/Random.html)
145 | * random floats between 0 and max
146 | * random ints between 0 and max
147 | * random bytes
148 | * [Common Lisp's `(random n)` function]([https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node133.html](http://clhs.lisp.se/Body/f_random.htm))
149 | * random ints between 0 and max
150 | * random floats between 0 and max
151 | * [Java's `Random` class](https://docs.oracle.com/javase/8/docs/api/java/util/Random.html)
152 | * random doubles, defaulting to [0,1) but can be given any min/max bounds
153 | * random ints (or longs), with any min/max bounds
154 | * random bools
155 | * random bytes
156 | * sample Gaussian distribution
157 | * [JS `genTest` library](https://www.npmjs.com/package/gentest)
158 | * random ints (within certain classes)
159 | * random characters
160 | * random "strings" (relatively short but random lengths, random characters)
161 | * random bools
162 | * random selection (N, with replacement) from a list
163 | * custom random generators
164 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "template-for-proposals",
4 | "description": "A repository template for ECMAScript proposals.",
5 | "scripts": {
6 | "start": "npm run build-loose -- --watch",
7 | "build": "npm run build-loose -- --strict",
8 | "build-loose": "node -e 'fs.mkdirSync(\"build\", { recursive: true })' && ecmarkup --load-biblio @tc39/ecma262-biblio --verbose spec.emu build/index.html --lint-spec"
9 | },
10 | "homepage": "https://github.com/tc39/template-for-proposals#readme",
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/tc39/template-for-proposals.git"
14 | },
15 | "license": "MIT",
16 | "devDependencies": {
17 | "@tc39/ecma262-biblio": "^2.1.2571",
18 | "ecmarkup": "^17.0.0"
19 | },
20 | "engines": {
21 | "node": ">= 12"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/spec.emu:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | title: Proposal Title Goes Here 8 | stage: -1 9 | contributors: Your Name(s) Here 10 |11 | 12 |
This is an algorithm:
15 |