ava中long類型可以表示 -9,223,372,036,854,775,808(即-2^64)到9,223,372,036,854,775,807(即2^64-1)範圍内的整數。有的時候我們希望能夠處理在此範圍之外的整數。
為此,我們設計了一個BigInteger類。它可以支援大整數的加、減、乘操作。請根據提供的代碼架構,完成整個程式。
> 注:
> 1) 請仔細閱讀代碼中的注釋,并在标有// YOU FILL THIS IN 的位置添加你的代碼。你可以添加自己的方法、變量,但是請不要修改已有的代碼。
> 2) 程式中的main函數隻是一個示例測試。在批改作業時,我們會用更多的用例來測試你的程式。是以請保證你的程式的正确性。
> 3) 我們在測試你的程式時,将使用合法的輸入值,是以你無需考慮輸入不合法的情況。輸入值可能有兩種形式:"343839939" 或者
"-394939399390343",輸入的數字位數不固定,但一定是合法的整數。
> 4) 不允許使用java.math包。
> 5) 請将你的項目打包成rar或者zip格式送出。請注意送出的格式:打開你的壓縮包,應該可以看到BigInteger檔案夾,這個檔案夾裡面包含了src和bin兩個檔案夾。
如果送出作業格式錯誤,将不能通過測試,沒有得分。
給出的代碼是這樣子的:
Java代碼

- public class BigInteger {
- // The sign of this integer - true for a positive number, and false
- // otherwise
- private boolean sign = true;
- // digits[0] is the most significant digit of the integer, and
- // the last element of this array is the least significant digit.
- // For example, if we have a BigInteger of value 34, then
- // digits[0] = 3 and digits[1] = 4.
- private byte[] digits;
- public BigInteger() {
- this.digits = new byte[1];
- this.digits[0] = 0;
- }
- public BigInteger(byte[] digits) {
- this.digits = digits;
- }
- public BigInteger(String numberStr) {
- // YOU FILL THIS IN
- // Note: You should parse the string and initialize the "digits" array
- // properly.
- // You may also need to set the "sign" variable to a correct value.
- }
- public BigInteger add(BigInteger another) {
- // YOU FILL THIS IN
- }
- public BigInteger add(int num) {
- // YOU FILL THIS IN
- }
- public BigInteger subtract(BigInteger another) {
- // YOU FILL THIS IN
- }
- public BigInteger subtract(int num) {
- // YOU FILL THIS IN
- }
- public BigInteger multiply(BigInteger another) {
- // YOU FILL THIS IN
- }
- public BigInteger multiply(int num) {
- // YOU FILL THIS IN
- }
- public String toString() {
- StringBuffer buf = new StringBuffer();
- if (!sign) {
- buf.append("-");
- }
- for (byte d : digits) {
- buf.append(d);
- }
- return buf.toString();
- }
- public static void main(String[] args) {
- BigInteger i1 = new BigInteger("999999999999999999");
- BigInteger i2 = i1.add(1);
- System.out.println(i2); // the output should be 1000000000000000000
- BigInteger i3 = i2.multiply(i1);
- System.out.println(i3); // expected: 999999999999999999000000000000000000
- System.out.println(i3.subtract(-3)); // expected: 999999999999999999000000000000000003
- }
- }
其實,大家不要被這個看似很難的題目吓到,想想國小時我們剛開始學加法和乘法的時候,老師都教我們怎麼算?豎式運算,對!由于BigInteger把每一位數都存在數組中,也為豎式運算提供了友善。
我們需要寫的方法是:
public BigInteger(String numberStr);
public BigInteger add(BigInteger another);
public BigInteger add(int num);
public BigInteger subtract(BigInteger another);
public BigInteger subtract(int num);
public BigInteger multiply(BigInteger another);
public BigInteger multiply(int num);
3個小時,如果真的每個方法都寫的話,估計很難做完。化歸思想大家都學過,我們來運用一下:
Java代碼

