3

There is this excellent question for basic random numbers in JavaScript within a specific range:

Generating random whole numbers in JavaScript in a specific range?

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

How do you do the same thing with BigInt in plain JavaScript (i.e. not with Node.js or using crypto.randomBytes)? Something that works across environments (with BigInt support)?

(Thinking out loud...) You can't just change the numbers in that formula to be appended with n to make them BigInts, because Math.random() returns a non-BigInt. An example of Math.random() returns 0.5427862726372646, that is 16 decimal places. But if I have a BigInt that is like 41223334444555556666667777777888888889999999997n, that is a 47 digit number, so multiplying 0.5427862726372646 * (10**46) gives 5.4278627263726465e+45. Wrap that in BigInt and you get BigInt(0.5427862726372646 * (10**46)) equal to 5427862726372646467145376115182187925303459840n. Hmmm... Is that the solution?

41223334444555556666667777777888888889999999997n
5427862726372646467145376115182187925303459840n

How did that work out? If that is the solution I just stumbled upon it just now trying to ask this question. Can you double check this is correct and confirm, and perhaps explain how a regular JavaScript number (with e+45 representation), when passed to the BigInt constructor, results in a seemingly accurately detailed BigInt value? So let me try then.

BigInt(Math.random() * (10 ** 45))
// => 329069627052628509799118993772820125779492864n

Hmmm. I don't understand, Math.random() was only to 16 digits?

const rand = Math.random()
// => 0.7894008119121056

BigInt(rand * (10 ** 45))
// => 789400811912105533187528403423793891092987904n

That doesn't make sense, I would have expected:

789400811912105600000000000000000000000000000

That is, 0.7894008119121056 shifted over 45 decimal places.

Not sure why this is working, is this only in Chrome?

So one final test:

console.log('10 ** 35 <=> 10 ** 45')
console.log(rint(10 ** 35, 10 ** 45).toString())
console.log('10 ** 35 <=> 10 ** 45')
console.log(rint(10 ** 35, 10 ** 45).toString())
console.log('10 ** 20 <=> 10 ** 40')
console.log(rint(10 ** 20, 10 ** 40).toString())
console.log('10 ** 20 <=> 10 ** 40')
console.log(rint(10 ** 20, 10 ** 40).toString())

function rint(min, max) {
  return BigInt(Math.random() * max - min + 1) + BigInt(min)
}

I am getting e.g.:

10 ** 35 <=> 10 ** 45 485253180777775593983353876860021068179439616n
10 ** 35 <=> 10 ** 45 178233587725359997576391063983941630941986816n
10 ** 20 <=> 10 ** 40 8245114695932740733462636549119006474240n
10 ** 20 <=> 10 ** 40 7214182941774644957099293094661617352704n

Is this a correct implementation then? How would you implement this then to get a random integer from 0 to an arbitrarily large BigInt then?

Mario Varchmin
  • 3,704
  • 4
  • 18
  • 33
Lance
  • 75,200
  • 93
  • 289
  • 503

1 Answers1

1

JavaScript numbers are always stored as double precision floating point numbers, following the international IEEE 754 standard.

This format stores numbers in 64 bits (where the number (the fraction) is stored in bits 0 to 51, the exponent in bits 52 to 62, and the sign in bit 63).

Math.random() returns only positive numbers, therefore the sign bit is irrelevant. That means that Math.random() can not produce more that 2 ** 63 different values, in fact it is less than that, as Math.random() returns only values between 0 and 1 (which means that depending on the implementation none or not all of the 11 exponent bits are used). When your range is larger than that, Math.random() will not be able to generate every number within that range. Within the limit, your approach should work.

Mario Varchmin
  • 3,704
  • 4
  • 18
  • 33
  • Is it possible to generate a random BigInt bigger than `2 ** 64`? Using any conceivable approach. – Lance Jan 12 '22 at 08:44
  • 1
    You can generate arbitrarily many single-digit strings and concatenate them together – Parzh from Ukraine Jan 12 '22 at 08:48
  • Produce multiple random numbers and concatenate the resulting string representations, so that you reach the wanted size. Then use this string as the argument in the BigInt constructor. – Mario Varchmin Jan 12 '22 at 08:49
  • [Follow-up question](https://stackoverflow.com/questions/70681782/generic-algorithm-to-produce-an-arbitrarily-large-bigint-random-number-between) to solve that last one. – Lance Jan 12 '22 at 12:45
  • For the record, since `Math.random()` returns values between 0 and 1, it can only return about `2 ** 52` different values. – jmrk Jan 12 '22 at 18:19
  • @jmrk, no it is more than 52 bits. The smaller the number, the higher the negative exponent. The exponent matters. – Mario Varchmin Jan 13 '22 at 12:42
  • 1
    @MarioVarchmin: it's true that there are more than 2**52 double values between 0 and 1, however `Math.random()` is spec'ed to return values "with approximately uniform distribution over that range", which effectively pins the exponent. I've checked V8's implementation: it assigns 52 bits randomly, the other 12 are fixed. – jmrk Jan 13 '22 at 13:20
  • Thanks for sharing your insights, @jmrk. That is one of the things I like about StackOverflow: the opportunity to learn new things, while helping others. – Mario Varchmin Jan 13 '22 at 13:54