Click here to Skip to main content
15,916,835 members
Articles / Programming Languages / C++

A Large Integer Class

Rate me:
Please Sign up or sign in to vote.
4.61/5 (17 votes)
5 Nov 2013MIT4 min read 56.5K   1.3K   27   26
Large Integer class acts similar to built-in type

Introduction

This article is to provide the LargeInteger class for use by others. This class provides the ability to have integer values that are thousands of digits long, or longer. The class overloads all mathematical, logical, shift, and comparison operators, so that the class can be used in expressions. The class also has both constructors and equals operator for the basic signed and unsigned integer types.

The theoretical longest integer length is 231 - 1 dwords, where a dword is 4 bytes. This limitation is because, in some places in the code, the unsigned integer index into an array of 32-bit dwords is cast to be a signed integer. This sets the maximum number to be 233 - 4 bytes long, or approximately 8 billion bytes long!

This limitation is not an issue on any computer I have today, because other practical concerns involving both time and memory limit the maximum integer length well before this theoretical limit will be reached.

There's currently no error checking code to see if the maximum index value exceeds the limit. If this is a concern, check the length of the number by calling the GetBinaryValue method with an argument of 0. There is an example of how to use this method below.

In addition, the class provides methods to set the large integer from either a string or a buffer, and to get the large integer contents.

The class also overloads istream and ostream methods. At present, the istream methods are not tested. The most practical way to enter a very long integer into a large integer is to use a null-terminated character string that contains the integer value. There are examples of how to do this below.

Background

I wrote this class over 15 years ago. I didn't finish debugging all of the code, and put the code aside. I only started working on it again a few weeks ago. Some of the algorithms used in the code, such as the way to convert base 10 values to base 8, are from Knuth's "Seminumerical Algorithms".

I will be working on this code to improve it further. Currently, the code only works for ASCII builds. Also, the multiplication method is a simple O(n2) method, which is okay for multiplicands with a few thousand digits, but too slow for numbers with a million digits. Eventually, I'll be incorporating the Schönhage-Strassen algorithm, or something very similar, which will allow multiplying very large integers in a reasonable amount of time.

List of Files

  • TLargeInteger.cpp - A simple large integer test program
  • LargeInteger.cpp - The large integer class implementation file
  • LargeInteger.h - The large integer class definition file
  • TLargeInteger.vcproj - A Visual Studio 2008 Project file
  • TLargeInteger.sln - A Visual Studio 2008 Solution file

About the Code

The code provides an example of how to overload arithmetic and logical operations in C++.

The following operator methods implement the basic arithmetic operations that take a single LargeInteger as an argument.

C++
operator +=(const LargeInteger & addend);
operator -=(const LargeInteger & subtrahend);
operator *=(const LargeInteger & addend);
operator /=(const LargeInteger & multiplier);

All other arithmetic operators are implemented in terms of these methods. To make this clearer, the implementation for other product operators, all that either directly, or indirectly, use operator *=, are shown below:

C++
//======================================================================
// operator * for integral types.
//======================================================================

//======================================================================
// Multiplication of two instances of this class.
//======================================================================

LargeInteger operator *(const LargeInteger & multiplier_a,
                       const LargeInteger & multiplier_b)
{
   return LargeInteger(multiplier_a) *= multiplier_b;
}

//======================================================================
// Multiplication of an instance of the LargeInteger class and a signed integer.
//======================================================================

LargeInteger operator *(const LargeInteger & x_multiplier,
                       int multiplier)
{
   return x_multiplier * LargeInteger(multiplier);
}

LargeInteger operator *(int multiplier,
                       const LargeInteger & x_multiplier)
{
   return x_multiplier * multiplier;
}

//======================================================================
// Multiplication of an instance of the LargeInteger class and an unsigned integer.
//======================================================================

LargeInteger operator *(const LargeInteger & x_multiplier,
                       unsigned int multiplier)
{
   return x_multiplier * LargeInteger(multiplier);
}

