Tutorial: The Random class

This tutorial introduces generating random numbers using the Random class. Random numbers are useful in all sorts of situations including games, cryptography, and audio.

Level: Beginner

Platforms: Windows, macOS, Linux, iOS, Android

Classes: Random, Range, BigInteger, Colour

Getting started

Download the demo project for this tutorial here: PIP | ZIP. Unzip the project and open the first header file in the Projucer.

If you need help with this step, see Tutorial: Projucer Part 1: Getting started with the Projucer.

The demo project

The demo project sets up a simple text console where we can display the results of various operations. This allows us to demonstrate some of the features of the Random class in this tutorial.

In its default configuration, the demo project displays a series of 100 randomly-generated 32-bit integers. These values should be distributed roughly uniformly in the range -2,147,483,648 .. 2,147,483,647. Of course these values aren't truly random. Since they are generated by a computer, they are pseudorandom. For most purposes the Random object will be random enough, but you may need to use more sophisticated techniques for certain critical or specialist applications.

Test bed code

Throughout this tutorial we will modify parts of the demo project to demonstrate different features of the Random class. All of the code under discussion is in the MainComponent class. In fact, many of the changes in the first instance will be to the runExample() function.

void runExample()
{
logMessage ("------------------------- START --------------------------");
for (auto iteration = 0; iteration < 100; ++iteration)
{
auto randomInt = juce::Random::getSystemRandom().nextInt();
logMessage (juce::String (randomInt));
}
logMessage ("----------------------- FINISHED -------------------------");
}

Here we access a shared (global) Random object using the Random::getSystemRandom() function. We call the Random::nextInt() function with this system Random object to generate a random number. In most cases it is satisfactory to use this system random object for generating all of your random numbers. In some cases it is necessary to create your own instance of the Random class. This is usually where the values you are generating are being generated on another thread — see Tutorial: Build a white noise generator. If two (or more) threads try to access the system Random object then the sequence could get corrupted and may cause problems.

Limiting ranges

You will almost certainly want to limit the range of values generated by the Random class. While this can be done using simple arithmetic, the Random class makes it even easier.

Setting a maximum value

To set the maximum value you simply pass the Random::nextInt() an integer like so:

auto randomInt = juce::Random::getSystemRandom().nextInt (6);

This will set the maximum value to one less than the argument passed. In this case the values will be in the range 0 .. 5. It might be easier in this case to think of the argument as being the number of possible outcomes. Using 6 as the maximum value means there are 6 possible outcomes. This is just like rolling a die except the faces of the die are number 0 .. 5 rather than 1 .. 6!

Setting a minimum and maximum value

Of course, to generate values in the range 1 .. 6 we could just add 1 like this:

auto randomInt = juce::Random::getSystemRandom().nextInt (6) + 1;

But, the Random class can handle this for us by passing it a Range object

auto randomInt = juce::Random::getSystemRandom().nextInt (juce::Range<int> (1, 7));

The maximum value still needs to be one larger than the maximum value we want the Random object to be able to generate. The minimum value is inclusive and the maximum value is exclusive.

Other numerical types

In the examples above the values generated are all in the range of 32-bit integers. The Random class can generate 64-bit integer values and even arbitrarily large integers using the BigInteger class.

Larger integers

To generate 64-bit integers use the Random::nextInt64() function:

auto randomInt = juce::Random::getSystemRandom().nextInt64();

This generates an integer in the range −9,223,372,036,854,775,808 .. 9,223,372,036,854,775,807. To specify a smaller range for 64-bit integers you would need to perform the arithmetic yourself.

To generate arbitrarily large integers use the Random::nextLargeNumber() function. This returns a BigInteger object and takes a single BigInteger object as an argument to indicate the maximum value (again the maximum value is exclusive):

void runExample()
{
logMessage ("------------------------- START --------------------------");
juce::BigInteger maximumValue;
maximumValue.setBit (256);
for (auto iteration = 0; iteration < 100; ++iteration)
{
auto randomInt = juce::Random::getSystemRandom().nextLargeNumber (maximumValue);
logMessage (randomInt.toString (10));
}
logMessage ("----------------------- FINISHED -------------------------");
}

Here, we create a very large maximum value of 2256 by setting bit 256 in the maximumValue BigInteger object. The output is some very large, randomly generated integers. For example:

