Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Randomly split a given number M into N parts

So, the idea that I have, is to be able to divide $2.00 into 10 person, and each of them will receive $x.xx amount of money randomly. (N and M will always be limited to 2 decimals and > 0)

Ex: {0.12, 0.24, 1.03, 0.01, 0.2, 0.04, 0.11, 0.18, 0.05, 0.02}

Currently I have tried:

private static BigDecimal[] randSum(int n, double m)
{
    Random rand = new Random();
    BigDecimal randNums[] = new BigDecimal[n], sum = new BigDecimal(0).setScale(2);

    for (int i = 0; i < randNums.length; i++)
    {
        randNums[i] = new BigDecimal(rand.nextDouble()).setScale(2, RoundingMode.HALF_EVEN);
        sum = sum.add(randNums[i]);
    }

    for (int i = 0; i < randNums.length; i++)
    {
        BigDecimal temp1 = randNums[i].divide(sum, 2, RoundingMode.HALF_EVEN);
        BigDecimal temp2 = temp1.multiply(new BigDecimal(m).setScale(2));
        randNums[i] = temp2;
    }

    return randNums;
}

public static void main(String[] args)
{
    BigDecimal d[] = randSum(5, 2);

    double sum = 0;
    for (BigDecimal n : d)
    {
        sum += n.doubleValue();
        System.out.println(n);
    }
    System.out.println("total: " + sum);
}

But BigDecimals are too confusing and they don't add up. Sometimes the total is 1.98 or 2.01. Doubles doesn't work because of the Double-precision floating-point.

The code was taken from:

Getting N random numbers that the sum is M

like image 311
feco Avatar asked Dec 24 '22 15:12

feco


2 Answers

Let's suppose you need a fixed precision (passed as prec argument):

static public BigDecimal[] split(BigDecimal sum, int prec, int count) {
    int s = sum.scaleByPowerOfTen(prec).intValue();
    Random r = new Random();
    BigDecimal[] result = new BigDecimal[count];
    int[] v = new int[count];

    for (int i = 0; i < count - 1; i++)
       v[i] = r.nextInt(s);
    v[count - 1] = s;

    Arrays.sort(v);
    result[0] = BigDecimal.valueOf(v[0]).scaleByPowerOfTen(-prec);
    for (int i = 1; i < count; i++)
       result[i] = BigDecimal.valueOf(v[i] - v[i - 1]).scaleByPowerOfTen(-prec);
    return result;
}

This approach uses property that Random.nextInt() is uniformly distributed. After sorting, values of v[] array are points by which the whole amount is split, so you generate result using differences between neighboring elements:

[   2,    5,   10,   11, ...,  197,  200]  // v[]
[0.02, 0.03, 0.05, 0.01, ...,  ..., 0.03]  // result[]

Here you operate with integer values, so rounding issues don't bother anymore.

like image 123
Alex Salauyou Avatar answered Jan 09 '23 20:01

Alex Salauyou


I suggest to multiply all the numbers by 100 and rephrase your problem: generate the n random non-negative integer numbers which sum equals to the given m integer number. Later you can divide all the generated numbers by 100 to get what you want. Here's my implementation (similar to @SashaSalauyou version):

private static int[] randSum(int n, int min, int m) {
    Random rand = new Random();
    int[] nums = new int[n];
    int max = m - min*n;
    if(max <= 0)
        throw new IllegalArgumentException();
    for(int i=1; i<nums.length; i++) {
        nums[i] = rand.nextInt(max);
    }
    Arrays.sort(nums, 1, nums.length);
    for(int i=1; i<nums.length; i++) {
        nums[i-1] = nums[i]-nums[i-1]+min;
    }
    nums[nums.length-1] = max-nums[nums.length-1]+min;
    return nums;
}

I also added one more parameter, min which is the minimal wanted number. Set it to 0 if you accept zeros in the answer. Otherwise you may set it to 1 (then after division by 100 the lowest possible number will be 0.01).

like image 30
Tagir Valeev Avatar answered Jan 09 '23 20:01

Tagir Valeev