LargeInteger operator *(unsigned int multiplier,
                       const LargeInteger & x_multiplier)
{
   return x_multiplier * multiplier;
}

I believe this is the simplest implementation, although not necessarily the most efficient way to implement the operations.

The same thing is done for logical operations. See operator |= in the code for one example.

Using the Code

After including file LargeInteger.h in your C++ file, here are some ways to set a LargeInteger to a value.

C++
// Use a large integer constructor to set 'a' to negative 20.

LargeInteger a = -20;

// Declaring x results in x = 0;

LargeInteger x;

// Increment x to be the value 1.

x++;

// User an equals operator to set x to 123

x = 123;

// User an equals operator to set x to a large magnitude negative number in base 10.

x = "-12345678901234567890123456789012345678901234567890";

// Change the default base to be base 16.  The SetDefaultBase method 
//only affects the constructor and operator equals method that take
a single null-terminated string for their only argument.

x.SetDefaultBase(16);

// User an equals operator to set x to a large number in base 16.

x = "FFFFFFFF00000000FFFFFFFF123456789ABCDEF";

// Another way to set x to the value above.

x.SetValue("FFFFFFFF00000000FFFFFFFF123456789ABCDEF");

// Set the internal number to the binary number 0x07FFFFFFFFFFFFFFFFFFF using
// the SetBinaryValue method.

char number_array[10] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F };
x.SetBinaryValue(&number_array[0], 10);

Here are some ways to either get or display the number in a large integer.

C++
// After including <iostream>, dump the value one of the ways shown.

std::cout << "Base 10 value of x = " << x << std::endl;

std::cout << "Base 8 value of x = " << std::oct << x << std::endl;

std::cout << "Base 10 value of x = " << std::dec << x << std::endl;

std::cout << "Base 16 value of x = " << std::hex << x << std::endl;

//------------------------------------------------------------
// After including <string> in your C++ file, use
// the GetNumberString method.
//-----------------------------------------------------------

std::string number_string;
unsigned int base = 10

x.GetNumberString(number_string, 10);

std::cout << "x = " << number_string.c_str() << std::endl;

//------------------------------------------------------------
// Get a copy of the binary data inside of the LargeInteger
// using the GetBinaryValue method.
//
// First find the required buffer length by passing 0
// for the argument to the GetBinaryValue method.
//------------------------------------------------------------

unsigned int length = x.GetBinaryValue(0);

char * buffer_ptr = new char [length];

if (buffer_ptr != 0)
{
   x.GetBinaryValue(buffer_ptr);
}

Calculations are done like any other built-in type.

C++
LargeInteger x = "12345678901234567890"

unsigned int y = 4;

LargeInteger z = x * y;

z = z + 42;

LargeInteger k = "987654321098765432109876543210987654321";

z = z - k;

z = -z / 5;

LargeInteger divisor = "123456789";

x = z / divisor;

If a memory allocation error, a divide-by-zero error with a LargeInteger divisor, or other errors occur, then a LargeIntegerException will be thrown. The LargeIntegerException class is defined in file LargeInteger.h and is derived from std::exception. It's somewhat simplistic to use a single exception type for multiple categories of errors. In the future, I might improve the exception code by providing more exception classes. All such classes will be derived from LargeIntegerException, so that client code that uses the current LargeInteger class won't have to be modified.

Points of Interest

I spent unnecessary extra time debugging the multiplication code because of the following test case:

C9F2C9CD04674EDEA40000000 = 38D7EA4C68000 * 38D7EA4C68000

Notice the two multiplicands are the same, and end in 000. I omitted those zeros to make the numbers smaller and I used the calculator program on Windows, with hex-mode selected, to compute the following product.

38D7EA4C68 * 38D7EA4C68

The calculator displayed the product "2C9CD04674EDEA4", which doesn't match the result of my program. My test program is correct, the WindowsTM calculator silently truncates the result!

