0
\$\begingroup\$

Util functions to generate random numbers with upper bound or with both upper and lower bounds:

var waver = (function() {
    return { between: function(min, max) {
                 return Math.floor( Math.random() * ( max - min + 1 ) + min );
           }
           , until: function(max) {
                 return Math.floor( Math.random() * ( max + 1 ) );
           }
};})();
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Please more context for why you need bound random numbers. \$\endgroup\$
    – pacmaninbw
    Commented Aug 18, 2023 at 13:00
  • 3
    \$\begingroup\$ Yes, it seems to work. Review done. 😏 That being said: I do not understand why the object is called "waver"? Something with the words "random", "select" or "pick" would make more sense. I would also use "upto" instead of "until". The latter most often refers to a point in time, or when something happens, not a specific number. \$\endgroup\$ Commented Aug 18, 2023 at 13:45
  • \$\begingroup\$ @KIKOSoftware Could you change the comment? I think that the code is off by one (see my answer. Please notify me because I've linked to the current comment in my answer (due to the naming, that's a fair assessment). \$\endgroup\$ Commented Aug 20, 2023 at 23:20
  • 1
    \$\begingroup\$ @MaartenBodewes I can't tell whether user275698 intends the result to include or exclude the max value. Returning a exclusive results would be logical: waver.until(10) would then return 10 possible values, instead of 11, but as I said, user275698 might have intended to return 11 possible values. \$\endgroup\$ Commented Aug 21, 2023 at 7:19
  • \$\begingroup\$ "Until" is indeed not the word that I would have used in that case - but yeah, handling time is always fickle. Maybe should not put too much fate in that word. \$\endgroup\$ Commented Aug 21, 2023 at 7:51

1 Answer 1

2
\$\begingroup\$

As already indicated in the comments, the naming of the functions is weird. Both waver and until are not well chosen.

The functions you've provided look very much as the example in the JavaScript documentation for the function Math.random():

function getRandomInt(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min) + min); // The maximum is exclusive and the minimum is inclusive
}

However, you can see that the + 1 you did isn't there, that's if the max value is inclusive. However, you've specified "until" as indicator, which would mean that the intent is to be exclusive.


The problem with using floating point values and operations is that they are always limited by a specific amount of precision. That in turn will mean that the function is ever so slightly biased. Usually this amount of bias can be safely ignored, but now always.

Instead it may be a better idea to use and rejection sampling using integers. Additionally I'd rather use a cryptographically secure random number generator that provides many security properties over Random.

function getRandomInt(max) {
  if (max <= 0 || !Number.isInteger(max)) {
    throw new Error("The max value should be a positive integer.");
  }
  
  const bitLength = Math.ceil(Math.log2(max));
  const byteLength = Math.ceil(bitLength / 8);
  const rejectionLimit = Math.pow(2, bitLength) - max;
  
  const buffer = new Uint8Array(byteLength);
  
  while (true) {
    crypto.getRandomValues(buffer);
    
    let randomValue = 0;
    for (let i = 0; i < byteLength; ++i) {
      randomValue |= (buffer[i] << (8 * i));
    }
    
    // Zero out the bits that are out of range, based on bitLength
    randomValue &= (Math.pow(2, bitLength) - 1);
    
    // Reject the sample if it's larger than the max value.
    if (randomValue >= rejectionLimit) {
      continue;
    }
    
    return randomValue % max;
  }
}

To use this with a minimum value as well:

function getRandomIntInRange(min, max) {
  if (!Number.isInteger(min) || !Number.isInteger(max) || max <= min) {
    throw new Error("Both min and max should be integers with max > min.");
  }

  const range = max - min;
  const randomInt = getRandomInt(range);
  return randomInt + min;
}

Beware that this is untested code. I'm pretty sure it works, but please test it before actual deployment.

\$\endgroup\$
4
  • \$\begingroup\$ Warning crypto is only available when running a secure context. Also one would use crypto not because of any bias in Math.random (it is a rather good PRNG on the major browsers), but because random can be determined if one has knowledge of a set/sequence of results, which makes it insecure (not bias). \$\endgroup\$
    – Blindman67
    Commented Aug 20, 2023 at 22:35
  • \$\begingroup\$ @Blindman67 Any secure code should be run in a secure context, i.e. over TLS :) I've changed the wording w.r.t. the CSPRNG, I didn't want to indicate that this has anything to do with using floating point, just that it is something that I would change as well. \$\endgroup\$ Commented Aug 20, 2023 at 23:19
  • 2
    \$\begingroup\$ Javascript uses double-precision floating-point values, with a precision of roughly 15 to 17 digits. That's a lot. I don't think this "bias" will be a problem in normal applications, but it is good to be aware of it. \$\endgroup\$ Commented Aug 21, 2023 at 7:17
  • \$\begingroup\$ @KIKOSoftware Yeah, you're right. Of course there are other reasons to use a cryptographic random instead of a normal random, e.g. you cannot calculate the state and find other random values before or after the random produced. You'll quickly find that getRandomValues will only return integer based arrays, so then you'd have to produce a double precision float from that , and in that case you might as well use rejection sampling. \$\endgroup\$ Commented Aug 21, 2023 at 22:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.