Skip to main content
added 411 characters in body
Source Link
Thomas
  • 111
  • 4
public static double[] GenerateRandomNumbers(uint values, double minimum, double maximum, double sum, Random generator = null)
    {
        if (values == 0)
            throw new InvalidOperationException($"Cannot create list of zero numbers.");
        if (minimum * values > sum)
            throw new InvalidOperationException($"The minimum value ({minimum}) is too high.");
        if (maximum * values < sum)
            throw new InvalidOperationException($"The maximum value ({maximum}) is too low.");
        if (minimum > maximum)
            throw new InvalidOperationException($"The maximum value ({maximum}) is lower than the minimum value ({minimum}).");
        if (generator == null)
            generator = new Random();

        var numberList = new double[values];

        for (var index = 0; index < values - 1; index++)
        {
            var rest = numberList.Length - (index + 1);

            var restMinimum = minimum * rest;
            var restMaximum = maximum * rest;

            minimum = Math.Max(minimum, sum - restMaximum);
            maximum = Math.Min(maximum, sum - restMinimum);

            numberList[index]var newRandomValue = generator.NextDouble(minimum, maximum);
            sumnumberList[index] = newRandomValue;
            sum -= numberList[index];newRandomValue;
        }

        numberList[values - 1] = sum;

        return numberList;
    }
var min = -1.0;
var max = 1.0;
var sum = 10.0;
uint values = 123456;100000;
var seed = 123;
var generator = new Random(seed);

var randomListrandomNumbers = Extensions.GenerateRandomNumbers(values, min, max, sum, generator);

Debug.WriteLine($"Distinct Values: {randomListrandomNumbers.Distinct().Count()}");
Debug.WriteLine($"Min: {randomListrandomNumbers.Min().ToString("F99").TrimEnd('0')}");
Debug.WriteLine($"Max: {randomListrandomNumbers.Max().ToString("F99").TrimEnd('0')}");
Debug.WriteLine($"Average: {randomListrandomNumbers.Average().ToString("F99").TrimEnd('0')}");
Debug.WriteLine($"Median: {randomListrandomNumbers.Median()}");
Debug.ToStringWriteLine("F99")$"Sum: {randomNumbers.TrimEndSum('0')}"); 