- public BigInteger(int integer){
- this(String.valueOf(integer));
- }
- public BigInteger add(int num) {
- return this.add(new BigInteger(num));
- }
- public BigInteger subtract(BigInteger another) {
- return this.add(another.negate());
- }
- public BigInteger subtract(int num) {
- return this.subtract(new BigInteger(num));
- }
- public BigInteger multiply(int num) {
- return this.multiply(new BigInteger(num));
- }
這裡的negate方法是新加的,就是傳回一個相反數。減法就是加上一個數的相反數,對吧?(注意,為了防止可能的副作用,這裡使用了deep copy)
Java代碼

- public BigInteger negate(){
- BigInteger bi = new BigInteger();
- byte[] digitsCopy = new byte[this.digits.length];
- for(int i = 0;i < this.digits.length;i++){
- digitsCopy[i] = this.digits[i];
- }
- bi.sign = !this.sign;
- bi.digits = digitsCopy;
- return bi;
- }
于是,我們需要寫的方法減少到了3個:
public BigInteger(String numberStr);
public BigInteger add(BigInteger another);
public BigInteger multiply(BigInteger another);
第一個方法不難,隻要先判斷第一個字元是不是'-',然後再把不是負号的部分加到digits數組中就行。(由于明确了輸入格式肯定是正确的,這裡不考慮輸入格式的問題):
Java代碼

- public BigInteger(String numberStr) {
- // YOU FILL THIS IN
- // Note: You should parse the string and initialize the "digits" array
- // properly.
- // You may also need to set the "sign" variable to a correct value.
- if(numberStr.charAt(0) == '-'){
- sign = false;
- StringBuilder sb = new StringBuilder(numberStr);
- sb.deleteCharAt(0);
- numberStr = new String(sb);
- }else{
- sign = true;
- }
- digits = new byte[numberStr.length()];
- for(int i = 0;i < numberStr.length();i++){
- switch(numberStr.charAt(i)){
- case '0': digits[i] = 0;break;
- case '1': digits[i] = 1;break;
- case '2': digits[i] = 2;break;
- case '3': digits[i] = 3;break;
- case '4': digits[i] = 4;break;
- case '5': digits[i] = 5;break;
- case '6': digits[i] = 6;break;
- case '7': digits[i] = 7;break;
- case '8': digits[i] = 8;break;
- case '9': digits[i] = 9;break;
- }
- }
- }
然後來看public BigInteger add(BigInteger another)方法,我們要考慮什麼?首先是符号,如果兩數同号,那好辦,和肯定是和兩數符号相同的;若異号,那麼我們就要看兩數絕對值的大小了,和與絕對值大的數同号。
怎樣判斷絕對值呢?首先看位數,位數大的絕對值肯定大。位數相同,則從首位開始比較,隻要有一位不同,不同位置的數字大的大;如果每一位都相同,那麼我們得到的就是0,省事很多。
然後我們考慮具體的加減,同号相加(其實是絕對值相加),要考慮進位問題,而産生的和的位數最多比絕對值大的數多一位;異号相加,其實是絕對值相減,要考慮借位問題,而得到的差位數不大于絕對值大的數。
這裡要特别注意的是,豎式計算的是從最末尾開始的,而我們的數組首位存儲的是最高位,第二位是第二高位,一次類推;故我們這裡用的循環大多同平日寫的循環有些不同:for(int i = 1;i <= digits.length;i++)。
梳理好以上思路,add方法寫法如下:
Java代碼

