New Extra Credit Policy!
To encourage you to start projects early, I will be awarding one extra credit point
for each day early you turn in the project. This project is due at 11:59pm on
Monday, April 28, so if you turn it in by 11:59pm on Sunday the 27th, you get
one bonus point.
COMP 142 Project 6: Large Integers
To be done in partners, if desired. No more than two people per group.
Turn in only one copy of your files per group.
The built-in C++ integer types such as int, long, and long long, all use a fixed
number of bytes to store integers. For instance, on many computers, an int always takes
up 32 bits (4 bytes), meaning you can only represent 2^32 possible integer values, from
-(2^31) to (2^31)-1, or -2,147,483,648 to 2,147,483,647.
When you exceed the limits of what an int (or long or long long) can handle, C++ will not realize it.
Consider this code below:
int main()
{
int x = 2147483646;
cout << x << endl;
x++;
cout << x << endl;
x++;
cout << x << endl;
x++;
cout << x << endl;
}
When this program runs, it prints the following:
2147483646
2147483647
-2147483648
-2147483647
What is happening is that when x becomes equal to 2147483647, adding 1 to the number
causes an overflow error,
which, from the user's standpoint, causes the value of the variable to wrap around to
the most negative number possible in the range.
To mitigate this issue, you will construct a largeint class, which is capable of
representing nonnegative integers with any number of digits. You will use a vector of
integers to store the digits (each digit will take up one slot in the vector).
The largeint class, conceptually
Largeints use a vector of ints to store a nonnegative integer. Though it sounds weird,
you should store the digits in backwards order (it will make your later algorithms
easier). For instance, the integer 6805 would be represented as a vector of ints as
in the diagram below.
position: [0] [1] [2] [3]
-------------------------
value: | 5 | 0 | 8 | 6 |
-------------------------
Why backwards?
Eventually, you will have to write an algorithm to add two largeints together. Your
algorithm will simulate the elementary-school addition algorithm, which aligns
integers to the right so the place-values match up between them:
6805
+ 32
======
6837
If you make your vectors store the digits in "normal" order, you get the following:
position: [0] [1] [2] [3]
-------------------------
value: | 6 | 8 | 0 | 5 |
-------------------------
-------------
value: | 3 | 2 |
-------------
In this representation, the digits don't line up right. You can still write the
addition algorithm, but the logic is trickier because the positions don't match up
between the two vectors. However, if you store the digits in "backwards" order,
everything lines up:
position: [0] [1] [2] [3]
-------------------------
value: | 5 | 0 | 8 | 6 |
-------------------------
-------------
value: | 2 | 3 |
-------------
Getting started
Download these three files and bring them into a Visual Studio project:
largeint.h largeint.cpp main.cpp
Why are there three files?
In the real world, class definitions are often split into separate files from your
main code that uses the classes. Furthermore, often time the class definitions are split
into a ".h" file with the class definition and method prototypes, and a ".cpp" file with
the method bodies.
OK, the files are in Visual Studio, now what?
The project should compile and run without any changes. The output will not be correct,
but everything should compile and run.
Modifying largeint.cpp
Let's take a look at the largeint class definition, in largeint.h:
class largeint
{
public:
largeint();
largeint(string s);
int num_digits() const;
friend ostream& operator<<(ostream & os, const largeint & lint);
largeint operator+(const largeint & other) const;
largeint operator*(const largeint & other) const;
bool operator==(const largeint & other) const;
bool operator!=(const largeint & other) const;
private:
vector<int> digits;
};
Notice that the only field the class has is the vector of ints, called digits.
The bodies of these functions are already defined in largeint.cpp. You should not make
any changes to largeint.h.
The functions you will have to fill in are the following (in order given in the
code above):
- Default constructor: This constructs a largeint representing zero. Your
digits vector should simply have a vector with a single digit zero in it.
- Constructor with a string argument: This constructs a largeint representing
whatever string the user provides. You may assume the string argument contains only
the characters '0' through '9'. I have provided you with a char_to_int function
in largeint.cpp that you may find useful.
- Calculate the number of digits: num_digits() is used to calculate the number of
digits in the largeint. Don't overthink this, you can do this in one line of code.
- Overload operator<<: This is used to be able to print an integer to the screen.
Follow the same pattern we used in class, or see the book (or me).
- Overload operator+: This is the addition operator, where you will write your
elementary addition algorithm.
- Overload operator*: This is the multiplication operator. You
do not have to simulate the elementary-school multiplication algorithm (though you
can if you want). Instead, just make a for loop that does repeated addition.
In other words, to multiply 9 by 5, make a new largeint, set it to zero, then add
9 to the new largeint five times. Yes this is slow, but it works. Hint:
See main.cpp for how to write a for loop that uses largeints.
Be careful with implementing multiplication; this can really slow down
your code if you do it wrong:
Say I want to multiply 100 by 3. I can calculate this as 100 + 100 + 100, or 3 + 3 + 3 +...+ 3 (100 total additions). It is much faster to do the first calculation (3 additions) rather than the second calculation (100 additions). Make sure your multiplication function is doing the faster version (you can use num_digits() to see which number is longer and determine which way to do the math).
- Overload operator== and operator!=: These are the comparison operator (equals
and not-equals), which I have already written for you.
Modifying main.cpp
You should feel free to add any code to the main() function that you want, but please
remove anything you add before turning in your code.
You should fill in the factorial function for largeints. Do not try to write this
recursively, because the regular recursive factorial algorithm needs a subtraction
operator. Instead, use a for loop similar to your multiplication algorithm to
calculate factorial by repeated multiplication. That is, 5! = 1 * 2 * 3 * 4 * 5.
How do I know when it works?
When your code successfully prints the largeints 0 through 100, and can calculate
100 factorial, you should be confident that your code works.
Sample output
large int counter is: 0
large int counter is: 1
large int counter is: 2
[ removed middle lines here ]
large int counter is: 98
large int counter is: 99
large int counter is: 100
100 factorial is 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
This number has 158 digits.
The addition algorithm
You should use the same idea that you learned in elementary school to add two numbers.
You will use a loop to iterate over the digits from right-most digit to left-most, adding
pairs of digits as you go. If you get a sum less than ten, that becomes a digit in
your answer. If you get a sum greater than or equal to ten, only the one's digit
goes into your sum. Use an int variable called carry to carry the extra 1 to the
next column.
Special considerations:
- What do you do if the two numbers have differing numbers of digits?
- How do you handle the carry at the end of the loop if your answer will have
more digits than either of the starting numbers?
- Good samples to try: 1 + 1, 5 + 5, 99 + 1, 1 + 99, 1234 + 4321, 123456789 + 987654321.
size_t
You may run into some problems using size_t if you want to make a loop count backwards
(ending at zero). The reason for this is that size_t is an unsigned type, so it can't
go below zero (which is what would signal the loop to stop). If you start getting
infinite loops, try switching to an int and see if that helps.
Turning in your code
Do not rename your files. Upload all three files (largeint.h, largeint.cpp,
main.cpp) to Moodle. Upload only one copy per group (if you're working with a partner).
Make sure both of your names are in the comments at the top of your code.
Challenge Problems
- Add other math operators: subtraction, division, less than (and related operators),
remainder (%), etc.
- Make the multiplication algorithm work like the elementary school method, rather
than repeated addition (which is slow).