25

我知道您可以使用此函数在 JavaScript 中生成一个范围内的随机数:

function getRandomInt (min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

此处Ionuț G. Stan 提供。

我想知道的是,您是否可以使用crypto.getRandomValues()而不是 Math.random()在一个范围内生成更好的随机数。我希望能够生成一个介于 0 和 10(含)或 0-1,甚至 10-5000(含)之间的数字。

您会注意到 Math.random() 产生的数字如下:0.8565239671015732

getRandomValues API 可能返回如下内容:

  • 231Uint8Array(1)
  • 54328Uint16Array(1)
  • 355282741Uint32Array(1).

那么如何将其转换回十进制数,以便我可以使用上面相同的范围算法?还是我需要一个新算法?

这是我尝试过的代码,但效果不太好。

function getRandomInt(min, max) {       
    // Create byte array and fill with 1 random number
    var byteArray = new Uint8Array(1);
    window.crypto.getRandomValues(byteArray);

    // Convert to decimal
    var randomNum = '0.' + byteArray[0].toString();

    // Get number in range
    randomNum = Math.floor(randomNum * (max - min + 1)) + min;

    return randomNum;
}

在低端(范围 0 - 1),它返回的 0 多于 1。使用 getRandomValues() 的最佳方法是什么?

非常感谢

4

6 回答 6

21

最简单的方法可能是拒绝抽样(参见http://en.wikipedia.org/wiki/Rejection_sampling)。例如,假设max - min小于 256:

function getRandomInt(min, max) {       
    // Create byte array and fill with 1 random number
    var byteArray = new Uint8Array(1);
    window.crypto.getRandomValues(byteArray);

    var range = max - min + 1;
    var max_range = 256;
    if (byteArray[0] >= Math.floor(max_range / range) * range)
        return getRandomInt(min, max);
    return min + (byteArray[0] % range);
}
于 2013-08-14T11:25:08.313 回答
19

恕我直言,这里描述了在一个[min..max]范围内生成随机数的最简单方法。window.crypto.getRandomValues()

一个 ECMAScript 2015 语法代码,如果链接是TL;TR

function getRandomIntInclusive(min, max) {
    const randomBuffer = new Uint32Array(1);

    window.crypto.getRandomValues(randomBuffer);

    let randomNumber = randomBuffer[0] / (0xffffffff + 1);

    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(randomNumber * (max - min + 1)) + min;
}
于 2017-02-18T23:01:47.620 回答
3

其中许多答案都会产生有偏见的结果。这是一个公正的解决方案。

function random(min, max) {
    const range = max - min + 1
    const bytes_needed = Math.ceil(Math.log2(range) / 8)
    const cutoff = Math.floor((256 ** bytes_needed) / range) * range
    const bytes = new Uint8Array(bytes_needed)
    let value
    do {
        crypto.getRandomValues(bytes)
        value = bytes.reduce((acc, x, n) => acc + x * 256 ** n, 0)
    } while (value >= cutoff)
    return min + value % range
}
于 2020-12-24T16:08:10.547 回答
2

死灵术。
嗯,这很容易解决。

考虑没有加密随机范围内的随机数

// Returns a random number between min (inclusive) and max (exclusive)
function getRandomArbitrary(min, max) {
    return Math.random() * (max - min) + min;
}

/**
 * Returns a random integer between min (inclusive) and max (inclusive).
 * The value is no lower than min (or the next integer greater than min
 * if min isn't an integer) and no greater than max (or the next integer
 * lower than max if max isn't an integer).
 * Using Math.round() will give you a non-uniform distribution!
 */
function getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

所以你需要做的就是用 crypt 中的随机数替换 Math.random。

那么 Math.random 做了什么?
根据MDN, Math.random() 函数返回一个浮点伪随机数,范围为 0 到小于 1(包括 0,但不包括 1)

所以我们需要一个加密随机数 >= 0 和< 1(不是 <=)。

所以,我们需要一个来自 getRandomValues 的非负(又名 UNSIGNED)整数。
我们如何做到这一点?

简单:我们不需要获取一个整数,然后执行 Math.abs,而是获取一个 UInt:

var randomBuffer = new Int8Array(4); // Int8Array = byte, 1 int = 4 byte = 32 bit 
window.crypto.getRandomValues(randomBuffer);
var dataView = new DataView(array.buffer);
var uint = dataView.getUint32();

其简写版本是

var randomBuffer = new Uint32Array(1);
(window.crypto || window.msCrypto).getRandomValues(randomBuffer);
var uint = randomBuffer[0];

现在我们需要做的就是用 uint 除以 uint32.MaxValue(又名 0xFFFFFFFF)来得到一个浮点数。由于结果集中不能有 1,我们需要除以 (uint32.MaxValue+1) 以确保结果 < 1。
除以 (UInt32.MaxValue + 1) 有效,因为 JavaScript 整数是 64-内部为位浮点数,因此不限于 32 位。

function cryptoRand()
{
    var array = new Int8Array(4);
    (window.crypto || window.msCrypto).getRandomValues(array);
    var dataView = new DataView(array.buffer);

    var uint = dataView.getUint32();
    var f = uint / (0xffffffff + 1); // 0xFFFFFFFF = uint32.MaxValue (+1 because Math.random is inclusive of 0, but not 1) 

    return f;
}

其简写是

function cryptoRand()
{
    const randomBuffer = new Uint32Array(1);
    (window.crypto || window.msCrypto).getRandomValues(randomBuffer);
    return ( randomBuffer[0] / (0xffffffff + 1) );
}

现在您需要做的就是将上述函数中的 Math.random() 替换为 cryptoRand()。

请注意,如果 crypto.getRandomValues 使用 Windows 上的 Windows-CryptoAPI 来获取随机字节,则不应将这些值视为真正加密安全的熵源。

于 2020-07-08T10:20:34.430 回答
1

Rando.js使用crypto.getRandomValues基本上为你做这件事

console.log(rando(5, 10));
<script src="https://randojs.com/2.0.0.js"></script>

如果您想在幕后查看,这是从源代码中提取的:

var cryptoRandom = () => {
  try {
    var cryptoRandoms, cryptoRandomSlices = [],
      cryptoRandom;
    while ((cryptoRandom = "." + cryptoRandomSlices.join("")).length < 30) {
      cryptoRandoms = (window.crypto || window.msCrypto).getRandomValues(new Uint32Array(5));
      for (var i = 0; i < cryptoRandoms.length; i++) {
        var cryptoRandomSlice = cryptoRandoms[i].toString().slice(1, -1);
        if (cryptoRandomSlice.length > 0) cryptoRandomSlices[cryptoRandomSlices.length] = cryptoRandomSlice;
      }
    }
    return Number(cryptoRandom);
  } catch (e) {
    return Math.random();
  }
};

var min = 5;
var max = 10;
if (min > max) var temp = max, max = min, min = temp;
min = Math.floor(min), max = Math.floor(max);
console.log( Math.floor(cryptoRandom() * (max - min + 1) + min) );

于 2020-07-08T22:20:32.193 回答
1

如果您使用的是 Node.js,则使用加密安全的伪随机 crypto.randomInt 会更安全。如果您不知道自己在做什么并且没有同行评审,请不要编写这种敏感的方法。

官方文档

crypto.randomInt([min, ]max[, callback])

添加于:v14.10.0、v12.19.0

  • min <integer>随机范围的开始(包括)。默认值:0。
  • max <integer>随机范围的结束(不包括)。
  • callback <Function> function(err, n) {}.

返回一个随机整数 n,使得 min <= n < max。这种实现避免了模偏差

范围 ( max - min) 必须小于 2^48。min并且max必须是安全整数

如果没有提供回调函数,则同步生成随机整数。

// Asynchronous
crypto.randomInt(3, (err, n) => {
  if (err) throw err;
  console.log(`Random number chosen from (0, 1, 2): ${n}`);
});
// Synchronous
const n = crypto.randomInt(3);
console.log(`Random number chosen from (0, 1, 2): ${n}`);
// With `min` argument
const n = crypto.randomInt(1, 7);
console.log(`The dice rolled: ${n}`);
于 2021-01-12T22:48:24.310 回答