How to implement big int in C++
Asked Answered
W

14

83

I'd like to implement a big int class in C++ as a programming exercise—a class that can handle numbers bigger than a long int. I know that there are several open source implementations out there already, but I'd like to write my own. I'm trying to get a feel for what the right approach is.

I understand that the general strategy is get the number as a string, and then break it up into smaller numbers (single digits for example), and place them in an array. At this point it should be relatively simple to implement the various comparison operators. My main concern is how I would implement things like addition and multiplication.

I'm looking for a general approach and advice as opposed to actual working code.

Wirework answered 6/11, 2008 at 16:11 Comment(3)
First thing - digit strings are fine, but think in terms of base 2^32 (4 billion odd distinct digits). Maybe even base 2^64 these days. Second, always work with unsigned integer "digits". You can do the twos complement for signed big integers yourself, but if you try to do your overflow handling etc with signed integers, you'll run into standards-undefined bahaviour issues.Huguenot
As for algorithms - for a basic library, the ones you learned at school are about right.Huguenot
If you want to perform the multi-precision math yourself, then I suggest you take a look at Donald Knuth's Art of Computer Programming. I believe Volume II, Seminumerical Algorithms, Chapter 4, Multiple Precision Arithmetic, is what you are interested in. Also see How to add 2 arbitrarily sized integers in C++?, which provides code for some C++ libraries and OpenSSL.Irregularity
O
38

Things to consider for a big int class:

  1. Mathematical operators: +, -, /, *, % Don't forget that your class may be on either side of the operator, that the operators can be chained, that one of the operands could be an int, float, double, etc.

  2. I/O operators: >>, << This is where you figure out how to properly create your class from user input, and how to format it for output as well.

  3. Conversions/Casts: Figure out what types/classes your big int class should be convertible to, and how to properly handle the conversion. A quick list would include double and float, and may include int (with proper bounds checking) and complex (assuming it can handle the range).

Outburst answered 6/11, 2008 at 16:18 Comment(4)
See here for the idiomatic ways to do the operators.Twink
For integers, operator << and >> are bit-shift operations. Interpreting them as I/O would be bad design.Behnken
@Dave: Except that it's standard C++ to use operator<< and operator>> with iostreams for I/O.Armoire
@Behnken You can still define << and >> for bit-shift operations along with I/O for streams...Boxberry
O
46

A fun challenge. :)

I assume that you want integers of arbitrary length. I suggest the following approach:

Consider the binary nature of the datatype "int". Think about using simple binary operations to emulate what the circuits in your CPU do when they add things. In case you are interested more in-depth, consider reading this wikipedia article on half-adders and full-adders. You'll be doing something similar to that, but you can go down as low level as that - but being lazy, I thought I'd just forego and find a even simpler solution.

But before going into any algorithmic details about adding, subtracting, multiplying, let's find some data structure. A simple way, is of course, to store things in a std::vector.

template< class BaseType >
class BigInt
{
typedef typename BaseType BT;
protected: std::vector< BaseType > value_;
};

You might want to consider if you want to make the vector of a fixed size and if to preallocate it. Reason being that for diverse operations, you will have to go through each element of the vector - O(n). You might want to know offhand how complex an operation is going to be and a fixed n does just that.

But now to some algorithms on operating on the numbers. You could do it on a logic-level, but we'll use that magic CPU power to calculate results. But what we'll take over from the logic-illustration of Half- and FullAdders is the way it deals with carries. As an example, consider how you'd implement the += operator. For each number in BigInt<>::value_, you'd add those and see if the result produces some form of carry. We won't be doing it bit-wise, but rely on the nature of our BaseType (be it long or int or short or whatever): it overflows.

Surely, if you add two numbers, the result must be greater than the greater one of those numbers, right? If it's not, then the result overflowed.

