Data Type | Range of Values |
---|---|
byte | -27 .. 27-1 |
short | -215 .. 215-1 |
int | -231 .. 231-1 |
long | -263 .. 263-1 |
The corresponding wrapper classes in the java.lang package (Byte, Short, Integer, and Long) all suffer from the same limitation, as an instance of any of these classes simply acts as a "wrapper" around a value of the similarly-named primitive data type.
However, some applications require calculations with integers whose magnitudes may be arbitrarily large, meaning that no particular upper bound can be known ahead of time. For applications such as these, none of the Java data types mentioned above are suitable. Which is why Java's standard library includes the class java.math.BigInteger.
Instances of BigInteger can represent integers of any magnitude, limited only by conditions imposed by the environment in which a program is running, such as how much memory is available. The class's documentation uses the phrase "arbitrary-precision integer", which refers to there being no upper bound ("arbitrary") on the number of digits ("precision") used in representing a number.
For this assignment, you are to complete the development of the Java class BigIntUnsigned, instances of which provide a small subset of the functionality of Java's BigInteger class.
Specifically, an instance of BigIntUnsigned can represent any natural number (i.e., nonnegative integer), with no upper bound on its magnitude. (Negative numbers are not representable.) The operations supported include
Students wishing to demonstrate superior understanding are encouraged to implement not only addition and subtraction but also any or all of multiplication, integer division, and mod (i.e., remainder).
As with Java's BigInteger class, instances of BigIntUnsigned are immutable, which is to say that the class includes no mutator methods (or what your textbook authors refer to as transformer methods). Rather, the methods that implement the arithmetic operations are generators, which means that they produce a new instance of the class rather than modifying the state of any such instance. For example if x and y refer to instances of BigIntUnsigned, the method call
returns a new instance of the class representing the sum of the values represented by x and y. The states of the objects referred to by x and y are not changed.
Welcome to the BigIntUnsigned Calculator! > 35672305934 + 67891054 Computing 35672305934 + 67891054 35740196988 > 8792345664223455 - 123456 Computing 8792345664223455 - 123456 8792345664099999 > 4569834 = 542455 Computing 4569834 = 542455 false > 4569834 < 542455 Computing 4569834 < 542455 false > 4569834 > 542455 Computing 4569834 > 542455 true > 99999 * 10000000 Computing 99999 * 10000000 999990000000 > 150 / 7 Computing 150 / 7 21 > 150 % 7 Computing 150 / 7 3 |
Given that you are probably more familiar with decimal numerals than with numerals of any other base, it is suggested that you design your BigIntUnsigned class to make use of a sequence of decimal digits for the purpose of representing a big unsigned integer. The two most obvious alternatives are thus to use an instance variable of type int[] or one of type ArrayList<Integer>.1. (Here is a sample program that makes use of an ArrayList.) Each choice has its advantages and disadvantages. The major advantage of using an ArrayList is that it can grow and shrink in length "automatically", while an array's length is fixed.
Whichever choice you make, it is strongly suggested that the digits be stored in a least-to-most significant order, as this will make the code implementing addition and subtraction much simpler. For example, assuming that the instance variable is named digits (whether it is an array or an ArrayList), the integer 8603472 should be stored like this:
0 1 2 3 4 5 6 +---+ +---+---+---+---+---+---+---+ digits | *-+---------->| 2 | 7 | 4 | 3 | 0 | 6 | 8 | +---+ +---+---+---+---+---+---+---+ |
As for how to implement the various operators, for addition and subtraction think back to the algorithms that you learned in elementary school. Both involve iterating through the digits of the two operands, going from least significant digit to most significant. Addition involves a "carry" from one column to the next, while subtraction involves "borrowing" from the next column.
If you are attempting to implement the multiplicative operators (multiplication, division, remainder), you are encouraged to make use of this characterization of multiplication:
k * m = { | 0 | if k = 0 |
k/2 * 2m | if k>0 and k is even | |
((k-1)/2 * 2m) + m | if k>0 and k is odd |
To support taking this approach, you will probably want to develop private methods for doubling and for halving an instance of BigIntUnsigned.
[1] To make objects of BigIntUnsigned more memory-efficient, you could use an instance variable of type byte[] or ArrayList<Byte> instead, but that would make doing arithmetic a little less convenient. Why? Because Java's arithmetic operators (+, -, etc.) produce values of type int even when the operands are of type, say, byte. To store the result as a value of type byte, it is necessary to perform a cast, as in (byte)(k+m).
It would also be reasonable to use an instance variable of type char[] or ArrayList<Character>.