For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/proposal-random-functions.md.
  • 简体中文
  • More Random Functions S1

    中文标题:更多随机函数

    提案概览
    提案速览

    该提案添加了一组均匀分布随机函数,如 Random.random()Random.number()Random.int()Random.bigint()Random.bytes()Random.fillBytes(),以及 Random.range(),以简化常见的随机化任务。它建立在种子随机提案之上,在 Random 命名空间和 Random.Seeded 类上提供方法。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    简单随机函数

    • 阶段 1
    • 作者:WhosyVox 和 Tab Atkins-Bittner
    • 提案负责人:Tab Atkins-Bittner
    • 规范文本:目前为自述文件

    历史上,JS 为随机性提供了一个非常简单的 API:Math.random() 函数,它从 [0,1) 范围内的均匀分布中返回一个随机值,没有更多细节。这是一个完全足够的原始方法,但大多数实际应用最终必须将其包装成其他函数来做一些有用的事情,例如在特定范围内获取随机整数,或采样正态分布。虽然编写其中许多实用函数并不难,但正确编写却可能并不平凡:即使在简单的“模拟掷骰子”情况下,也容易出错差一错误。

    本提案旨在添加一些新的简单均匀分布随机数方法。(应委员会要求,进一步的用例将被转移到单独的提案,特别是 随机集合函数随机分布。)

    本提案基于(现为阶段 2 的)种子随机 提案,该提案添加了一个新的 Random 命名空间对象,既用于承载 Random.Seeded 类本身,也用于承载 Random.Seeded 随机方法,这样您可以使用由用户代理创建的随机播种的 PRNG 来调用它们,而不必总是自己创建 Random.Seeded

    新的随机函数

    我们可能包含的函数族非常庞大。 本提案特意缩减为基本的一组常用随机数函数:除 0-1 之外的范围内的随机数、随机整数、随机 BigInt 和随机字节,这些方法均从均匀分布中抽取:

    • Random.random() - 生成 0-1 之间的数字
    • Random.number(a,b) - 生成 a-b 之间的数字
    • Random.range(...) - 生成一个随机值,与 Iterator.range(...) 生成的相同
    • Random.int(a,b) - 生成 a-b 之间的整数
    • Random.bigint(a,b) - 生成 a-b 之间的 bigint
    • Random.bytes(n) - 生成一个填充了 N 个随机字节的 UInt8Array
    • Random.fillBytes(buf) - 用随机字节填充 buf

    Random.random(options: RandomOptions?): Number

    返回一个范围在 [0, 1) 内(即包含 0,但不包含 1)的随机 Number, 与 Math.random() 相同, 但具有更好的建议算法。

    如果传入了 options, 且提供了有效的 step, 或提供了真值的 excludeMin, 则行为类似于 Random.number(0, 1, options)。 (因为范围是半开的, excludeMax 没有任何效果。)

    NOTE

    问题 19 讨论精确的计划算法,使用单个 64 位随机性块来获得 2^53 个可能值,这些值是均匀间隔的。 (除非它需要像 Random.number() 那样行为,在这种情况下会略有不同。)

    Random.number(lo: Number, hi: Number, stepOrOptions: (Number or RandomOptions)?): Number

    如果省略了 step, 返回一个在范围 (lo, hi) 内(即不包含 lohi)的随机 Number, 并具有均匀分布。

    如果 lo 大于 hi, 抛出 {{RangeError}}。

    如果 lohi 相等或为连续的浮点数(即它们之间没有浮点数), 则返回 lo,除非 excludeMin 选项为真; 否则返回 hi,除非 excludeMax 选项为真; 否则抛出 {{RangeError}}。

    NOTE

    问题 19 讨论精确的计划算法,使用 2 个 64 位随机性块来获得最多 2^54 个可能值,这些值是均匀间隔的。

    如果传入了 step(直接传入或作为 step 选项), 返回一个形式为 lo + N*step 的随机 Number, 范围在 [lo, hi] 内(即可能包含 lohi)。 如果传入了 excludeMinexcludeMax 选项, 它分别避免返回 lohi; 如果这导致没有可能的值可以返回, 则抛出 {{RangeError}}。

    具体来说:

    1. epsilon 为与 step 相关的小值,选择方式与 Iterator.range 问题 #64 一致。
    2. 如果 excludeMin 选项为假,则令 minN 为 0;如果为真,则为 1。
    3. maxN 为最大的整数,使得 lo + maxN*step 小于或等于 hi(如果 excludeMax 选项为假),或小于 hi(如果为真)。
    4. 如果 excludeMax 选项为假, 且 lo + maxN*step 不在 hiepsilon 范围内, 但 lo + (maxN+1)*step 在(即使它大于 hi), 则将 maxN 设置为 maxN+1
    5. 如果 minN 大于 maxN,抛出 RangeError。
    6. N 为介于 minNmaxN 之间的随机整数,包括两端。
    7. 如果 N 等于 maxNexcludeMax 选项为假,且 lo + maxN*stephiepsilon 范围内, 返回 hi。否则,返回 lo + N*step
    NOTE

    这种 step/epsilon 行为直接取自 CSS 的 random() 函数。 它也 被提议用于 Iterator.range()

    如果 step 为正,则 lo 必须小于或等于 hi, 否则抛出 {{RangeError}}。 如果 step 为负,则 lo 必须大于或等于 hi, 否则抛出 {{RangeError}}。

    Random.range(...)

    这是 Iterator.range() 的配套函数, 它将位于此提案或 Iterator.range 提案中,取决于哪个提案先进入下一阶段。

    Random.range() 的参数与 Iterator.range() 完全相同, 并且解释方式也相同。 该函数返回范围中的一个值,该值是均匀随机选择的。 如果等效范围将是无限的, 则抛出 RangeError。

    NOTE

    这只是一个便捷函数,用于我预计会常见的需求。 它是对等效的带 step 参数的 Random.number() 的语法糖。

    Random.int(lo: Number, hi: Number, stepOrOptions: (Number or RandomOptions)?): Number

    返回一个范围在 [lo, hi] 内(即包含 lohi)的随机整数 Number, 并具有均匀分布。 如果传入了 excludeMinexcludeMax 选项且为真, 它将排除 lohi。 如果范围因此不包含任何可能的值, 则抛出 RangeError。

    如果传入了 step(直接传入或作为 step 选项), 返回一个形式为 lo + N*step 的随机整数 Number, 范围在 [lo, hi] 内。 (根据所选的 histep,可能无法返回 hi 值。) 如果传入了 excludeMinexcludeMax 选项且为真, 它将排除 lohi。 如果范围因此不包含任何可能的值, 则抛出 RangeError。

    lohi 的顺序, 以及与正 step、负 step 或省略 step 的关系, 具有与 Random.number() 相同的约束。

    NOTE

    问题 19 讨论了要使用的算法。当前计划在 lo-hi 范围包含少于 2^63 个值时使用 2 个 64 位块。如果范围更大,则算法使用大约 N+1 个 64 位块,其中 N 是表示范围本身所需的 64 位块的数量(存在可忽略的拒绝机会,需要另一组块)。无论哪种情况,范围中的每个整数都是可能的,并且具有均匀的机会,这与大范围的 Random.number(lo, hi, {step:1}) 不同。(对于 .int(),如果范围超出安全整数范围,则值的概率不均匀,但相对于它们所代表的整数数量,统计上尽可能接近均匀。)

    Random.bigint(lo: BigInt, hi: BigInt, stepOrOptions: (BigInt or RandomOptions)?): BigInt

    Random.int() 相同,只是返回 BigInt。

    Random.bytes(n: Number): Uint8Array

    返回一个长度为 nUint8Array, 填充了均匀分布的随机字节。

    Random.fillBytes(buffer: BufferType, start: Number?, end: Number?): BufferType

    用均匀分布的随机字节填充传入的 TypedArrayArrayBuffer。 如果传入了 start 和/或 end, 则仅在这些位置之间填充, 与 TypedArray.prototype.fill() 相同。

    NOTE

    注意,“随机字节”对于整数类型(如 Uint8ArrayInt32Array 等)产生均匀分布的值。 对于浮点类型(如 Float64Array)则不然。 这些类型在其可能值范围内没有“均匀”的直接定义。 如果您需要更智能的方法,您需要自行编写以满足您的确切用例。

    Random.Seeded 的交互

    以上所有函数也将在 Random.Seeded 上定义为方法,具有相同的签名和行为。也就是说,Random.number(...)new Random.Seeded(...).number(...) 都将工作。

    将为 Random.Seeded 方法定义精确的生成算法,以确保可重现性。建议 Random 版本使用相同的算法,但不是严格要求的;这样做只是让您可以使用内部的 Random.Seeded 对象,避免实现两次相同的函数。

    预期问题

    为什么有些函数使用开区间,有些使用闭区间?

    算法中表示的区间的开/闭性有些偶然。它们都应该被视为闭区间,即包含两个端点。这使我们可以跨函数提供一组相同的选项(excludeMinexcludeMax)。

    对于大多数随机整数用例,开/闭/半开是极其重要且可见的 - 例如,如果模拟 d6,如果 Random.int(1,6) 使用开区间或半开区间,它将产生极其不正确的统计。

    对于几乎所有随机数字来说,并非如此。大多数时候,Random.number() 的范围将覆盖至少一个完整的浮点指数区(两个连续 2 的幂之间的范围),这意味着至少有 2^52 个可能的返回值的可能值(四千万亿!)。(计划算法最多提供 2^54,约 18 千万亿个可能值。)即使它没有覆盖完整的指数区,任何现实场景几乎肯定覆盖一个大的部分,仍然能保证一个非常大的可能值数量,几乎肯定在数十亿到数万亿之间。

    这意味着任何特定值被返回的可能性极小,例如实际的最小值或最大值。除了奇怪的角落情况(范围的两个端点非常接近的情况),您根本无法依赖看到这些值出现,因此它们是否实际上可能被看到是无关紧要的。在大多数情况下,excludeMinexcludeMaxRandom.number() 中大部分时间都没有实际作用这一事实是无法检测的。

    换一个略有不同的话题,有时范围中的大多数值完全没问题,但一些值会引起问题。通常我们不能自动处理这种情况,您必须自己做拒绝采样。例如,如果您生成 Random.number(1, 10) 但必须避免纯整数,那您自己负责。这种事情相当罕见。

    罕见的是端点在某些方面特殊。例如,1 / Random.number(0, 1) 对于几乎所有可能的值都没问题,除了 0 本身,它会导致 Infinity。您只有在不到千万亿分之一的时间里触发该问题 - 太罕见以至于永远无法依赖它发生,但也不是那么罕见以至于不可能在大规模情况下看到。通过在 Random.number() 中默认省略端点,我们避免了一整类极其罕见但仍可能发生的错误,如这个。如果作者确实指定了 excludeMin/excludeMax 选项,我们确保端点不会出现,即使范围本来会是空的。(这个论点不适用于随机整数,因为如果端点有问题,您可以... 使用下一个整数。而“下一个浮点数”则更难表达。)

    注意,Random.random() 忽略了所有这些,只有半开区间。这是因为 [0-1) 对于这个确切的用例来说是一个非常常见、规范的区间,因此很难证明违反它的合理性。此外,有一个超好、便宜的算法可以生成此范围内的数字,并且它自然地生成半开区间。

    先前技术

    • Python 的 random 模块
      • 任意最小/最大边界(以及任意步长)的随机浮点数
      • 任意最小/最大边界(以及任意步长)的随机整数
      • 随机字节
      • 从列表中随机选择(或有放回地选择 N 个),可选权重
      • 从列表中随机采样(或 N 个样本,放回),可选计数
      • 随机打乱数组
      • 采样各种随机分布:二项分布、三角分布、Beta 分布、指数分布、伽马分布、正态分布、对数正态分布、von Mises 分布、Pareto 分布、Weibull 分布
    • .Net 的 Random
      • 任意最小/最大边界的随机整数
      • 任意最小/最大边界的随机浮点数
      • 随机字节
      • 随机打乱数组
    • Haskell 的 RandomGen 接口
      • 随机 u8/u16/u32/u64,覆盖完整范围或介于 0 和最大值之间
      • 从初始 RNG 生成两个 RNG(种子随机用例)
      • 从具有范围的任何类型(如所有数字类型、枚举等)或此类元组中随机获取
      • 随机字节
      • 0 到 1 之间的随机浮点数
    • Ruby 的 Random
      • 0 到最大值之间的随机浮点数
      • 0 到最大值之间的随机整数
      • 随机字节
    • Common Lisp 的 (random n) 函数
      • 0 到最大值之间的随机整数
      • 0 到最大值之间的随机浮点数
    • Java 的 Random
      • 随机双精度数,默认为 [0,1) 但可以给定任意最小/最大边界
      • 任意最小/最大边界的随机整数(或长整数)
      • 随机布尔值
      • 随机字节
      • 采样高斯分布
    • JS genTest
      • 随机整数(在某些类中)
      • 随机字符
      • 随机“字符串”(相对较短但随机长度,随机字符)
      • 随机布尔值
      • 从列表中随机选择(有放回的 N 个)
      • 自定义随机生成器

    历史

    • 2024-04:初始阶段 0 提案已编写。
    • 2025-05:提案被接受为阶段 1,内容有所缩减(待办:链接到会议记录)