• Aucun résultat trouvé

ARITHMETIC METHODS

Large Integer Computing

2.3 ARITHMETIC METHODS

2.3 ARITHMETIC METHODS

Of course, we must write methods to perform arithmetic with Int objects. We must be able to add, subtract, multiply, and divide. For convenience, we should add methods to increment and decrement Int objects. First, we consider the addition of two integers aandb:

• If one is zero, return the other as the answer. Otherwise go on.

• If a=⫺b, return zero. Otherwise go on.

• If the digits are the same sign, add them beginning with the lowest digits, adding any car-ries in the subsequent addition. Otherwise go on.

• If the digits are of different signs, this is a subtraction; either a⫺–borb⫺–a. To do a subtraction, you must determine the larger of the two integers without regard to sign, then subtract the smaller integer from the larger. The sign of the answer is the same as that of the larger operand.

We shouldn’t be too surprised at this addition/subtraction scheme, because it is the way humans (using base 10) normally do it. Notice that writing the add(Int) and subtract(Int) may entail using some of the methods already developed (like equals(Int) and lessThan(Int)), and may require writing a few more methods as well. For instance, in the following there is a method to negate an Int, and one to produce the absolute value. These are listed first.

public Int absoluteValue() {

//Make a new Int by copying this Int Int answer=new Int(this);

//Set negative to false answer.negative=false;

return answer;

}

public Int negative() { Int answer=new Int(this);

//Flip the negative value answer.negative=!this.negative;

return answer;

}

The code which does most of the work is in the add(Int) method. First, it determines the sign of the two numbers; if they are the same, it is an addition problem, and the addDigits(Int) method is called. If they are not the same, it is a subtraction problem, and the subtractDig-its(Int) method is called. The subtractDigsubtractDig-its(Int) method may call the borrow(Int) method, which allows us to borrow from digits to the left for subtraction. Of course, after the num-bers are added or subtracted, there may be leading zeros, which should be removed.

public Int add(Int other) { Int ans;

//zero is a special case-nothing to do but return a copy of the //nonzero Int

if (this.equals(ZERO)) return new Int(other);

else if (other.equals(ZERO)) return new Int(this);

else if (this.equals(other.negative())) return new Int();

//If they are the same sign, perform the addition; add carries else if (negative==other.negative) {

ans=addDigits(other);

ans.negative=negative;

}

//If they are of different signs, determine the larger //(magnitude-wise) and subtract the smaller from it.

//Result has same sign as first (larger) operand.

else if (this.absoluteValue().lessThan(other.absoluteValue())) { ans=other.subtractDigits(this);

ans.negative=other.negative;

} else {

ans=this.subtractDigits(other);

ans.negative=this.negative;

}

//Trim leading zeros and return return ans.trimZeros();

}

public Int subtract(Int other) { //To subtract, we add the negative return this.add(other.negative());

}

private Int addDigits(Int other) { int top1=this.digits.length-1;

int top2=other.digits.length-1;

int top3=Math.max(this.digits.length,other.digits.length)+1;

Int answer=new Int();

answer.digits=new int[top3];

top3--;

int carry=0; int sum=0;

while (top1>=0&&top2>=0) {

sum=this.digits[top1]+other.digits[top2]+carry;

if (sum>9) {sum%=10; carry=1;} else carry=0;

answer.digits[top3]=sum;

top1--;top2--;top3--;

}

if (top1<0&&top2<0) { answer.digits[0]=carry;

} else if (top1<0) {

2.3 Arithmetic Methods 43

while (top2>=0) {

sum=other.digits[top2]+carry;

if (sum>9) {sum%=10; carry=1;} else carry=0;

answer.digits[top3]=sum;

top2--;top3--;

}

answer.digits[top3]=carry;

} else {

while (top1>=0) {

sum=this.digits[top1]+carry;

if (sum>9) {sum%=10; carry=1;} else carry=0;

answer.digits[top3]=sum;

top1--;top3--;

}

answer.digits[top3]=carry;

}

return answer;

}

private Int subtractDigits(Int other) { Int answer=new Int();

Int copy=new Int(this);

answer.digits=new int[this.digits.length];

int top1=this.digits.length-1;

int top2=other.digits.length-1;

while (top2>=0) {

if (copy.digits[top1]<other.digits[top2]) { borrow(copy,top1-1);

copy.digits[top1]+=10;

}

answer.digits[top1]=copy.digits[top1]-other.digits[top2];

top1--; top2--;

}

while (top1>=0) {

answer.digits[top1]=copy.digits[top1];

top1--;

}

return answer;

}

//Method to “borrow” for subtraction private void borrow(Int n,int pos) {

while (n.digits[pos]==0) { n.digits[pos]=9;

pos--;

}

n.digits[pos]—;

}