...
104920467355765962354471349450287456411052143302125448224736731840703932891139
113537286625531390989138082815985050172086775818737779507269901485377454431686
57847088262227027688174446009482649397747696846679750345153185249067937632876
54822036781617285561665007930420018266697875685845320423632890189355412858007
69785221527278648790869522951189615281519473001003509768672723497611119666776
53474255551114041705196319572561227500136240211575200675493708156631961926592
99704173402721617789464355135995656339243151513499563088758939994549638940776
89393625021259981953975863158742834192382645809510667002452217673394247775970
16555501734946882319812302845214545517919239951054372751749796998179564377182
----------------------- FINISHED -------------------------

Floating point values

The Random class can generate floating-point values too:

void runExample()
{
logMessage ("------------------------- START --------------------------");
for (auto iteration = 0; iteration < 100; ++iteration)
{
auto randomValue = juce::Random::getSystemRandom().nextFloat();
logMessage (juce::String (randomValue));
}
logMessage ("----------------------- FINISHED -------------------------");
}

And it can generate double values:

auto randomValue = juce::Random::getSystemRandom().nextDouble();

In both of these cases the values returned are in the range 0.0 .. 1.0. To generate a different range you will need to apply the arithmetic yourself (see Tutorial: Build a white noise generator for an example). You can use the jmap() function for this.

Visualising values

It can be useful to visualise the random values generated, rather than looking at long lists of numbers. This is especially useful if you need a set of random values with a distribution other than a uniform distribution. This can also create some interesting patterns.

Random rectangle positions

Change the MainContentComponent to the following simple code:

class MainContentComponent : public juce::Component
{
public:
MainContentComponent()
{
setSize (600, 600);
}
~MainContentComponent() {}
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::black);
g.setColour (juce::Colours::orange);
auto& random = juce::Random::getSystemRandom();
juce::Rectangle<int> rect (0, 0, 20, 20);
for (auto i = 0; i < 100; ++i)
{
rect.setCentre (random.nextInt (getWidth()), random.nextInt (getHeight()));
g.drawRect (rect);
}
}
private:
//==============================================================================
};

This should generate an image something like this:

100 randomly generated rectangles

Try resizing the window. You will notice that a new set of rectangles is generated each time the window is resized and the paint() function is called.

Exercise
Try changing the number of rectangles and the size of the rectangles. You could make the rectangle size proportion to the size of the window, for example. Try drawing circles instead of rectangles.

Random rectangle sizes

To randomise the size of the rectangles we could do this:

void paint (Graphics& g) override
{
auto& random = juce::Random::getSystemRandom();
juce::Rectangle<int> rect (0, 0, 20, 20);
for (auto i = 0; i < 100; ++i)
{
rect.setSize (random.nextInt (getWidth() / 4), random.nextInt (getHeight() / 4));
rect.setCentre (random.nextInt (getWidth()), random.nextInt (getHeight()));
g.drawRect (rect);
}
}

To make this generate squares each time we could do this:

for (auto i = 0; i < 100; ++i)
{
auto size = random.nextInt (jmin (getWidth(), getHeight()) / 4);
rect.setSize (size, size);
rect.setCentre (random.nextInt (getWidth()), random.nextInt (getHeight()));
g.drawRect (rect);
}

Random colours

Colours can be randomised too, but to create colours that work well together using randomisation is a little trickier. For example, this generates a new random colour for each of the rectangles. But this colour could be any colour:

void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::black);
auto& random = juce::Random::getSystemRandom();
juce::Rectangle<int> rect (0, 0, 20, 20);
for (auto i = 0; i < 100; ++i)
{
juce::Colour colour (random.nextInt (256),
random.nextInt (256),
random.nextInt (256));
g.setColour (colour);
rect.setCentre (random.nextInt (getWidth()), random.nextInt (getHeight()));
g.drawRect (rect);
}
}

This should look something like this:

Randomly generated colours

To limit the colours in a simple way we could randomise only one or two of the elements (red, green, or blue) and limit the range of values:

juce::Colour colour (random.nextInt (juce::Range<int> (100, 256)),
random.nextInt (juce::Range<int> (50, 200)),
200);
g.setColour (colour);

This produces colours in the pink and purple shades:

Pinks and purples

