Click here to Skip to main content
15,889,216 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 55.7K   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

 
QuestionModulo makes some trouble Pin
MichiMe16-May-19 1:01
MichiMe16-May-19 1:01 
BugGreat class, double conversion issue Pin
rkatieb10-Mar-16 2:21
rkatieb10-Mar-16 2:21 
Buganother bug..... Pin
peilinok14-Oct-14 21:11
peilinok14-Oct-14 21:11 
BugBug in void GetBase10NumberString() Pin
peilinok7-Oct-14 21:27
peilinok7-Oct-14 21:27 
GeneralRe: Bug in void GetBase10NumberString() Pin
Bill_Hallahan9-Oct-14 15:23
Bill_Hallahan9-Oct-14 15:23 
GeneralRe: Bug in void GetBase10NumberString() Pin
Rosenthal Esq.20-Nov-14 5:45
Rosenthal Esq.20-Nov-14 5:45 
QuestionWrong multiplication and division of negative numbers Pin
Member 109934045-Aug-14 8:27
Member 109934045-Aug-14 8:27 
AnswerRe: Wrong multiplication and division of negative numbers Pin
Bill_Hallahan8-Aug-14 18:35
Bill_Hallahan8-Aug-14 18:35 
AnswerRe: Wrong multiplication and division of negative numbers Pin
Bill_Hallahan5-Sep-14 15:12
Bill_Hallahan5-Sep-14 15:12 
QuestionTesting Pin
Frans_5512912-Nov-13 2:58
Frans_5512912-Nov-13 2:58 
AnswerRe: Testing Pin
Bill_Hallahan12-Nov-13 13:51
Bill_Hallahan12-Nov-13 13:51 
GeneralNice work and a few comments Pin
Dezhi Zhao5-Nov-13 17:58
Dezhi Zhao5-Nov-13 17:58 
GeneralRe: Nice work and a few comments Pin
Bill_Hallahan6-Nov-13 12:52
Bill_Hallahan6-Nov-13 12:52 
Thank you for the nice comments.

I did consider using a unsigned 64-bit integer array, however, that limits the platforms the code can be used on, so the first implementation uses 32-bit integers. I might add that at some point, but right now algorithmic issues are more serious. A multiply routine that does convolution by using forward and inverse FFTs, with a post-processing step, will be much faster multiplication than even going to 64-bit (although of course, doing the convolution with 64-bit integers will be faster still), and right now multiplication is the bottleneck. Division is slow too, but division is not used nearly as often in the algorithms I am interested int.

Still, you are not wrong. A 64-bit array would be faster. If I add that, it will be by using typedef for the main array integer type and some internal types, and, if necessary, some #ifdefs to compile for the two integer sizes. I do think it worthwhile adding that to the code at some point, and of course it will speed up all arithmetic and logical operations, not just the multiply routine.

For modern Intel x86 and x64 machines, I am also considering using SSE assembly instructions, which can do more than one multiplication in parallel. I've written SSE code before to speed up an image processing function. That two would be conditionally compiled based on the platform. However, all that is best done once the fastest algorithm is used. The algorithm is more important than the other optimizations.


As to exception handling, I do not agree. I might agree that the exceptions should be done differently, but they are necessary. If someone writes code such as:

x = y / z

And z equals zero, then it's necessary for an exception to be thrown, otherwise the error would occur silently. In this case, an exception would be thrown anyway by the C++ run-time code, only it would be an integer divide by zero. Since regular integer code can be mixed with large integer code, it's best if this is a different exception.

The same is true for failure to allocate memory. Note, some platforms can compile C++ so that when operator new fails to allocate memory, an exception is thrown anyway. In those cases, my memory checks are superfluous. Note, Visual Studio can do this with the right compile switches, but this is not universal for all compilers and all platforms.




I did not know about popcnt32 and popcnt64. That's good to know. The code could certainly have #ifdefs to use those for the platforms that support them. Thanks for the information.

I probably won't be updating this code for some time. I just started a new job, and my weekends are filled in the foreseeable future, but I do intend to update this code eventually. The next change will be the multiplication routine, but after that, I will look at these other changes. I agree, the intrinsic functions would be better, those are usually optimized for the platform.

Thanks again!

modified 18-Dec-13 20:24pm.

QuestionNice but how do you test? Pin
Axel Rietschin3-Nov-13 20:26
professionalAxel Rietschin3-Nov-13 20:26 
AnswerRe: Nice but how do you test? Pin
Bill_Hallahan4-Nov-13 12:50
Bill_Hallahan4-Nov-13 12:50 
GeneralMy vote of 4 Pin
Paulo Zemek3-Nov-13 17:55
mvaPaulo Zemek3-Nov-13 17:55 
GeneralRe: My vote of 4 Pin
Bill_Hallahan4-Nov-13 12:50
Bill_Hallahan4-Nov-13 12:50 
QuestionConfused Pin
Paulo Zemek3-Nov-13 17:33
mvaPaulo Zemek3-Nov-13 17:33 
AnswerRe: Confused Pin
Nemanja Trifunovic4-Nov-13 7:13
Nemanja Trifunovic4-Nov-13 7:13 
GeneralMessage Closed Pin
4-Nov-13 12:44
Bill_Hallahan4-Nov-13 12:44 
GeneralRe: Confused Pin
Nemanja Trifunovic4-Nov-13 14:51
Nemanja Trifunovic4-Nov-13 14:51 
AnswerRe: Confused Pin
Bill_Hallahan4-Nov-13 15:54
Bill_Hallahan4-Nov-13 15:54 
GeneralRe: Confused Pin
Paulo Zemek5-Nov-13 1:48
mvaPaulo Zemek5-Nov-13 1:48 
GeneralRe: Confused Pin
Bill_Hallahan5-Nov-13 12:44
Bill_Hallahan5-Nov-13 12:44 
GeneralRe: Confused Pin
Paulo Zemek5-Nov-13 13:07
mvaPaulo Zemek5-Nov-13 13:07 

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.