Search code examples
javascriptrandom

Generating random numbers 0 to 1 with crypto.getRandomValues()


It looks like Math.random() generates a 64-bit floating point number in the range [0,1) while the new crypto.getRandomValues() API only returns ints. What would be the ideal way to produce a number in [0,1) using this API?

This seems to work but seems suboptimal:

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

EDIT: To clarify, I am trying to produce better results than Math.random(). From my understanding of floating point, it should be possible to get a fully random fraction for 52 bits of randomness. (?)

EDIT 2: To give a little more background, I'm not trying to do anything cryptographically secure but there are a lot of anecdotal stories about Math.random() being implemented poorly (e.g. http://devoluk.com/google-chrome-math-random-issue.html) so where a better alternative is available I'd like to use it.


Solution

  • Remember that floating point numbers are just a mantissa coefficient, multiplied by 2 raised to an exponent:

    floating_point_value = mantissa * (2 ^ exponent)
    

    With Math.random, you generate floating points that have a 32-bit random mantissa and always have an exponent of -32, so that the decimal place is bit shift to the left 32 places, so the mantissa never has any part to the left of the decimal place.

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

    Try running Math.random().toString(2) a few times to verify that this is the case.

    Solution: you can just generate a random 32-bit mantissa and multiply it by 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);
    

    Note that floating points do not have an even distribution (the possible values become sparser the larger the numbers become, due to a lack of precision in the mantissa), making them ill-suited for cryptographic applications or other domains which require very strong random numbers. For that, you should use the raw integer values provided to you by crypto.getRandomValues().

    EDIT:

    The mantissa in JavaScript is 52 bits, so you could get 52 bits of randomness:

    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);
    

    So, all in all, no, this isn't ant shorter than your own solution, but I think it's the best you can hope to do. You must generate 52 random bits, which needs to be built from 32-bit blocks, and then it need to be shifted back down to below 1.