template< class BaseType >
BigInt< BaseType >& BigInt< BaseType >::operator += (BigInt< BaseType > const& operand)
{
  BT count, carry = 0;
  for (count = 0; count < std::max(value_.size(), operand.value_.size(); count++)
  {
    BT op0 = count < value_.size() ? value_.at(count) : 0, 
       op1 = count < operand.value_.size() ? operand.value_.at(count) : 0;
    BT digits_result = op0 + op1 + carry;
    if (digits_result-carry < std::max(op0, op1)
    {
      BT carry_old = carry;
      carry = digits_result;
      digits_result = (op0 + op1 + carry) >> sizeof(BT)*8; // NOTE [1]
    }
    else carry = 0;
  }

  return *this;
}
// NOTE 1: I did not test this code. And I am not sure if this will work; if it does
//         not, then you must restrict BaseType to be the second biggest type 
//         available, i.e. a 32-bit int when you have a 64-bit long. Then use
//         a temporary or a cast to the mightier type and retrieve the upper bits. 
//         Or you do it bitwise. ;-)

The other arithmetic operation go analogous. Heck, you could even use the stl-functors std::plus and std::minus, std::times and std::divides, ..., but mind the carry. :) You can also implement multiplication and division by using your plus and minus operators, but that's very slow, because that would recalculate results you already calculated in prior calls to plus and minus in each iteration. There are a lot of good algorithms out there for this simple task, use wikipedia or the web.

And of course, you should implement standard operators such as operator<< (just shift each value in value_ to the left for n bits, starting at the value_.size()-1... oh and remember the carry :), operator< - you can even optimize a little here, checking the rough number of digits with size() first. And so on. Then make your class useful, by befriendig std::ostream operator<<.

Hope this approach is helpful!

Orling answered 6/11, 2008 at 20:59 Comment(2)
"int" (as in signed) is a bad idea. Standards undefined behaviour on overflows makes it difficult (if not impossible) to get the logic right, at least portably. It's pretty easy, though, to work in twos complement with unsigned integers, where overflow behaviour is strictly defined as giving modulo 2^n results.Huguenot
Surely, if you add two numbers, the result must be greater than the greater one of those numbers, right? What if its something plus zero? You mean greater than or equal to?Alginate
O
38

Things to consider for a big int class:

  1. Mathematical operators: +, -, /, *, % Don't forget that your class may be on either side of the operator, that the operators can be chained, that one of the operands could be an int, float, double, etc.

  2. I/O operators: >>, << This is where you figure out how to properly create your class from user input, and how to format it for output as well.

  3. Conversions/Casts: Figure out what types/classes your big int class should be convertible to, and how to properly handle the conversion. A quick list would include double and float, and may include int (with proper bounds checking) and complex (assuming it can handle the range).

Outburst answered 6/11, 2008 at 16:18 Comment(4)
See here for the idiomatic ways to do the operators.Twink
For integers, operator << and >> are bit-shift operations. Interpreting them as I/O would be bad design.Behnken
@Dave: Except that it's standard C++ to use operator<< and operator>> with iostreams for I/O.Armoire
@Behnken You can still define << and >> for bit-shift operations along with I/O for streams...Boxberry
O
30

There's a complete section on this: [The Art of Computer Programming, vol.2: Seminumerical Algorithms, section 4.3 Multiple Precision Arithmetic, pp. 265-318 (ed.3)]. You may find other interesting material in Chapter 4, Arithmetic.

If you really don't want to look at another implementation, have you considered what it is you are out to learn? There are innumerable mistakes to be made and uncovering those is instructive and also dangerous. There are also challenges in identifying important computational economies and having appropriate storage structures for avoiding serious performance problems.

A Challenge Question for you: How do you intend to test your implementation and how do you propose to demonstrate that it's arithmetic is correct?

You might want another implementation to test against (without looking at how it does it), but it will take more than that to be able to generalize without expecting an excrutiating level of testing. Don't forget to consider failure modes (out of memory problems, out of stack, running too long, etc.).

Have fun!

Outrageous answered 6/11, 2008 at 20:0 Comment(1)
Comparing with some reference implementation won't get you any further, because then you have another problem: how to test if the reference implementation is also correct? The same problem is with testing knowledge in general: If one man has to test another, who will test the former? There's no way out of this problem except one, invented long time ago: proving from axioms. If the set of axioms is considered correct (no contradictions), and the proof is derived properly according to the rules of logic, it cannot be wrong, even for infinite number of cases no one could possibly test for.Countryman
K
7

addition would probably have to be done in the standard linear time algorithm
but for multiplication you could try http://en.wikipedia.org/wiki/Karatsuba_algorithm

Kamerun answered 6/11, 2008 at 16:20 Comment(0)
P
5

Once you have the digits of the number in an array, you can do addition and multiplication exactly as you would do them longhand.

Phial answered 6/11, 2008 at 16:14 Comment(0)
O
5

Don't forget that you don't need to restrict yourself to 0-9 as digits, i.e. use bytes as digits (0-255) and you can still do long hand arithmetic the same as you would for decimal digits. You could even use an array of long.

Overripe answered 6/11, 2008 at 16:15 Comment(4)
If you want to represent the numbers in decimal (i.e. for mere mortals), the 0-9 per nibble algorithm is easier. Just give up the storage.Reviewer
You figure it's easier to do BCD algorithms than their regular binary counterparts?Carnation
AFAIK base 10 is often used because converting big numbers in base 255 (or anything not a power of 10) from/to base 10 is expensive, and your programs' input and output will generally be in base 10.Inquisitionist
@Tobi: I would probably recommend base 10000 kept in unsigned, which is fast IO, and easy to do multiplication with, the downside that it wastes 59% of the storage space. I recommend base (2^32) for more advanced learning, which is much much faster than base 10/10000 for everything except IO, but much harder to implement multiplication/division.Twink
R
4

I'm not convinced using a string is the right way to go -- though I've never written code myself, I think that using an array of a base numeric type might be a better solution. The idea is that you'd simply extend what you've already got the same way the CPU extends a single bit into an integer.

For example, if you have a structure

typedef struct {
    int high, low;
} BiggerInt;

You can then manually perform native operations on each of the "digits" (high and low, in this case), being mindful of overflow conditions:

BiggerInt add( const BiggerInt *lhs, const BiggerInt *rhs ) {
    BiggerInt ret;

    /* Ideally, you'd want a better way to check for overflow conditions */
    if ( rhs->high < INT_MAX - lhs->high ) {
        /* With a variable-length (a real) BigInt, you'd allocate some more room here */
    }

    ret.high = lhs->high + rhs->high;

    if ( rhs->low < INT_MAX - lhs->low ) {
        /* No overflow */
        ret.low = lhs->low + rhs->low;
    }
    else {
        /* Overflow */
        ret.high += 1;
        ret.low = lhs->low - ( INT_MAX - rhs->low ); /* Right? */
    }

    return ret;
}

It's a bit of a simplistic example, but it should be fairly obvious how to extend to a structure that had a variable number of whatever base numeric class you're using.

Repro answered 6/11, 2008 at 16:22 Comment(2)
By string the OP meant taking a string containing the number you want in its numeric representation (under whatever base) and initializing the BigInt with the value.Ferryman
STLPLUS use string to hold big integer.Oldtime
S
2

Use the algorithms you learned in 1st through 4th grade.
Start with the ones column, then the tens, and so forth.

Saxena answered 6/11, 2008 at 16:14 Comment(0)
C
2

Like others said, do it to old fashioned long-hand way, but stay away from doing this all in base 10. I'd suggest doing it all in base 65536, and storing things in an array of longs.

Carnation answered 6/11, 2008 at 16:19 Comment(0)
R
1

If your target architecture supports BCD (binary coded decimal) representation of numbers, you can get some hardware support for the longhand multiplication/addition that you need to do. Getting the compiler to emit BCD instruction is something you'll have to read up on...

The Motorola 68K series chips had this. Not that I'm bitter or anything.

Reviewer answered 6/11, 2008 at 16:20 Comment(0)
S
0

My start would be to have an arbitrary sized array of integers, using 31 bits and the 32n'd as overflow.

The starter op would be ADD, and then, MAKE-NEGATIVE, using 2's complement. After that, subtraction flows trivially, and once you have add/sub, everything else is doable.

There are probably more sophisticated approaches. But this would be the naive approach from digital logic.

Silicium answered 6/11, 2008 at 16:34 Comment(0)
N
0

Could try implementing something like this:

http://www.docjar.org/html/api/java/math/BigInteger.java.html

You'd only need 4 bits for a single digit 0 - 9

So an Int Value would allow up to 8 digits each. I decided i'd stick with an array of chars so i use double the memory but for me it's only being used 1 time.

Also when storing all the digits in a single int it over-complicates it and if anything it may even slow it down.

I don't have any speed tests but looking at the java version of BigInteger it seems like it's doing an awful lot of work.

For me I do the below

//Number = 100,000.00, Number Digits = 32, Decimal Digits = 2.
BigDecimal *decimal = new BigDecimal("100000.00", 32, 2);
decimal += "1000.99";
cout << decimal->GetValue(0x1 | 0x2) << endl; //Format and show decimals.
//Prints: 101,000.99
Nag answered 5/9, 2012 at 1:1 Comment(1)
OP never said s/he wants to focus on decimal digits.Jacksmelt
F
0

The computer hardware provides facility of storing integers and doing basic arithmetic over them; generally this is limited to integers in a range (e.g. up to 2^{64}-1). But larger integers can be supported via programs; below is one such method.

Using Positional Numeral System (e.g. the popular base-10 numeral system), any arbitrarily large integer can be represented as a sequence of digits in base B. So, such integers can be stored as an array of 32-bit integers, where each array-element is a digit in base B=2^{32}.

We already know how to represent integers using numeral-system with base B=10, and also how to perform basic arithmetic (add, subtract, multiply, divide etc) within this system. The algorithms for doing these operations are sometimes known as Schoolbook algorithms. We can apply (with some adjustments) these Schoolbook algorithms to any base B, and so can implement the same operations for our large integers in base B.

To apply these algorithms for any base B, we will need to understand them further and handle concerns like:

  • what is the range of various intermediate values produced during these algorithms.

  • what is the maximum carry produced by the iterative addition and multiplication.

  • how to estimate the next quotient-digit in long-division.

(Of course, there can be alternate algorithms for doing these operations).

Some algorithm/implementation details can be found here (initial chapters), here (written by me) and here.

Fernald answered 7/8, 2021 at 8:58 Comment(0)
I
-2

subtract 48 from your string of integer and print to get number of large digit. then perform the basic mathematical operation . otherwise i will provide complete solution.

Impermanent answered 12/3, 2010 at 17:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.