Debug.WriteLine($"Sum:"\nFirst {randomList10 values:");
randomNumbers.SumTake(10).ToStringToList("F99").TrimEndForEach('0'v => Debug.WriteLine(v)}");
Distinct Values: 12320199800
Min: -0,999982522800557999962684698385
Max: 1,
Average: 0,00000810005184033385
Median value: 0,0010687396866588800128587102577371
Sum: 10

First 10 values:
0,00000000000026969113830462617
0,815630646336652
0,487091036274606
0,623283306892628
0,477558290342595
-0,903369966849391
-0,965998261219821
-0,701281160908416
-0,610592191857562
0,26017893536956

One problem I faced with Winkels' code was that it was a recursive method and for large lists (> 30 000 numbers) the program would throw StackOverflow exceptions. This is why I wrote it as a iterative function. I also tried to clean up and removed unnecessary operations. I also added support for seeding the random number generator, and some error handling.

There is still room for improvement. I haven't looked too much into how truly random this is, so investigate further if you need to do anything scientific.

public static double[] GenerateRandomNumbers(uint values, double minimum, double maximum, double sum, Random generator = null)
    {
        if (values == 0)
            throw new InvalidOperationException($"Cannot create list of zero numbers.");
        if (minimum * values > sum)
            throw new InvalidOperationException($"The minimum value ({minimum}) is too high.");
        if (maximum * values < sum)
            throw new InvalidOperationException($"The maximum value ({maximum}) is too low.");
        if (minimum > maximum)
            throw new InvalidOperationException($"The maximum value ({maximum}) is lower than the minimum value ({minimum}).");
        if (generator == null)
            generator = new Random();

        var numberList = new double[values];

        for (var index = 0; index < values - 1; index++)
        {
            var rest = numberList.Length - (index + 1);

            var restMinimum = minimum * rest;
            var restMaximum = maximum * rest;

            minimum = Math.Max(minimum, sum - restMaximum);
            maximum = Math.Min(maximum, sum - restMinimum);

            numberList[index] = generator.NextDouble(minimum, maximum);
            sum = sum - numberList[index];
        }

        numberList[values - 1] = sum;

        return numberList;
    }
var min = -1.0;
var max = 1.0;
var sum = 1.0;
uint values = 123456;
var seed = 123;
var generator = new Random(seed);

var randomList = Extensions.GenerateRandomNumbers(values, min, max, sum, generator);

Debug.WriteLine($"Distinct Values: {randomList.Distinct().Count()}");
Debug.WriteLine($"Min: {randomList.Min().ToString("F99").TrimEnd('0')}");
Debug.WriteLine($"Max: {randomList.Max().ToString("F99").TrimEnd('0')}");
Debug.WriteLine($"Average: {randomList.Average().ToString("F99").TrimEnd('0')}");
Debug.WriteLine($"Median: {randomList.Median().ToString("F99").TrimEnd('0')}");
Debug.WriteLine($"Sum: {randomList.Sum().ToString("F99").TrimEnd('0')}");
Distinct Values: 123201
Min: -0,999982522800557
Max: 1,
Average: 0,00000810005184033385
Median value: 0,00106873968665888
Sum: 1,00000000000026

One problem I faced with Winkels' code was that it was a recursive method and for large lists (> 30 000 numbers) the program would throw StackOverflow exceptions. This is why I wrote it as a iterative function. I also tried to clean up and removed unnecessary operations. I also added support for seeding the random number generator, and some error handling.

public static double[] GenerateRandomNumbers(uint values, double minimum, double maximum, double sum, Random generator = null)
    {
        if (values == 0)
            throw new InvalidOperationException($"Cannot create list of zero numbers.");
        if (minimum * values > sum)
            throw new InvalidOperationException($"The minimum value ({minimum}) is too high.");
        if (maximum * values < sum)
            throw new InvalidOperationException($"The maximum value ({maximum}) is too low.");
        if (minimum > maximum)
            throw new InvalidOperationException($"The maximum value ({maximum}) is lower than the minimum value ({minimum}).");
        if (generator == null)
            generator = new Random();

        var numberList = new double[values];

        for (var index = 0; index < values - 1; index++)
        {
            var rest = numberList.Length - (index + 1);

            var restMinimum = minimum * rest;
            var restMaximum = maximum * rest;

            minimum = Math.Max(minimum, sum - restMaximum);
            maximum = Math.Min(maximum, sum - restMinimum);

            var newRandomValue = generator.NextDouble(minimum, maximum);
            numberList[index] = newRandomValue;
            sum -= newRandomValue;
        }

        numberList[values - 1] = sum;

        return numberList;
    }
var min = -1.0;
var max = 1.0;
var sum = 0.0;
uint values = 100000;
var seed = 123;
var generator = new Random(seed);

var randomNumbers = Extensions.GenerateRandomNumbers(values, min, max, sum, generator);

Debug.WriteLine($"Distinct Values: {randomNumbers.Distinct().Count()}");
Debug.WriteLine($"Min: {randomNumbers.Min()}");
Debug.WriteLine($"Max: {randomNumbers.Max()}");
Debug.WriteLine($"Average: {randomNumbers.Average()}");
Debug.WriteLine($"Median: {randomNumbers.Median()}");
Debug.WriteLine($"Sum: {randomNumbers.Sum()}"); 

Debug.WriteLine("\nFirst 10 values:");
randomNumbers.Take(10).ToList().ForEach(v => Debug.WriteLine(v));
Distinct Values: 99800
Min: -0,999962684698385
Max: 1
Average: 0
Median: 0,00128587102577371
Sum: 0

First 10 values:
0,969113830462617
0,815630646336652
0,487091036274606
0,623283306892628
0,477558290342595
-0,903369966849391
-0,965998261219821
-0,701281160908416
-0,610592191857562
0,26017893536956

One problem I faced with Winkels' code was that it was a recursive method and for large lists (> 30 000 numbers) the program would throw StackOverflow exceptions. This is why I wrote it as a iterative function. I also tried to clean up and removed unnecessary operations. I also added support for seeding the random number generator, and some error handling.

There is still room for improvement. I haven't looked too much into how truly random this is, so investigate further if you need to do anything scientific.

Source Link
Thomas
  • 111
  • 4

This question is maybe old, but here is my solution (written in C#), which is based on Maarten Winkels Java code:

public static double[] GenerateRandomNumbers(uint values, double minimum, double maximum, double sum, Random generator = null)
    {
        if (values == 0)
            throw new InvalidOperationException($"Cannot create list of zero numbers.");
        if (minimum * values > sum)
            throw new InvalidOperationException($"The minimum value ({minimum}) is too high.");
        if (maximum * values < sum)
            throw new InvalidOperationException($"The maximum value ({maximum}) is too low.");
        if (minimum > maximum)
            throw new InvalidOperationException($"The maximum value ({maximum}) is lower than the minimum value ({minimum}).");
        if (generator == null)
            generator = new Random();

        var numberList = new double[values];

        for (var index = 0; index < values - 1; index++)
        {
            var rest = numberList.Length - (index + 1);

            var restMinimum = minimum * rest;
            var restMaximum = maximum * rest;

            minimum = Math.Max(minimum, sum - restMaximum);
            maximum = Math.Min(maximum, sum - restMinimum);

            numberList[index] = generator.NextDouble(minimum, maximum);
            sum = sum - numberList[index];
        }

        numberList[values - 1] = sum;

        return numberList;
    }

Code for generating random double values between a minimum and maximum value:

public static double NextDouble(this Random generator, double minimum, double maximum)
    {
        if (minimum > maximum)
            throw new InvalidOperationException($"The maximum value ({maximum}) is lower than the minimum value ({minimum}).");

        return generator.NextDouble() * (maximum - minimum) + minimum;
    }

And here is how it is used:

var min = -1.0;
var max = 1.0;
var sum = 1.0;
uint values = 123456;
var seed = 123;
var generator = new Random(seed);

var randomList = Extensions.GenerateRandomNumbers(values, min, max, sum, generator);

Debug.WriteLine($"Distinct Values: {randomList.Distinct().Count()}");
Debug.WriteLine($"Min: {randomList.Min().ToString("F99").TrimEnd('0')}");
Debug.WriteLine($"Max: {randomList.Max().ToString("F99").TrimEnd('0')}");
Debug.WriteLine($"Average: {randomList.Average().ToString("F99").TrimEnd('0')}");
Debug.WriteLine($"Median: {randomList.Median().ToString("F99").TrimEnd('0')}");
Debug.WriteLine($"Sum: {randomList.Sum().ToString("F99").TrimEnd('0')}");

The output:

Distinct Values: 123201
Min: -0,999982522800557
Max: 1,
Average: 0,00000810005184033385
Median value: 0,00106873968665888
Sum: 1,00000000000026

One problem I faced with Winkels' code was that it was a recursive method and for large lists (> 30 000 numbers) the program would throw StackOverflow exceptions. This is why I wrote it as a iterative function. I also tried to clean up and removed unnecessary operations. I also added support for seeding the random number generator, and some error handling.