使用crypto.getRandomValues()生成0到1之间的随机数。

6

看起来 Math.random() 生成的是一个范围为 [0,1) 的 64 位浮点数,而新的 crypto.getRandomValues() API 只返回整数。如何使用该 API 产生一个在 [0,1) 范围内的理想数字呢?

这个方法似乎有效但不够优化:

ints = new Uint32Array(2)
window.crypto.getRandomValues(ints)
return ints[0] / 0xffffffff * ints[1] / 0xffffffff

编辑:澄清一下,我正在尝试产生比Math.random()更好的结果。根据我对浮点数的理解,应该有可能得到一个完全随机的52位小数。(?)

编辑2:为了提供一些背景信息,我并不打算做什么加密安全的事情,但有很多关于Math.random()实现不良的轶事(例如http://devoluk.com/google-chrome-math-random-issue.html),所以在有更好的选择时,我想使用它。


我在Chrome中尝试了你的代码,也得到了大于1的值,例如2.5...。 - Šime Vidas
为什么要使用 Math.abs() 函数?从 Chrome 中看到的,随机数都是正数。 - Šime Vidas
@ŠimeVidas 实际上,它们必须是正数,因为 OP 是使用 Uint(无符号整数)缓冲区视图。 crypto.getRandomValues 只生成一个随机的二进制缓冲区,由使用的视图类型将其转化为数字。 - apsillers
为什么要用那个表达式?只用 ints[0] / 0xffffffff 不是可以产生一个随机的 [0,1) 值吗? - Šime Vidas
@apsillers 很好的观点;我尝试了几种不同的方法,并在复制/粘贴中混淆了它们! - svachalek
显示剩余2条评论
3个回答

7

请记住,浮点数只是一个 尾数 系数,乘以2的指数

floating_point_value = mantissa * (2 ^ exponent)

Math.random用于生成浮点数,其有一个32位的随机尾数,且指数始终为-32,因此小数点会向左移动32位,这样尾数就不会存在小数点左边的部分。

mantissa =         10011000111100111111101000110001 (some random 32-bit int)
mantissa * 2^-32 = 0.10011000111100111111101000110001

尝试运行Math.random().toString(2)几次以验证这一点。

解决方案:您可以生成一个随机的32位尾数,然后乘以Math.pow(2,-32)

var arr = new Uint32Array(1);
crypto.getRandomValues(arr);
var result = arr[0] * Math.pow(2,-32);
// or just   arr[0] * (0xffffffff + 1);

请注意,浮点数的分布不均匀(可能的值随着数字变大而变得更加稀疏,因为尾数精度不足),使它们不适用于需要非常强的随机数的密码学应用程序或其他领域。为此,您应该使用 crypto.getRandomValues() 提供给您的原始整数值。
编辑:在 JavaScript 中,尾数有 52 位,因此您可以获得 52 位的随机性。
var arr = new Uint32Array(2);
crypto.getRandomValues(arr);

// keep all 32 bits of the the first, top 20 of the second for 52 random bits
var mantissa = (arr[0] * Math.pow(2,20)) + (arr[1] >>> 12)

// shift all 52 bits to the right of the decimal point
var result = mantissa * Math.pow(2,-52);

所以,总的来说,不,这并不比您自己的解决方案更短,但我认为这是您能够期望的最好结果。您必须生成52位随机数,这需要从32位块构建,然后它需要向下移动到小于1的位置。

1
这相当于 var result = arr[0] / ( 0xffffffff + 1 );,对吧? - Šime Vidas
我应该澄清一下,我正在寻找与Math.random()相同但更好的接口,即52位的随机性。 - svachalek
@ŠimeVidas 是的,这比每次计算 Math.pow 更好。谢谢,我已经添加到我的答案中了。 - apsillers
@svachalek编辑了一个解决方案,但恐怕它并没有比你自己的更短! - apsillers
我认为这样做更好,因为缺少随机位相乘;虽然我不是很擅长数学,但这让我觉得某种程度上减少了随机性。我认为使用常量比Math.pow快:(ints[0] * 0x100000 + (ints[1] & 0xfffff)) / 0xfffffffffffff - svachalek
显示剩余2条评论

0

如果您确实需要在范围[0,1)内的数字,那么这就是最优的情况。

该代码的问题在于不同数字的概率不再相同。

例如,使用该代码更有可能获得0.5(1*0.5、0.5*1、0.75*0.666)而不是1(1*1)。


@ŠimeVidas 没错,我的确修改了错误的示例代码,但我认为规范很清晰。 - svachalek

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接