Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generating large random numbers between an inclusive range in Node.js

So I'm very familiar with the good old

Math.floor(Math.random() * (max - min + 1)) + min;

and this works very nicely with small numbers, however when numbers get larger this quickly becomes biased and only returns numbers one zero below it (for ex. a random number between 0 and 1e100 will almost always (every time I've tested, so several billion times since I used a for loop to generate lots of numbers) return [x]e99). And yes I waited the long time for the program to generate that many numbers, twice. By this point, it would be safe to assume that the output is always [x]e99 for all practical uses.

So next I tried this

Math.floor(Math.pow(max - min + 1, Math.random())) + min;

and while that works perfectly for huge ranges it breaks for small ones. So my question is how can do both - be able to generate both small and large random numbers without any bias (or minimal bias to the point of not being noticeable)?

Note: I'm using Decimal.js to handle numbers in the range -1e2043 < x < 1e2043 but since it is the same algorithm I displayed the vanilla JavaScript forms above to prevent confusion. I can take a vanilla answer and convert it to Decimal.js without any trouble so feel free to answer with either.

Note #2: I want to even out the odds of getting large numbers. For example 1e33 should have the same odds as 1e90 in my 0-1e100 example. But at the same time I need to support smaller numbers and ranges.

like image 373
ElJay Avatar asked Jul 17 '26 06:07

ElJay


1 Answers

Your Problem is Precision. That's the reason you use Decimal.js in the first place. Like every other Number in JS, Math.random() supports only 53 bit of precision (Some browser even used to create only the upper 32bit of randomness). But your value 1e100 would need 333 bit of precision. So the lower 280 bit (~75 decimal places out of 100) are discarded in your formula.

But Decimal.js provides a random() method. Why don't you use that one?

function random(min, max){
    var delta = new Decimal(max).sub(min);
    return Decimal.random( +delta.log(10) ).mul(delta).add(min);
}

Another "problem" why you get so many values with e+99 is probability. For the range 0 .. 1e100 the probabilities to get some exponent are

e+99  => 90%, 
e+98  =>  9%,
e+97  =>  0.9%,
e+96  =>  0.09%,
e+95  =>  0.009%,
e+94  =>  0.0009%,
e+93  =>  0.00009%,
e+92  =>  0.000009%,
e+91  =>  0.0000009%,
e+90  =>  0.00000009%,
and so on

So if you generate ten billion numbers, statistically you'll get a single value up to 1e+90. That are the odds.

I want to even out those odds for large numbers. 1e33 should have the same odds as 1e90 for example

OK, then let's generate a 10random in the range min ... max.

function random2(min, max){
    var a = +Decimal.log10(min), 
        b = +Decimal.log10(max);
    //trying to deal with zero-values. 
    if(a === -Infinity && b === -Infinity) return 0;  //a random value between 0 and 0 ;)
    if(a === -Infinity) a = Math.min(0, b-53);
    if(b === -Infinity) b = Math.min(0, a-53);

    return Decimal.pow(10, Decimal.random(Math.abs(b-a)).mul(b-a).add(a) );
}

now the exponents are pretty much uniformly distributed, but the values are a bit skewed. Because 101 to 101.5 10 .. 33 has the same probability as 101.5 to 102 34 .. 100

like image 150
Thomas Avatar answered Jul 18 '26 19:07

Thomas



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!