The truncated answer displayed in the WindowsTM calculator can be seen in the result:

C9F2C9CD04674EDEA40000000 

History

  • Initial post
  • Updated code to fix sign issue in the multiplication and division operations

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior)
United States United States
I'm an electrical engineer who has spend most of my career writing software. My background includes Digital Signal Processing, Multimedia programming, Robotics, Text-To-Speech, and Storage products. Most of the code that I've written is in C, C++ and Python. I know Object Oriented Design and I'm a proponent of Design Patterns.

My hobbies include writing software for fun, amateur radio, chess, and performing magic, mostly for charities.

Comments and Discussions

 
GeneralRe: Confused Pin
Bill_Hallahan5-Nov-13 14:06
Bill_Hallahan5-Nov-13 14:06 
Paulo, each routine is very different.

Ignoring the input and output routines for numbers, the four main mathematical routines are operator *=, operator /=, operator +=, and operator -=.

These allow the following types of calculations respectively.

C++
x *= y;
x /= y;
x += y;
x -= y;


All other operations, such as the operator + that takes two LargeIntegers for arguments, or one of the operator + methods that take a LargeInteger and an build-int type, such as 'int', or 'unsigned int' are implemented in terms of one of those four methods. Note, there are two methods for each built-in type, one with the built-in type the first argument, and one the built-in type the second argument. These each are converted to a LargeInteger using one of the LargeInteger constructors, and then call the operator + that takes two LargeIntegers for arguments. That then calls the operator += method.

Note, there is a special unary operator -, that is just to make a number negative, for example:

C++
x = -y


The logical operations, such as operator |, and others, are implemented the same way. For example, all operator | methods eventually call operator |=.

There are shift operators, such as operator << and operator >>, again, based on operator <<= and operator >>= respectively. These allow expressions such as:

C++
x <<= 5;
x = y << z;


And, of course, using >> to shift to the right.

Finally, there are six comparison operators, operator ==, operator !=, operator <, operator <= operator >, and operator >=. Although all of these could be implemented just calling operator <, they use both operator < and operator == for efficiency reasons.

To explain that last sentence, the main part of the body of operator == can be implemented in terms of operator < using the following code:

C++
bool is_equal = !(x < y) && !(y < x);


However, I did not use this relation. I implemented operator == in the more straightforward way. With operator < and operator ==, all other comparison operators can be generated.

See the code for how the comparison operators are implemented. (By the way, I wonder if they couldn't be more efficient by rearranging these. While it's necessary to scan every dword to check for equality, for testing for inequality, one could exit as soon as any byte mismatches, so instead of implementing operator != in terms of operator =, perhaps I should have done that the opposite way. I am considering changing this, but right now, making the multiplication routine faster is a higher priority for me).

Of course, the comparison operators allow expression such as:

C++
bool is_less_than = x < y;

if (x < y)
{
    // Do something here.
    a = a + 1;
}


As far as the mathematics goes, that would take a really long article to explain. Check the Knuth book.

The multiply routine uses base 4294967296 numbers. 4294967296 is 2 to the 32 power. Each digit is a 32-bit number from 0 to 4294967295 to form the base 4294967296 number. Two 32-bit products are multiplied to form a 64-bit product, and multiplication is done just like you would do it on paper, by multiplying each digit in the first number by every digit of the other number to produce a partial product, and then moving to the next digit in the first number and calculating the next partial product, and repeating this until all digits in the first number are done.

Note the two nested loops in the multiply routine, the outer one for the first number and the inner one for the second number. Finally, adding the resulting sums produces the answer. However, the sums are calculated for each partial product before calculating the next partial product. Other than that, it's like multiplying by hand, only using a very large base.

The divide routine less complicated. I believe I just used shift and subtract. I'm sure that could be done more efficiently too.

Check out the reference I gave for the input conversions between various bases. It's "Seminumerical Algorithms" by Knuth.

I hope that helps.

modified 18-Dec-13 20:23pm.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.