//Method to chop off any leading zeros private Int trimZeros() {

int i;

//Look for first nonzero in the array for (i=0;i<this.digits.length;i++)

if (this.digits[i]!=0) break;

Int answer=new Int();

answer.negative=this.negative;

//Make a (possibly) smaller array for answer answer.digits=new int[this.digits.length-i];

//Copy the nonzero digits over, and return answer for (int j=0;j<answer.digits.length;j++)

answer.digits[j]=this.digits[j+i];

return answer;

}

Methods to multiply and divide Ints should also be written. We will develop the multi-ply(Int) method here. When we multiply two integers, we really just multiply by a single digit at a time, then perform a total of these individual products. For example, when we do

we actually do these separate products:

527⫻6 = 3162, 527 ⫻1 = 527, and 527 ⫻3 = 1581.

We then add these products together, shifting some of the products to the left. That is, we append a zero to 527 ⫻1 since 1 is in the tens column, and we append two zeros to 526 ⫻ 6 because 6 is in the hundreds column: This gives us

Thus our multiplication problem actually becomes an addition problem, as long as we are able to multiply an integer by a single digit, and as long as we can append a certain number of zeros to our sub-products. Doing the latter in Java is very simple, as you can see from the following code:

527 x 613 + 52701581 + 316200 323051

⫻ 613527

2.3 Arithmetic Methods 45

private Int appendZeros(int places) { //Make a new Int object

Int result=new Int();

//If this equals 0, return 0; no need to append if (this.equals(ZERO)) return result;

//Make the resulting array larger

result.digits=new int[this.digits.length+places];

//Shift the digits into the new array for (int i=0;i<this.digits.length;i++) {

result.digits[i]=this.digits[i];

}

return result;

}

Now, to multiply an Int by a single digit, we simply multiply each digit in the first operand by the selected digit from the second operand. If this product is greater than 9, we use the remainder of division by 10 for the corresponding digit in the result (plus a possible carry from the previous multiplication), and we record the quotient when dividing by 10 as a carry. Take the following example:

We first take 7 ⫻3 = 21; 1 becomes the digit in the one’s column of the result, and 2 becomes the carry.

We then take 2 ⫻3 = 6, and add the previous carry 2, to get 8. This becomes the digit in the tens column of the result, and 0 becomes the carry.

Now we take 5 ⫻3 = 15, and add the previous carry 0. 5 becomes the digit in the hun-dreds column of the result, and 1 becomes the carry.

1 527 x 3 581 0 527 x 3

?81 2 527 x 3

??1 527 x 3

???

There are no more digits to multiply, but there is the possibility of one final carry, as is the case here. This becomes the thousands digit in the result.

Of course, this is exactly how we’ve done multiplication by a single digit since elemen-tary school, but perhaps coding it seems not so elemenelemen-tary. Examine the following code carefully:

//Method to multiply an Int by a single decimal digit //Called repeatedly by multiply(Int) method

private Int multiply(int otherDigit) { //Make a new Int for the answer Int result=new Int();

//If digit to multiply by is 0, return 0 if (otherDigit==0) return result;

//Make the answer array one longer than the first operand, //in case there is a carry

result.digits=new int[this.digits.length+1];

int carry=0;

int tempInteger;

int i;

for (i=this.digits.length-1;i>=0;i--) {

//i+1th digit of result is the ith digit of the first operand //times the digit in the second operand. If this is more than //10, we must keep only the least significant digit.

//We also add any previous carries

tempInteger=this.digits[i]*otherDigit+carry;

result.digits[i+1]=tempInteger%10;

//If the product is more than 10, we must set carry //for the next round

carry=tempInteger/10;

}

//Possibility of one last carry; do the final digit.

result.digits[0]=carry;

return result;

}

Once we have the ability to append zeros and to multiply an Int by a single digit, multi-plying two Int objects simply becomes a matter of calculating these sub-products and adding them together. This part is actually easy with the two previous methods defined, as you can see in the following code:

public Int multiply(Int other) { 527 x 3 1581

2.3 Arithmetic Methods 47

//Initialize the answer to 0 Int result=new Int();

//If either operand is 0, return 0

if (this.equals(ZERO)||other.equals(ZERO)) return result;

//Now, multiply the first operand by each digit in the

//second operand, shifting left each answer by a power of ten //as we pass through the digits, adding each time to result.

for (int i=0; i<other.digits.length; i++) {

result=result.add(this.multiply(other.digits[i]) .appendZeros(other.digits.length-1-i));

}

//If operands are same sign, result is positive

//otherwise, the result is negative; 0 is already taken care of if (this.negative==other.negative) result.negative=false;

else result.negative=true;

//Return the result

return result.trimZeros();

}