To be more specific, you could generate colours using hue, saturation, and brightness. For example, the oranges are at a hue of around 30° (or 0.083 in the 0 .. 1 range). If we generate random hues in the range 0.05 .. 0.15 these should all be different shades of orange.

for (auto i = 0; i < 100; ++i)
{
auto hue = juce::jmap (random.nextFloat(), 0.05f, 0.15f);
g.setColour (juce::Colour::fromHSV (hue, 0.9f, 0.9f, 1.0f));
rect.setCentre (random.nextInt (getWidth()), random.nextInt (getHeight()));
g.drawRect (rect);
}
Randomly generated orange hues
Exercise
Try generating other collections of random colours that work well together.

Choosing randomly from an array

You could choose randomly from a specific set of colours by using an array:

class MainContentComponent : public Component
{
public:
MainContentComponent()
{
colours.add (juce::Colours::white);
colours.add (juce::Colours::orange);
setSize (400, 400);
}
~MainContentComponent() {}
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::black);
auto& random = Random::getSystemRandom();
juce::Rectangle<float> rect (0.0f, 0.0f, 5.0f, 5.0f);
for (auto i = 0; i < 1000; ++i)
{
g.setColour (colours[random.nextInt (colours.size())]);
rect.setCentre ((float) random.nextInt (getWidth()),
(float) random.nextInt (getHeight()));
g.drawEllipse (rect, 1.0f);
}
}
private:
juce::Array<Colour> colours;
//==============================================================================
};

Here we store the items in an array. To select an item at random we generate a random value based on the array's size. Then we use this value as the index into the array.

Other distributions

All of the random numbers generated in this tutorial so far have been uniformly distributed between the minimum and maximum values. While this is not a tutorial on statistics, it can be useful to know some simple methods of making certain randomly-generated values more likely to occur than others. This has application in games, computer-generated art, and generative sound and music.

In the following code we generate 1,000 circles distributed uniformly across the window:

void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::black);
g.setColour (juce::Colours::orange);
auto& random = juce::Random::getSystemRandom();
juce::Rectangle<float> rect (0.0f, 0.0f, 5.0f, 5.0f);
for (auto i = 0; i < 1000; ++i)
{
rect.setCentre ((float) random.nextInt (getWidth()),
(float) random.nextInt (getHeight()));
g.drawEllipse (rect, 1.0f);
}
}
Uniformly distributed circles

You can see from the image that the circles are roughly distributed uniformly.

A linear distribution is where values at one end of the range are more likely than others. Values across the range become less likely towards the other end of the range. The probability between these two points changes in a linear fashion. A simple way to implement this is to generate one random value based on the outcome of another random value. In the following example we generate linearly-distributed values for both the x and y coordinates of the circles.

for (auto i = 0; i < 1000; ++i)
{
auto x = random.nextInt (random.nextInt (Range<int> (1, getWidth())));
auto y = random.nextInt (random.nextInt (Range<int> (1, getHeight())));
rect.setCentre (x, y);
g.drawEllipse (rect, 1.0f);
}

Here you can see that we generate one random value then pass the result to the Random::nextInt() function to generate the final value.

Note
We can't use zero as the maximum value passed to the Random::nextInt() function so we have to make sure the first random number has a minimum of 1.

The result should look something like this:

Circles focused around the top-left corner

Since values towards zero are more likely, the circles appear to be focused around the top-left corner. To focus the circles around the centre we could change the code like so:

auto centreX = getWidth() / 2;
auto centreY = getHeight() / 2;
for (auto i = 0; i < 1000; ++i)
{
auto x0 = random.nextInt (juce::Range<int> (1, getWidth() / 2));
auto x = random.nextInt (juce::Range<int> (centreX - x0, centreX + x0));
auto y0 = random.nextInt (juce::Range<int> (1, getHeight() / 2));
auto y = random.nextInt (juce::Range<int> (centreY - y0, centreY + y0));
rect.setCentre (x, y);
g.drawEllipse (rect, 1.0f);
}

Weighted distributions

In some cases you may want to weight the probability of certain outcomes specifically. Let's take our earlier example where we chose randomly from three colours in an array. Let's say we want white to be the most likely, orange less likely, and green even less likely than orange. We could define probabilities as shown in the following table:

Colour Probability
White 70%
Orange 25%
Green 5%

