Tutorial: The BigInteger class

This tutorial introduces the BigInteger class, which is for handling arbitrarily large integers. BigInteger objects are often used in cryptography applications, when large bit masks are needed, and anywhere else where really large integers are needed.

Level: Beginner

Platforms: Windows, macOS, Linux, iOS, Android

Classes: BigInteger, TextEditor, MemoryBlock

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 sets up a simple text console where we can display the results of various calculations. This allows us to demonstrate some of the operations that can be performed by the BigInteger class in this tutorial.

In its default configuration, the demo project displays a series of 100 integers starting with the value 11. Each new value is the old value multiplied by 11.

As you can see, the BigInteger class can indeed represent very large integers! A signed 32-bit integer (for example, the `int`

type on most compilers) can represent numbers up to 2,147,483,647 and a 64-bit integer (the `int64`

type in JUCE) can represent up to "just" 9,223,372,036,854,775,807!

Throughout this tutorial we will modify parts of the demo project to demonstrate different features of the BigInteger class. All of the code under discussion is in the `MainComponent`

class. In fact, all of the changes will be to the `runExample()`

function.

void runExample()

{

logMessage ("------------------------- START --------------------------");

int base = 10;

juce::BigInteger bigInt = 11;

for (auto iteration = 0; iteration < 100; ++iteration)

{

logMessage (bigInt.toString (base));

bigInt *= 11;

}

logMessage ("----------------------- FINISHED -------------------------");

}

Here we can see the BigInteger object initialised to 11 then it is multiplied by 11 for each iteration of the `for()`

loop. The `logMessage()`

function simply posts the string that is passed to our TextEditor object.

Now that we have introduced our test bed code we can start trying out some more operations on BigInteger objects.

- Exercise
- Try out different start values and multipliers to generate different geometric sequences.

The BigInteger class supports binary operations, too. In fact, the BigInteger class is often used as a bit mask (for example, the AudioIODevice class uses BigInteger objects to represent enabled input and output channels — see Tutorial: The AudioDeviceManager class). Here is a simple example where we display the BigInteger values in binary (using base-2) starting with a value of 3 (`11`

in binary) and bit-shift to the left by one place for each iteration:

void runExample()

{

logMessage ("------------------------- START --------------------------");

int base = 2;

juce::BigInteger bigInt = 3;

for (auto iteration = 0; iteration < 100; ++iteration)

{

logMessage (bigInt.toString (base));

bigInt = bigInt << 1;

}

logMessage ("----------------------- FINISHED -------------------------");

}

Here you will see that we end up with a binary value containing two 1s and many trailing zeros:

...

11000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

1100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

11000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

1100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

11000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

----------------------- FINISHED -------------------------

- Exercise
- Try a different starting bit pattern. Or start with a very large value and gradually bit-shift it to the right instead.

We can also set and test bits individually. For example, this sets the first thirty even-numbered bits and displays the result in binary, decimal, hexadecimal and octal:

void runExample()

{

logMessage ("------------------------- START --------------------------");

juce::BigInteger bigInt;

for (auto bit = 0; bit < 60; bit += 2)

bigInt.setBit (bit);

logMessage (juce::String ("binary: ") + bigInt.toString (2));

logMessage (juce::String ("decimal: ") + bigInt.toString (10));

logMessage (juce::String ("hex: ") + bigInt.toString (16));

logMessage (juce::String ("octal: ") + bigInt.toString (8));

logMessage ("----------------------- FINISHED -------------------------");

}

The result should be:

------------------------- START --------------------------

binary: 10101010101010101010101010101010101010101010101010101010101

decimal: 384307168202282325

hex: 555555555555555

octal: 25252525252525252525

----------------------- FINISHED -------------------------

- Exercise
- Try setting different patterns of bits using the code above as a starting point.

We can test bits in a BigInteger object using the subscript operator as if a BigInteger object is an array of `bool`

values. This example uses our original code to generate a geometric sequence and tests bit 3 for each value produced:

void runExample()

{

logMessage ("------------------------- START --------------------------");

int base = 10;

juce::BigInteger bigInt = 11;

for (auto iteration = 0; iteration < 100; ++iteration)

{

bool isBit3set = bigInt[3];

logMessage (bigInt.toString (base) + " : " + (isBit3set ? "Bit 3 is set" : "Bit 3 is NOT set"));

bigInt *= 11;

}

logMessage ("----------------------- FINISHED -------------------------");

}

This makes code much more readable than using the standard bit-wise operators in C++. Since it is so simple to set and test bits using the BigInteger class, it is useful even if the integers are small!

BigInteger objects can also be converted to, and from, arbitrary data via MemoryBlock objects. The following example converts a string to a MemoryBlock object (via a MemoryOutputStream object), then to a BigInteger, and finally back to a String object via a MemoryBlock object:

void runExample()

{

logMessage ("------------------------- START --------------------------");

juce::String originalText ("BigInteger objects are really useful for cryptography");

logMessage ("Original text: ");

logMessage (originalText);

logMessage (newLine);

juce::MemoryOutputStream originalData;

originalData << originalText;

juce::BigInteger originalInteger;

originalInteger.loadFromMemoryBlock (originalData.getMemoryBlock());

logMessage ("Original text as a BigInteger: ");

logMessage (originalInteger.toString (10));

logMessage (newLine);

juce::MemoryBlock convertedData (originalInteger.toMemoryBlock());

juce::String convertedString (convertedData.toString());

logMessage ("BigInteger converted back to a string: ");

logMessage (convertedString);

logMessage ("----------------------- FINISHED -------------------------");

}

NewLine newLine

A predefined object representing a new-line, which can be written to a string or stream.

As the message in the applications says, BigInteger objects are really useful for cryptography. The RSAKey class applies its cryptographic algorithm to BigInteger objects in order to encrypt and decrypt messages.

- Exercise
- Experiment with different strings in the code above. What happens when you use longer or shorter strings, for example?

This tutorial has introduced the BigInteger class. After following this tutorial you should be able to:

- Use BigInteger objects to store integers and apply arithmetic operations to them just like regular integers.
- Convert a BigInteger object to a string for display in binary, decimal, hexadecimal and octal.
- Test and set the individual bits of a BigInteger object.
- Convert strings and other arbitrary data to and from BigInteger objects.