3

I want to generate random numbers between 2 ranges (100 and 150 for example) that sums up to N.

What I've done so far:

function randomNumbers() {

    let i, nb, min, max, totAtteindre, tot, ajout;

    let resultat = new (Array);
    let alea        = [];

    nb              = document.getElementById("nbjours").value;
    totAtteindre    = document.getElementById("total").value;
    min             = Math.ceil(document.getElementById("minimum").value);
    max             = Math.floor(document.getElementById("maximum").value);


    tot = 0;

    for (i = 0; i < nb ; i++)
    {
        alea[i] = Math.random() * (max - min) + min;
        alea[i] = alea[i].toFixed(2);
        alea[i] = parseFloat(alea[i]);
        tot += alea[i];

    }

    tot = parseFloat(tot);

    if (totAtteindre > tot)
    {
        ajout = (totAtteindre - tot) / nb;
        ajout = ajout.toFixed(2);
        ajout = parseFloat(ajout);

        for (i = 0; i < nb ; i++)
        {
            alea[i] += ajout;
            tot += ajout;
        }

    }
    else
    {
        ajout = (tot - totAtteindre) / nb ;
        ajout = ajout.toFixed(2);
        ajout = parseFloat(ajout);

        for (i = 0; i < nb ; i++)
        {
            alea[i] -= ajout;
            tot -= ajout;
        }

    }

    let tmp = totAtteindre - tot;
    tot += tmp;

    alea[0] += tmp;

    // Affichage en vert ou rouge pour les valeurs positives ou négatives
    for (i = 0; i < nb ; i++)
    {
        if ((alea[i] > min) && (alea[i] < max))
        {
            resultat += alea[i].toFixed(2).replace('.', ',').fontcolor("green");
            if (i < nb-1)
            {
                resultat += "<br>";
            }
        }
        else
        {
            resultat += alea[i].toFixed(2).replace('.', ',').fontcolor("red");
            if (i < nb-1)
            {
                resultat += "<br>";
            }
        }
    }

    document.getElementById("valeurs").innerHTML = resultat;
    document.getElementById("totalAp").innerHTML = tot.toFixed(2);
}
<body>
<main>

    <header>
        <h1 style="font-size: 40px; text-align: center; margin: 20px; border: solid 2px black; font-family: 'Big Caslon'"><strong>Générateur de valeurs aléatoires</strong></h1>
    </header>

    <section>
    <form style="display: block; margin: 20px; text-align: center;">
        <h1 style="margin-bottom: 50px; font-family: 'Big Caslon';" ><u>Veuillez saisir le nombre de valeurs, les bornes inférieures/supérieures approximatives et le montant à atteindre</u><br></h1>

        <fieldset>
            <legend><strong>Nombre de valeurs</strong></legend>
            <input id="nbjours" type="number" name ="nbj">
        </fieldset>

        <br>

        <fieldset>
            <legend><strong>Bornes journalières approximatives</strong></legend>
            <input id="minimum" type="number" name="mont1">
            <input id="maximum" type="number" name="mont2">
            <div class="help-tip">
                <p style="text-align: justify; font-size: smaller">Il est possible d'avoir un résultat dont les valeurs dépassent les bornes. <br>Dans ce cas-là, diminuez les bornes ou augmentez les.</p>
            </div>
        </fieldset>

        <br>

        <fieldset>
            <legend><strong>Montant à atteindre</strong></legend>
            <input id="total" type="number" name="to">
        </fieldset>
    </form>

        <div style="display: flex; justify-content: center; margin: 20px;">
            <button onclick="randomNumbers()" class="myButton">Générer</button>
        </div>
    </section>

    <section style="display: block; text-align: center; margin-top: 10px; width: 50%; margin-left: auto; margin-right: auto">

        <fieldset>

            <legend style="background-color: unset;">
                <button onclick="copyText('valeurs')">
                    Copier les valeurs
                </button>
                <br>
            </legend>
            <div id="valeurs" ></div>

        </fieldset>

    </section>

    <section style="text-align: center; margin: 10px; width: 30%; display: block; margin-left: auto; margin-right: auto">
        <fieldset>
            <legend style="background-color: firebrick"><strong>Total</strong></legend>
            <div id="totalAp"></div>
        </fieldset>
    </section>

</main>
</body>
</html>

As you can see, sometimes you get values under the min or max.

Because I add the total remaining divided by the number of values and add them to each of the values.

I don't know if there exists a way to only get values between the two ranges (minimum and maximum) strictly with like a method or else. I could maybe do if statements to not add the added value to those that would surpass the min/max values and divide more. But I tried and I would still get one or two not being strictly in the range.