Note that at the end of the multiply(Int) method we remove any leading zeros which may come about. Another consideration is the sign of each operand: If they are equal, the result is a nonnegative number; otherwise, the result is negative. Zero is treated as a special case.

At this point, you should be able to write the divide(Int), and remainder(Int) methods, and will be asked to do so in the exercises. It would be prudent to write a divideAndRemain-der() method, since both the quotient and remainder would probably be generated at the same time. It could return an array of two Ints, with the quotient in slot 0, and the remain-der in slot 1 of the array.

Division is the most costly (in terms of computer time) to run. For now, we only consider problems where the dividend and divisor are both positive. One of the primary problems is estimating the digits in the quotient. For example, consider the following division problem:

6772190÷37658.

Note that 3 (the leading digit of the divisor) goes into 6 (the leading digit of the dividend) twice, but the real quotient of the previous division is 179. The estimate of the first digit of the quotient is too high. A similar problem occurs in the following problem:

19276÷273.

Note that the leading digit of the divisor cannot go into the leading digit of the dividend at all, and we must therefore attempt to take the divisor into the first two digits of the divi-dend. One way to avoid spending too much time estimating is to allow the quotient to have mixed positive and negative digits. Consider again

19276 ÷ 273.

Number the position of each digit:

19276 273 54321 321

First, attempt to divide 1 by 2; if this fails, incorporate another digit. Here we divide 19 by 2, which yields 9. Produce the first value for the quotient:

90.

The number of zeros to add is clear when you note the position of the 9 in the dividend is 4, and the position of the 2 in the divisor is 3. Thus, we add 4 ⫺3 = 1 zero. Now, sub-tract 90 · 273 from 19276:

Now, attempt to divide 2 into ⫺5; this yields ⫺2. Since the ⫺5 is at position 4, and 2 is still at position 3, we add a zero to get

-20 We now modify the quotient value:

90 + -20 = 70.

Now, subtract ⫺20· 273 from ⫺5294; this gives us

Note now that the value remaining is smaller than the divisor 273; thus, we have

quotient = 70, and remainder = 166.

Here is the whole process written out; the only difference between this and the way we normally do division is that negative quantities appear in the quotient:

–2070 273

)

19276 90 –24570 –5294 ––5460 166 -5294 --5460 166 19276 -24570 -5294

2.3 Arithmetic Methods 49 Here is another example for you:

It is entirely possible that this process could yield a negative remainder, as in the following example (the absolute value of ⫺542 is less than the divisor, 982).

When this happens, it is easy to fix: simply add the divisor to the negative remainder and subtract 1 from the quotient. In this case, this yields:

quotient = 9 remainder = 440.

Now we consider what to do when either the dividend or divisor is negative. Note that ifxis the positive dividend, yis the positive divisor, qis the quotient, and ris the remain-der, we can express their relationship to each other as:

x = yq + r b > r 0.

If either xorychanges sign, we can maintain this relationship by inverting some signs.

For example, we can perform calculations with all positive numbers, because:

ifyis negative, change the sign of q, since x=yq+r iff

x=⫺y(⫺q) + r,

982

)

927810 –9820 –542

80297 300–3 –10000 90000 123

)

9876543

–11070000 –1193457 ––1230000 36543 –36900 ––369–357 12

FIGURE 2.4 (a)

(b)

ifxis negative, change the sign of both qandr, since x=yq+r iff

⫺x=⫺(yq+r) iff

⫺x=y(⫺q)⫺r,

and if both xandyare negative, change the sign of r, since x=yq+r iff

⫺x=⫺(⫺y(⫺q) + r)

⫺x=⫺yq⫺r.

With this in mind, writing a division method should be a snap.

Naturally, the arithmetic methods must be tested. No programmer ever designs a class without writing a multitude of programs to wring all the bugs out of it. I’ve written a sim-ple apsim-plet to do this. I ask the user to enter an Int in a text field, and then they click an oper-ation, either “+”,“⫺”, or “*”. They then enter another Int and press the “=” button. This is the beginning of a calculator; you will be asked to produce a better one in the exercises. Some screen shots follow, and the applet can be found on the book’s website under the class name TestIntArithmeticMethodsApplet. (See Figure 2.4a–c.)

2.5 Constructors 51 (c)

FIGURE 2.4 (continued)

Having an Int class is a very valuable tool, for it frees us from the boundaries placed on us by the primitive integer data types of most computer languages. A Java int, for example, is only 4 bytes, which is not nearly large enough for the numbers we will need to handle in this book. However, the cost of this freedom is decreased performance, and as a result large integer packages are usually very carefully designed and optimized to yield the maximum benefit. The Int class as we have designed it here is in fact rather poor, but it is neverthe-less a good introduction for someone who has never attempted the feat before. In the exer-cises we will discuss how to produce a much better Int class.