No, it isn't.
What you're effectively doing (via a cast of binary data to a floating point value) is calculating floor(max * rand() / (RAND_MAX + 1.0)) (with RAND_MAX equal to 252−1). This will always result in a skewed distribution unless max is a factor of RAND_MAX+1, as explained here.
This is quite easy to demonstrate:
function random_number(max) {
let buffer=new ArrayBuffer(8);
let ints=new Int8Array(buffer);
window.crypto.getRandomValues(ints);
ints[7]=64-1;
ints[6]|=0xf0;
let float=new DataView(buffer).getFloat64(0,true)-1;
return Math.floor(float*Math.floor(max+1));
}
function check_skew() {
var m = Math.floor(Math.pow(2,52) * 2 / 3);
var o = [0,0];
var ns = 100000;
for (i=0; i<ns; i++) o[random_number(m)&1]++; o;
console.log("Out of "+ns+" random numbers, "+o[0]*100/ns+
"% were even and "+o[1]*100/ns+"% were odd.");
}
<button onclick="check_skew()">Click this button a few times and check the results</button>
The correct way to obtain a random integer over a specified range is to start with a uniform random number whose bit length is at least as long as that of max. Discard the higher bits, and return the result if it is less than or equal to max. Otherwise repeat the process.
Something like this, perhaps:
function rand_int(max) {
// Returns a uniform random integer from 0 to max (inclusive)
var mask = 1;
var crypto = window.crypto;
max = Math.floor(max);
if (!crypto) throw "window.crypto undefined";
if (max < 1) throw "max value too small";
if (max > 0xffffffff) throw "max value too large";
// Generate binary mask (all 1)
while (mask < max) mask = (mask << 1) | 1;
// Now generate random values until one is within range
var r = new Int32Array(1);
do {
crypto.getRandomValues(r);
r[0] &= mask;
} while (r[0] > max);
return r[0];
}