终于等到你!JS 即将原生支持“可播种”的 PRNG 随机数

B站影视 内地电影 2025-06-06 14:27 2

摘要:JS 的 PRNG (Pseudo-Random Number Generator) 方法,例如:Math.random、crypto.getRandomvalues 等均 自动生成种子,即每次调用都会生成一个新的不可预测的随机数,并且无法在运行 (Run)

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

1. 为什么需要种子伪随机数

JS 的 PRNG (Pseudo-Random Number Generator) 方法,例如:Math.random、crypto.getRandomvalues 等均 自动生成种子,即每次调用都会生成一个新的不可预测的随机数,并且无法在运行 (Run) 或域 (Realm) 之间重现。然而,有些用例确实需要可重现的随机值,因此希望能够自行设置随机生成器。

典型的用例包括:

新的 API,例如: CSS Custom Paint,虽然无法存储状态,但可以频繁调用,因此希望能够在每次调用时生成相同的伪随机数集测试框架出于某些目的利用随机性,并希望能够在本地和持续集成上使用相同的伪随机数来重现给定的测试结果使用随机性并希望避免 “保存 - 加载 (save-scumming)” 的游戏,即玩家保存并反复重新加载游戏,直到符合自己的目的

目前,实现以上目标的唯一方法是用 JS 手动实现自己的伪随机数生成器 (PRNG)。像 LCG 这样的简单 PRNG 编写并不难,但无法生成良好的伪随机数,而更优秀的 PRNG 则更难实现。

总之有时候,开发者确实想为生成器提供种子并生成可预测序列。目前,可以依靠 JS 特性来提供比典型随机数库更好的可用性,以满足此类用例的需求。

2. 种子伪随机数如何使用

Random.Seeded 对象一旦构造完成就会有一个 .random 方法,其作用与 Math.random 完全相同,因此,任何现有的 Math.random 用法都可以用于初始化 Random.Seeded 对象,然后调用 prng.random 来替代。

let globalPRNG = new Random.Seeded(0);Math.random = globalPRNG.random.bind(globalPRNG);// 此时 `Math.random` 在每次页面加载时都生成相同值序列

以下是 Random.Seeded API 提案的概要:

Random.Seeded = class Random.Seeded {#state: Uint8Array;constructor(init: Uint8Array | Number) {if(looksLikeAState(init)) this.#state = copy(init);else if(looksLikeASeed(init)) this.#state = stateFromSeed(init);else if(isNumber(init)) this.#state = stateFromNumber(init);else throw TypeError("Random.Seeded(init) argument must be a seed, a state, or a Number.");random: Number {let [val, this.#state] = randomVal(this.#state); // Number in [0,1)return val;seed: Uint8Array {let [seed, this.#state] = randomSeed(this.#state); // Uint8Array that's a valid seed.return seed;getState: Uint8Array {return copy(this.#state);setState(state: Uint8Array): undefined {if(looksLikeAState(state)) this.#state = copy(val);else throw TypeError("Random.Seeded.setState(v) argument must be a valid state.")Random.random = function: Number {return TheInternalBrowserPRNG.random;Random.seed = function: Uint8Array {return TheInternalBrowserPRNG.seed;}3. 种子伪随机数典型用例3.1 获取随机数:.random 方法

如果要从 PRNG 对象获取随机数,可以使用 .random 方法。每次调用时,其会根据状态输出一个在 [0,1) 范围内的适当伪随机数,然后更新其状态以进行下一次调用。

生成此值的方法:

从 PRNG 获取 64 位随机数右移 11 位,得到一个 53 位整数将此整数转换为等效的 float64 类型将此 float64 类型乘以 1/(2**53)

因此,使用 prng 对象与使用 Math.random 基本相同:

const prng = new Random.Seeded(0);for(let i = 0; i 3.2 获取随机种子:.seed 方法

很多时候,开发者需要在页面上生成多个不同的 PRNG ,例如:游戏可能需要一个用于地形生成,一个用于云生成,一个用于 AI 等等。

使用单个 Random.Seeded 对象确实也可以实现,例如:将每三个值中的第一个设置为地形,第二个设置为云等等,但这很不合理,而且浪费资源。

此时,开发者可以使用现有 Random.Seeded 对象中的随机种子来初始化多个 Random.Seeded 对象,如下所示:

const parent = new Random.Seeded(0);const child1 = new Random.Seeded(parent.seed);const child2 = new Random.Seeded(parent.seed);// child1.random != child2.random

以上示例可确保 “子”PRNG 在从同一个 “父”PRNG 启动时始终生成相同的值序列,同时充分利用所有可能的种子熵。

3.3 序列化 / 恢复 / 克隆 PRNG:.getState 和 .setState 方法

.getState 方法返回一个包含 PRNG 当前状态的 Uint8Array,而. setState 方法设置一个包含 PRNG 状态的 Uint8Array 并验证该状态是否为 PRNG 有效,并用该数据替换其自身状态。

接着,开发者可以像下面这样克隆 PRNG:

const prng = new Random.Seeded(0);for(let i = 0; i

例如,游戏可以将 prng 的当前状态存储在文件中,确保在加载时生成与玩家继续玩游戏时相同的随机数序列。

来源:小向科技论

相关推荐