- public BigInteger add(BigInteger another) {
- // YOU FILL THIS IN
- BigInteger sum = new BigInteger();
- if(this.sign == another.sign){
- //the signs of both are equal
- int length1 = this.digits.length;
- int length2 = another.digits.length;
- int biggerLength = Math.max(length1, length2);
- byte[] temp = new byte[biggerLength];
- byte carry = 0;
- for(int i = 1;i <= biggerLength;i++){
- byte i1 = (length1 - i < 0)?0:this.digits[length1 - i];
- byte i2 = (length2 - i < 0)?0:another.digits[length2 -i];
- int s = i1 + i2 + carry;
- if(s < 10){
- temp[biggerLength - i] = (byte)s;
- carry = 0;
- }else{
- temp[biggerLength - i] = (byte)(s - 10);
- carry = 1;
- }
- }
- if(carry == 0){
- sum.digits = temp;
- }else{
- sum.digits = new byte[biggerLength + 1];
- sum.digits[0] = carry;
- for(int i = 0;i < biggerLength;i++){
- sum.digits[i + 1] = temp[i];
- }
- }
- sum.sign = this.sign;
- }else{
- //the signs differ
- boolean isAbsoluteEqual = false;//the default value is false
- boolean isThisAbsoluteBigger = false;// the default value is false
- if(this.digits.length > another.digits.length){
- isThisAbsoluteBigger = true;
- }else if(this.digits.length == another.digits.length){
- isAbsoluteEqual = true;
- for(int i = 0;i < this.digits.length;i++){
- if(this.digits[i] != another.digits[i]){
- if(this.digits[i] > another.digits[i]){
- isThisAbsoluteBigger = true;
- }
- isAbsoluteEqual = false;
- break;
- }
- }
- }
- //if isAbsoluteEqual is true, the sum should be 0, which is just the default value
- if(!isAbsoluteEqual){
- byte[] temp;
- byte[] bigger;
- byte[] smaller;
- if(isThisAbsoluteBigger){
- sum.sign = this.sign;
- temp = new byte[this.digits.length];
- bigger = this.digits;
- smaller = another.digits;
- }else{
- sum.sign = another.sign;
- temp = new byte[another.digits.length];
- bigger = another.digits;
- smaller = this.digits;
- }
- boolean borrow = false;
- for(int index = 1;index <= bigger.length;index++){
- byte biggerDigit = bigger[bigger.length - index];
- biggerDigit = (byte) ((borrow)?(biggerDigit - 1):biggerDigit);
- byte smallerDigit = (smaller.length - index < 0)?0:smaller[smaller.length - index];
- int s = biggerDigit - smallerDigit;
- if(s < 0){
- borrow = true;
- s += 10;
- }else{
- borrow = false;
- }
- temp[temp.length - index] = (byte)s;
- }
- int zeroCount = 0;
- for(int i = 0;i < temp.length;i++){
- if(temp[i] == 0){
- zeroCount++;
- }else{
- break;
- }
- }
- sum.digits = new byte[temp.length - zeroCount];
- for(int i = 0;i < sum.digits.length;i++){
- sum.digits[i] = temp[zeroCount + i];
- }
- }
- }
- return sum;
- }
最後就是乘法了,其實還是豎式計算,就是稍微麻煩了一點(暫時還沒找到更好的解法,3個小時,咱就不考慮什麼算法優化了)。第一還是先考慮符号,這個比加法簡單,要是同号,商為正,要是異号,那麼商為負。
第二是具體的豎式計算怎麼做,先看一個例子:
Java代碼

- 1 2 3 4
- x 9 3 4
- ----------------
- 4 9 3 6
- 3 7 0 2
- + 1 1 1 0 6
- ----------------
- 1 1 5 2 5 5 6
規律是什麼?1. 大數在上,小數在下; 2. 大數乘以小數的每一位,分别得到商; 3.最後把各個商按位相加。 聽上去挺簡單,不是嗎?實作1,和加法裡面的循環類似,從低位開始相乘,還要考慮進位。2中的商我們可以儲存在一個二維數組中,數組第一維的大小是較小數的位數,第二維是較大數的位數+1,為什麼有個+1?看看上面的第三個商,最多有可能比大數多一位。 而3呢?位數的便宜似乎比較難實作,但是想想我們上次做過的WordPuzzle,如果是個矩陣呢?其實上面的幾個商,按照矩陣排列就是:
Java代碼

- 0 4 9 3 6
- 0 3 7 0 2
- 1 1 1 0 6
對應的加法是沿着主對角線方向的!有點感覺了是吧,而這個矩陣就是我們剛剛做的那個二維數組!
乘法實作如下:
Java代碼