We could add an additional array of weights to our code where the indices of the weights correspond with the indices of the colours:

class MainContentComponent : public juce::Component
{
public:
MainContentComponent()
{
colours.add (juce::Colours::white);
weights.add (0.7f);
colours.add (juce::Colours::orange);
weights.add (0.25f);
weights.add (0.05f);
setSize (400, 400);
}
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::black);
juce::Random random;
juce::Rectangle<float> rect (0.0f, 0.0f, 5.0f, 5.0f);
auto sumOfWeights = sumFloatArray (weights); // [1]
for (auto i = 0; i < 1000; ++i)
{
g.setColour (colourAtQuantile (random.nextFloat() * sumOfWeights)); // [2]
rect.setCentre ((float) random.nextInt (getWidth()),
(float) random.nextInt (getHeight()));
g.drawEllipse (rect, 1.0f);
}
}
private:
static float sumFloatArray (const juce::Array<float>& values)
{
auto sum = 0.0f;
for (auto value : values)
sum += value;
return sum;
}
static int indexOfQuantile (const juce::Array<float>& weights, float quantile)
{
auto runningTotal = 0.0f; // [4]
for (auto weight : weights)
{
runningTotal += weight;
if (quantile < runningTotal) // [5]
return weights.indexOf (weight);
}
return -1;
}
juce::Colour colourAtQuantile (float quantile) const // [3]
{
auto index = indexOfQuantile (weights, quantile);
return index < 0 ? juce::Colours::transparentBlack : colours[index];
}
//==============================================================================
juce::Array<juce::Colour> colours;
juce::Array<float> weights;
//==============================================================================
};

This works as follows:

  • [1]: To generate the random number, first take the sum of the weights. In this case we know that the sum of the weights is 1.0, but using this technique the weights do not need to sum to 1.0.
  • [2]: Then generate a random number in the range 0 .. sum.
  • [3]: The colourAtQuantile() function uses this value to find the colour.
  • [4]: This is achieved by keeping a running total of the weights as we iterate over the the array of weights.
  • [5]: When the running total exceeds the randomly generated value we have found the colour.
Note
The code for this final example can be found in the RandomTutorial_02.h file of the demo project.

The result is shown in the following screenshot:

Weighted choices of colours

Summary

In this tutorial we have introduced the Random class and generating random numbers in general. After reading this tutorial you should be able to:

  • Generate random integers and floating point values.
  • Limit the range of the random numbers generated.
  • Use random values to perform drawing tasks.
  • Generate specific ranges of randomly generated colours.
  • Choose values randomly from an array, including weighting the probability of these choices.

See also

Colours::orange
const Colour orange
Definition: juce_Colours.h:145
Colours::transparentBlack
const Colour transparentBlack
Definition: juce_Colours.h:43
Graphics::drawRect
void drawRect(int x, int y, int width, int height, int lineThickness=1) const
Draws a rectangular outline, using the current colour or brush.
jmin
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
Definition: juce_MathsFunctions.h:109
Colours::black
const Colour black
Definition: juce_Colours.h:53
Component
#define Component
Definition: juce_IncludeModuleHeaders.h:34
Graphics
A graphics context, used for drawing a component or image.
Definition: juce_GraphicsContext.h:47
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for ...
Definition: juce_PlatformDefs.h:235
Graphics::fillAll
void fillAll() const
Fills the context's entire clip region with the current colour or brush.
BlocksProtocol::MessageFromDevice::logMessage
Colours::lightgreen
const Colour lightgreen
Definition: juce_Colours.h:113
jmap
constexpr Type jmap(Type value0To1, Type targetRangeMin, Type targetRangeMax)
Remaps a normalised value (between 0 and 1) to a target range.
Definition: juce_MathsFunctions.h:123
Range< int >
Component::paint
virtual void paint(Graphics &g)
Components can override this method to draw their content.
Component
The base class for all JUCE user-interface objects.
Definition: juce_Component.h:38
Component::setSize
void setSize(int newWidth, int newHeight)
Changes the size of the component.
Graphics::setColour
void setColour(Colour newColour)
Changes the current drawing colour.
Colours::white
const Colour white
Definition: juce_Colours.h:183
Random::getSystemRandom
static Random & getSystemRandom() noexcept
The overhead of creating a new Random object is fairly small, but if you want to avoid it,...