I want to implement a method that returns true
with a probability of n/m
and returns false
with a probability of (m-n)/m
.
For example, I want to get true
with a probability of 7/10000.
To achieve this, I first get a random integer n
that is under 10000 from the function getRandomIntUnderN
. Then, I judge whether n
is smaller than (7+1), if it is, I return true
, false
if not.
I have an implementation below:
// 0 is included while n is not
const getRandomIntUnderN = (n) => {
const rn = Math.random() * n
return Math.trunc(rn)
}
// the opportunity of a truthy return value is n/m
const goAtChance = (n, m) => {
return getRandomIntUnderN(m) < n
}
// a opportunity of 7‰ to return true
console.log(goAtChance(7, 10000))
My question is: is it okay that I just judge whether n
is under (7+1) to make a perfect probability as expected?
On one hand, numbers from 1 to 7 are not distributed discretely enough in the range from 1 to 10000: it seems there will be a bias that makes it unlikely to return a truthy value.
On the other hand, since I can get a purely random number from getRandomIntUnderN
, the chance will not be affected by which numbers I choose to determine the return value. It can be [1,2,3,4,5,6,7], [21,22,23,24,25,26,27], [23,55,3,66,762,92,123] or whatever under 10000.
So, which opinion is right?
Then, I judge whether n is smaller than (7+1), if it is, return true, false if not.
This is not how you implemented it. Your code checks that n is less than 7, which is the correct way to do it.
it seems there will be a bias that makes it unlikely to return a truthy value.
Where does that statement come from? Surely you can test this premise... and see how likely it is.
the chance will not be affected by which numbers did I choose to determine the return value
This is true.
You can easily test what the distribution is of your implementation. You can call the function repeatedly and keep a record of the results you get, and see how that evolves over time. In statistics you get more reliable results the greater the size of your sample is.
Here is a snippet that keeps executing the goAtChance
function and records the total number of calls and the number of true
results. Every 10 milliseconds the results are updated on the page, including the ratio of the number of true
over the total number. This ratio should over time converge to 0.0007 if all is well.
const getRandomIntUnderN = (n) => Math.floor(Math.random() * n);
const goAtChance = (n, m) => getRandomIntUnderN(m) < n;
let [outTotal, outHits, outRatio] = document.querySelectorAll("span");
let hits = 0; // Number of results that are true
let total = 0; // Total number of results
requestAnimationFrame(function loop() {
let deadline = performance.now() + 10;
do {
hits += goAtChance(7, 10000); // boolean coerces to 0 or 1
total++;
} while (performance.now() < deadline);
// Show the accumulated results
outTotal.textContent = total;
outHits.textContent = hits;
outRatio.textContent = (hits / total).toFixed(8);
requestAnimationFrame(loop); // Allow screen to update and then continue
});
Samples: <span></span><br>
Hits: <span></span><br>
Ratio: <span></span>