Faisal Hotak
  • 425
  • 1
  • 5
  • 10
  • `I want to generate random numbers between 2 values that sums up to N` ... You will have to take N in consideration from the beginning. One way to look at this problem is to say *how to divide N into M random parts*. Or thereabouts. Its not really a javascript problem per se. More algorithm design. – GetSet Feb 13 '20 at 19:01
  • Does this answer your question? [Is there an efficient way to generate N random integers in a range that have a given sum or average?](https://stackoverflow.com/questions/61393463/is-there-an-efficient-way-to-generate-n-random-integers-in-a-range-that-have-a-g) – Peter O. May 01 '20 at 02:25
  • Yes. The answer below did. – Faisal Hotak May 01 '20 at 12:00

3 Answers3

2

This function has four parameters: size, min, max, and sum- where size is the length of the array you want to produce. will return false if the mix/max/size combo you pass is an impossible task, meaning that either picking the minimum every time would still be too high OR picking the maximum every time would still be too low.

To create the array (called set), we pick size - 1 random numbers, each time subtracting the random number from our desired sum. It's important to not that we also check with each pick that the pick does NOT cause a situation where picking the minimum every time would still be too high OR picking the maximum every time would still be too low. If we find that that would be the case, our while loop scraps that random number and chooses a different one instead. Because we use this method to never set ourselves up for failure, we only have to pick size - 1 random numbers, and what's left over in our sum variable is guaranteed to be within our min/max range AND bring our total up to our original desired sum.

The bottleneck here is that we're asking for a random float between two numbers but then rejecting it if it doesn't fit our ulterior motives. Too many rejections can slow things down, so the more padding you leave to play with, the better this is likely to perform. For example, randomSet(10, 50, 100, 500); is virtually guaranteed to stall (because we'd have to pick exactly 50.00000 TEN times in a row- and what are the chances of that??), while randomSet(10, 50, 100, 750); will take virtually no time to finish successfully.

function randomSet(size, min, max, sum) {
  if (min * size > sum || max * size < sum) return false;

  var set = [];
  for (; size > 1; size--) {
    var randomNumber = rando(min, max, "float");
    while (min * (size - 1) > sum - randomNumber || max * (size - 1) < sum - randomNumber) {
      randomNumber = rando(min, max, "float");
    }
    set.push(randomNumber);
    sum -= randomNumber;
  }
  set.push(sum);

  return set;
}

var myRandomSet = randomSet(30, 100, 200, 4500);
console.log(myRandomSet);
console.log("SUM: " + myRandomSet.reduce((a, b) => a + b, 0));
<script src="https://randojs.com/1.0.0.js"></script>

If you want floats that are rounded to the hundredths place:

function randomSet(size, min, max, sum) {
  if (min * size > sum || max * size < sum) return false;

  var set = [];
  for (; size > 1; size--) {
    var randomNumber = Math.round(rando(min, max, "float") * 100) / 100;
    while (min * (size - 1) > sum - randomNumber || max * (size - 1) < sum - randomNumber) {
      randomNumber = Math.round(rando(min, max, "float") * 100) / 100;
    }
    set.push(randomNumber);
    sum -= randomNumber;
  }
  set.push(Math.round(sum * 100) / 100);

  return set;
}

var myRandomSet = randomSet(30, 100, 200, 4500);
console.log(myRandomSet);
console.log("SUM: " + myRandomSet.reduce((a, b) => a + b, 0));
<script src="https://randojs.com/1.0.0.js"></script>

This code uses randojs.com to simplify the min/max float randomness because this algorithm was complicated enough without throwing common randomness math into the mix. So, if you want to use this code, just make sure you include this in the head tag of your html document:

<script src="https://randojs.com/1.0.0.js"></script>
Aaron Plocharczyk
  • 2,776
  • 2
  • 7
  • 15
  • This is what I was looking for. Is it possible to make the float look for number between 2 decimals after comma ? Instead of like 15/16 numbers after comma ? I tried with toFixed(2) or Math.round(n*100)/100 but I sometimes get +0,01 or -0,01 in the final answer. – Faisal Hotak Feb 15 '20 at 17:32
  • Glad to hear it! Thanks for accepting and upvoting. I've updated my answer to add an option for what you've requested. – Aaron Plocharczyk Feb 15 '20 at 22:39
1

Hi I can't understand much of the code but you can simply generate 1 number that is from 0 ≤ n ≤ TargetNumber, then subtract it from the target number. E g

local N = 100

local firstNumber = math.random(0, N)
local secondNumber = N - firstNumber

local total = firstNumber + secondNumber

print(total)

Whoops misread the question, not sure on how to restrict the range of firstNumber and secondNumber ;)

Richard Bamford
  • 1,835
  • 3
  • 15
  • 19
0

You could check if the sum of the interval is equal to the sum and take either an alorithm for the min and max value or one which if adjusted by the sum to get directly a random pair.

function generate(min, max, sum) {
    var v = min + max === sum
            ? Math.random() * (max - min) + min
            : Math.random() * (sum - max - min) + (min + max) / 2;

    return [v, sum - v];
}

console.log(...generate(6, 8, 15));
console.log(...generate(1, 10, 15));
console.log(...generate(6, 9, 15));
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392