- public BigInteger multiply(BigInteger another) {
- // YOU FILL THIS IN
- BigInteger product = new BigInteger();
- if(this.sign == another.sign){
- product.sign = true;
- }else{
- product.sign = false;
- }
- int biggerLength;
- int smallerLength;
- byte[] bigger;
- byte[] smaller;
- byte[][] tempProducts;
- if(this.digits.length >= another.digits.length){
- biggerLength = this.digits.length;
- smallerLength = another.digits.length;
- bigger = this.digits;
- smaller = another.digits;
- }else{
- biggerLength = another.digits.length;
- smallerLength = this.digits.length;
- bigger = another.digits;
- smaller = this.digits;
- }
- tempProducts = new byte[smallerLength][];
- for(int i = 1;i <= smallerLength;i++){
- byte[] temp = new byte[biggerLength + 1];//make plenty of space to avoid overflow
- byte carry = 0;
- byte m1 = smaller[smallerLength - i];
- for(int j = 1;j <= biggerLength;j++){
- byte m2 = bigger[biggerLength - j];
- int tempProduct = m1 * m2 + carry;
- temp[biggerLength + 1 - j] = (byte)(tempProduct % 10);
- carry = (byte)(tempProduct / 10);
- }
- temp[0] = carry;
- tempProducts[i - 1] = temp;
- }
- byte[] sum = new byte[smallerLength + biggerLength];
- byte carry = 0;
- int count = 1;
- int row = 0;
- int column = biggerLength;
- while(count <= sum.length){
- int startR = row;
- int startC = column;
- int currentSum = 0;
- while((startR < smallerLength) && (startC < biggerLength + 1)){
- currentSum += tempProducts[startR][startC];
- startR++;
- startC++;
- }
- currentSum += carry;
- if(currentSum < 10){
- sum[sum.length - count] = (byte)(currentSum);
- carry = 0;
- }else{
- sum[sum.length - count] = (byte)(currentSum % 10);
- carry = (byte)(currentSum / 10);
- }
- //System.out.println("processing digit: " + (sum.length - count) + " current digit: " + sum[sum.length - count] + " current carry: " + carry);
- if(column == 0){
- row++;
- }else{
- column--;
- }
- count++;
- }
- int zeroCount = 0;
- for(int i = 0;i < sum.length;i++){
- if(sum[i] == 0){
- zeroCount++;
- }else{
- break;
- }
- }
- product.digits = new byte[sum.length - zeroCount];
- for(int i = 0;i < product.digits.length;i++){
- product.digits[i] = sum[zeroCount + i];
- }
- return product;
- }
以上幾個方法中的zeroCount和接下來跟着的循環是用來去掉數組前幾位不必要的0而設計的。
用java.math包内自帶的BigInteger測試了幾個相同的執行個體:
Java代碼

- public static void main(String[] args) {
- BigInteger bi = new BigInteger("-123456789").multiply(new BigInteger("1111111111")).multiply(new BigInteger("-222222222")).multiply(new BigInteger("333333333")).multiply(new BigInteger("-444444444")).multiply(new BigInteger("555555555")).multiply(new BigInteger("678987654"));
- System.out.println(bi);
- BigInteger bi2 = new BigInteger("123456789").multiply(new BigInteger("-999999999").multiply(new BigInteger("2387423749237")));
- System.out.println(bi2);
- System.out.println(bi.subtract(bi2));
- System.out.println(bi.add(bi2));
- }
得到的結果:(沒兩行上面的是java.math包内的BigInteger的結果,下面是我寫的BigInteger的結果)
Java代碼

- -1703513391044005504226057865745939441413078537590685258276720
- -1703513391044005504226057865745939441413078537590685258276720
- -294743669768397549929858780007
- -294743669768397549929858780007
- -1703513391044005504226057865745644697743310140040755399496713
- -1703513391044005504226057865745644697743310140040755399496713
- -1703513391044005504226057865746234185082846935140615117056727
- -1703513391044005504226057865746234185082846935140615117056727