How to use window.crypto.getRandomValues to get random values in a specific range

前端 未结 2 773
没有蜡笔的小新
没有蜡笔的小新 2021-01-21 04:09

We had been using Math.random to get random numbers between 4000-64000.:

Math.floor(Math.random() * 60000 + 4000);

We have to now replace this

相关标签:
2条回答
  • 2021-01-21 04:44

    A simple replacement for Math.random might look like this:

    /**
     * Return values in the range of [0, 1)
     */
    const randomFloat = function () {
      const int = window.crypto.getRandomValues(new Uint32Array(1))[0]
      return int / 2**32
    }
    

    To extend this to integers:

    
    /**
     * Return integers in the range of [min, max)
     *
     * @todo check that min is <= max.
     */
    const randomInt = function (min, max) {
      const range = max - min
      return Math.floor(randomFloat() * range + min)
    }
    

    To extend this to arrays of integers:

    
    /**
     * Generate an array of integers in the range of [min, max).
     */
    const randomIntArray = function (length, min, max) {
      return new Array(length).fill(0).map(() => randomInt(min, max))
    }
    

    Generate an array of ten integers from 0 to 2 inclusive:

    randomIntArray(10, 0, 3)
    
    [0, 2, 1, 2, 0, 0, 1, 0, 1, 0]
    
    0 讨论(0)
  • 2021-01-21 05:05

    For a given min and max, the formula describes how many bits you'll use on average if you request u bits at once and retry if returning the result would introduce bias.

    Fortunately, the optimal strategy is simply requesting ceil(log2(max - min + 1)) bits at once. We can only get full bytes with crypto.getRandomValues anyways, so if we have one call of crypto.getRandomValues per function call, the best we can do is:

    // Generate a random integer r with equal chance in  min <= r < max.
    function randrange(min, max) {
        var range = max - min;
        if (range <= 0) {
            throw new Exception('max must be larger than min');
        }
        var requestBytes = Math.ceil(Math.log2(range) / 8);
        if (!requestBytes) { // No randomness required
            return min;
        }
        var maxNum = Math.pow(256, requestBytes);
        var ar = new Uint8Array(requestBytes);
    
        while (true) {
            window.crypto.getRandomValues(ar);
    
            var val = 0;
            for (var i = 0;i < requestBytes;i++) {
                val = (val << 8) + ar[i];
            }
    
            if (val < maxNum - maxNum % range) {
                return min + (val % range);
            }
        }
    }
    

    If you generate many values, you may consider some optimizations, namely requesting more bytes (i.e. a larger array) in advance. If your range becomes smaller (say you want to flip a coin), than it may also be beneficial to work in a bit-based manner, i.e. request many bits upfront and then only use up the random bits you really need.

    0 讨论(0)
提交回复
热议问题