5

I want a bit of javascript that will allow me to generate 4 random numbers that add up to a certain value e.g.

if

max = 20

then

num1 = 4
num2 = 4
num3 = 7
num4 = 5

or

max = 36

then

num1 = 12
num2 = 5
num3 = 9
num4 = 10

What I have so far is...

var maxNum = 20;
var quarter;
var lowlimit;
var upplimit;
var num1 = 1000;
var num2 = 1000;
var num3 = 1000;
var num4 = 1000;
var sumnum = num1+num2+num3+num4;

quarter = maxNum * 0.25;
lowlimit = base - (base * 0.5);
upplimit = base + (base * 0.5);

if(sumnum != maxNum){
    num1 = Math.floor(Math.random()*(upplimit-lowlimit+1)+lowlimit);
    num2 = Math.floor(Math.random()*(upplimit-lowlimit+1)+lowlimit);
    num3 = Math.floor(Math.random()*(upplimit-lowlimit+1)+lowlimit);
    num4 = Math.floor(Math.random()*(upplimit-lowlimit+1)+lowlimit);
}
Tater
  • 67
  • 1
  • 5
  • What do you have so far? We'll help you out if you've got some code and it doesn't quite work or isn't quite finished, but we're not going to write it for you. –  Oct 09 '13 at 16:49
  • You can't have 4 random number that total a predetermined value. If you you have 3 random numbers and a total then the 4th isn't random. – Chris Charles Oct 09 '13 at 16:50
  • 2
    Hint: Generate one random number r1 between 1 and max-3, then another random number r2 between 1 and max-2-r1, then another one r3 between 1 and max-1-r1-r2. The remaining number must be the difference between max and the other three numbers – devnull69 Oct 09 '13 at 16:51
  • @ChrisC That's not quite right. If I pick a random float between 0 and 1, then 1-p is going to be just as random as p. – Bill the Lizard Oct 09 '13 at 16:58
  • Sorry, you're right. I wanted them to be iid but the OP didn't ask for that. – Chris Charles Oct 09 '13 at 17:01

5 Answers5

9

This code will create four integers that sum up to the maximum number and will not be zero

var max = 36;
var r1 = randombetween(1, max-3);
var r2 = randombetween(1, max-2-r1);
var r3 = randombetween(1, max-1-r1-r2);
var r4 = max - r1 - r2 - r3;


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

EDIT: And this one will create thecount number of integers that sum up to max and returns them in an array (using the randombetween function above)

function generate(max, thecount) {
  var r = [];
  var currsum = 0;
  for(var i=0; i<thecount-1; i++) {
     r[i] = randombetween(1, max-(thecount-i-1)-currsum);
     currsum += r[i];
  }
  r[thecount-1] = max - currsum;
  return r;
}
MemeDeveloper
  • 6,457
  • 2
  • 42
  • 58
devnull69
  • 16,402
  • 8
  • 50
  • 61
  • 2
    But doesn't this solution make uneven probabilities for each of the 4 numbers? The first one has a 50% chance to take at least 50% of the whole "max-3" numbers, while the last one has a 50% chance to take at least 50% of the numbers that are left over, that's probably way less than the first one. http://pastebin.com/QbsxvGXF – olivarra1 Nov 10 '15 at 08:28
  • I just made an answer to this question with my solution, based on yours. – olivarra1 Nov 10 '15 at 10:40
6

For my own project, I found another solution that maybe it's helpful for other people.

The idea is to let all numbers have the same probability... The first thing that came to my mind was creating an array [1,2,3,..,N,undefined,undefined,....] of length max, shuffle it, and get the positions of 1,2,3,...,N with indexOf, but this was slow for big numbers.

Finally I found another solution I think it's right: Create N random numbers between 0 and 1, and this is the part of the fraction "they want to take". If all numbers pick the same number (p.e. 1) they all will get the same value.

Code, based on the solution of @devnull69:

function generate(max, thecount) {
    var r = [];
    var currsum = 0;
    for(var i=0; i<thecount; i++) {
        r.push(Math.random());
        currsum += r[i];
    }
    for(var i=0; i<r.length; i++) {
        r[i] = Math.round(r[i] / currsum * max);
    }
    return r;
}

EDIT: there's a problem with this solution with Math.round. Imagine we want 4 numbers that add up to 20, and get this numbers before doing Math.round:

 7.4 3.4 5.7 3.3 

If you add them, they sum 20. But if you apply Math.round:

 7 3 6 3

Which they add to 18.

Then in my project, I had to do another round to give those "missing" values to the numbers that have a higher decimal fraction. This gets more complex, like:

function generate(max, thecount) {
    var r = [];
    var decimals = [];
    var currsum = 0;
    for(var i=0; i<thecount; i++) {
        r.push(Math.random());
        currsum += r[i];
    }

    var remaining = max;
    for(var i=0; i<r.length; i++) {
        var res = r[i] / currsum * max;
        r[i] = Math.floor(res);
        remaining -= r[i];
        decimals.push(res - r[i]);
    }

    while(remaining > 0){
        var maxPos = 0;
        var maxVal = 0;

        for(var i=0; i<decimals.length; i++){
            if(maxVal < decimals[i]){
                maxVal = decimals[i];
                maxPos = i;
            }
        }

        r[maxPos]++;
        decimals[maxPos] = 0; // We set it to 0 so we don't give this position another one.
        remaining--;
    }

    return r;
}
olivarra1
  • 3,269
  • 3
  • 23
  • 34
2

Psuedo code for whatever language.

a = rand()
b = rand()
c = rand()
d = rand()

total = a + b + c + d
adjust = desiredNumber / total

a *= adjust
b *= adjust
c *= adjust
d *= adjust
Eli Davis
  • 415
  • 5
  • 6
0

try this:

  1. First generate one number between zero and max minus 3 (for the three numbers left).
  2. Then generate one number between first number and max minus 2.
  3. Then generate one number between first plus second number and max minus 1.
  4. Then the last number is what ever is left.
user1404617
  • 585
  • 1
  • 5
  • 20
0

It was not said that the generated random numbers should all be distinct, which is e.g. impossible if the sum of the numbers should be 4.

<!DOCTYPE html>
<html>
<head>
    <title>Random numbers</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script type="text/javascript">
        function randomSum(sum, nrOfNumbers) {
            var a = new Array(nrOfNumbers);
            var i = 0;
            var remainingSum = sum;
            while(i < nrOfNumbers) {
                if(i == (nrOfNumbers -1)) {
                    a[i] = remainingSum;
                }
                else {
                    // get a random number in range [0, remainingSum]
                    a[i] = Math.floor(Math.random() * (remainingSum + 1));
                    remainingSum -= a[i];
                }
                ++i;
            }
            return a;
        }
    </script>
</head>
<body>
    <script type="text/javascript">
        var randomNumbers = randomSum(20,4);
        for(var i in randomNumbers) {
            document.writeln(randomNumbers[i]+"<br>");
        }
    </script>
</body>
</html>
Michael Besteck
  • 2,415
  • 18
  • 10