Big integer algorithm [05] shift operation, integer 05
In the previous blog post, we talked about operations related to the entire number of digits. This time the content is still related to bit operations: shift operations. It should be noted that the big integer shift operation mentioned here is arithmetic shift (the accuracy will increase if the Left shift is used, rather than dropping the removed bit ).
★Differences between the two types of Shift
There are two different operation methods in the shift operation: Logical shift and arithmetic shift. In the logical shift, the removed bits are lost and the moved bits are 0. In the arithmetic shift, the removed bits are lost, the left shifted bits are 0, and the right shifted bits are signed bits, that is, the data symbols represented by the highest BITs remain unchanged. For example, two types of variables (in a 32-bit system) are defined:
Unsigned int u = 0x80000000; (7 digits after 8 are 0)
Int s = 0x80000000;
In binary format: 1000 0000 0000 0000 0000 0000 0000 0000
The difference between u and s is that the highest bit of s is the sign bit (0 represents a positive number, and 1 represents a negative number). Therefore, if the two variables are printed using the following statement, the result will be: 2147483648,-2147483648.
Printf ("\ nu = % u \ n", u );
Printf ("\ ns = % d \ n", s );
The first result indicates that the unsigned number is 2 ^ 31, but why is the second result-2 ^ 31? It should be-0. The reason is that these numbers are all represented by a complement.
Let's take a look at how the number-2 ^ 31-1 (-2147483647) is represented by a complement code.
Original code: 1111 1111 1111 1111 1111 1111 1111 1111. Note that the highest bit is the symbol bit. Requires complement. First, calculate the anticode. The calculation of the anticode is that the symbol bit remains unchanged, and the rest is reversed.
Anti-code: 1000 0000 0000 0000 0000 0000 0000 0000. After obtaining the anti-code, add the anti-code 1 to get the complement code.
Supplementary Code: 1000 0000 0000 0000 0000 0000 0000 0001. Therefore, the-2 ^ 31-1 is expressed as 0x80000001.
To represent-2 ^ 31, you only need to subtract 1 from the complement code of-2 ^ 31-1, so it is 0x80000000, the minimum negative integer that can be expressed under the 32-bit signed integer is obtained.
Next let's take a look at the result after the shift.
For the Left shift operation, whether it is logical left shift or arithmetic left shift, the removed bits on the left are lost, and the bits on the right are all 0. Therefore, if you execute the following two statements: u <= 1; s <= 1; the result is 0.
For the right shift operation, the logic shifts to the left to fill 0, and the arithmetic shifts to the left to fill the sign bit. Therefore, if you execute the following two statements: u> = 1; s> = 1; the result is 1073741824,-1073741824. The result completion code is in binary format:
U: 0100 0000 0000 0000 0000 0000 0000
S: 1100 0000 0000 0000 0000 0000 0000
The first-digit right shift operation is equivalent to the calculation: 2147483648/2 = 1073741824;-2147483648/2 =-1073741824.
For a shift operation, the Left shift n is equal to multiplying by 2 ^ n, and the right shift n is equal to dividing by 2 ^ n.
The logic shift and arithmetic shift are different. For large integers, the arithmetic shift operation does not require any supplementary code, because dp points to the memory and stores the absolute values of large integers, and the symbols are tracked by sign. What's different from C is that if the number of digits after the Left shift is not enough, the precision of the big integer will increase, rather than dropping the shifted bit as C does.
★Shifts left and right B digits
Simply put, multiply by or divide by 2 ^ (biL * B). The shift operation is performed in units of the entire number.
Shift B digits left:
int bn_lshd(bignum *x, size_t b)
{
int ret;
size_t i;
bn_digit *top, *bottom;
x->used += b;
BN_CHECK(bn_grow(x, x->used));
top = x->dp + x->used - 1;
bottom = x->dp + x->used - b - 1;
for(i = x->used; i > b; i--)
*top-- = *bottom--;
top = x->dp;
for(i = 0; i < b; i++)
*top++ = 0;
clean:
return ret;
}
After B digits are shifted to the left, the number is increased to B, so the used value is increased to B. Use the bn_grow function to increase the accuracy of bignum. Then, use the top pointer to point to the highest bit after the bignum left shift, the bottom pointer to the current highest bit of bignum, and then move each digit to the left by repeating used-B. After the operation is complete, let the top pointer point to the lowest Bit. In the cycle of B, the lowest number of BITs is 0 to complete the shift operation.
Shift B digits to the right:
int bn_rshd(bignum *x, size_t b)
{
int ret = 0;
size_t i;
bn_digit *top, *bottom;
if(x->used <= b)
{
BN_CHECK(bn_set_word(x, 0));
return ret;
}
bottom = x->dp;
top = x->dp + b;
for(i = 0; i < x->used - b; i++)
*bottom++ = *top++;
for(; i < x->used; i++)
*bottom++ = 0;
x->used -= b;
clean:
return ret;
}
Unlike the Left shift, the precision of the right shift does not increase. However, if the used value is smaller than or equal to B, the highest bit of bignum is removed from the right and the result is 0. If used> B, let bottom point to the lowest digit of bignum, top Pointer Points to B + 1, then cycle used-B to move each digit to the right B, finally, set the remaining bits on the left to 0 to complete the right shift operation.
★Shifts one bit left and one bit right.
Well, multiply by 2 or divide by 2. The following algorithm assigns the value of x to y.
Shift 1 to the left:
int bn_lshift_1(bignum *y, const bignum *x)
{
int ret;
size_t i, olduse, shift;
bn_digit r, rr, *px, *py;
BN_CHECK(bn_grow(y, x->used + 1));
olduse = y->used;
y->used = x->used;
px = x->dp;
py = y->dp;
r = 0;
shift = (size_t)(biL - 1);
for(i = 0; i < x->used; i++)
{
rr = *px >> shift;
*py++ = (*px++ << 1) | r;
r = rr;
}
if(r != 0)
{
*py = 1;
y->used++;
}
py = y->dp + y->used;
for(i = y->used; i < olduse; i++)
*py++ = 0;
y->sign = x->sign;
clean:
return ret;
}
First, the algorithm increases the accuracy of y to x-> used + 1 digits by default. The reason for this is to add 1, because it may just move the highest bit to the next digit. Olduse records the number of the current y, and then increases the number of y to the number of x. If there is a carry, y-> used will add one. Shift variable indicates the number of BITs that each digit is to move to the right. For example, in a 32-bit system, shift = In a 64-bit system. Variable r stores the leftmost bits of the last digit, and variable rr stores the leftmost bits of the current digit. In a loop, first shift the current digit to the right to obtain the leftmost bits, then shift the digit to the left to 1 and perform the OR operation with the leftmost bits of the right, in this way, each bit of the current digit moves one bit to the left, and the leftmost bit of the right digit also moves to the rightmost position of the current digit. After the cycle ends, if r is not 0, it indicates that the leftmost bits of the original bignum leftmost digit is 1, and is moved to the new digit in the left shift. Therefore, used adds one, the new digit value is 1. Finally, set the high position of y to 0, and assign the symbol of x to y to complete all operations.
Shift 1 to the right:
int bn_rshift_1(bignum *y, const bignum *x)
{
int ret;
size_t i, olduse, shift;
bn_digit r, rr, *px, *py;
BN_CHECK(bn_grow(y, x->used));
olduse = y->used;
y->used = x->used;
px = x->dp + y->used - 1;
py = y->dp + y->used - 1;
r = 0;
shift = (size_t)(biL - 1);
for(i = y->used; i > 0; i--)
{
rr = *px & 1;
*py-- = (*px-- >> 1) | (r << shift);
r = rr;
}
py = y->dp + y->used;
for(i = y->used; i < olduse; i++)
*py++ = 0;
y->sign = x->sign;
bn_clamp(y);
clean:
return ret;
}
The precision of moving one bit to the right is not increased, but the precision of calling the bn_grow function is increased because y may not be accurate enough at the beginning. The one-to-right operation is similar to the one-to-left operation, but the opposite is true. In the first loop, first obtain the rightmost bits of the current digit and store it in the rr variable. Then, the current digit shifts 1 to the right, next, shift the rightmost bits of the Left digit to the left and perform the OR operation with the current digit. Finally, store the rr value in the variable r, in this way, all the bits of the current bit are moved to the right, and the rightmost bits of the Left bits are also moved to the leftmost part of the current bits. After completing the loop, set the high position to 0, set the symbol bit, and compress the excess bit to complete the operation.
★Shifts n bits left and right
If the previous shift operations have special characteristics, the following two operations will be generalized. Shifts n places left or right by multiplying or dividing by 2 ^ n.
Shift n places left:
int bn_lshift(bignum *y, const bignum *x, size_t count)
{
int ret;
size_t i, d, shift;
bn_digit r, rr, *py;
if(y != x)
BN_CHECK(bn_copy(y, x));
BN_CHECK(bn_grow(y, y->used + count / biL + 1));
if(count >= biL)
BN_CHECK(bn_lshd(y, count / biL));
d = count & (biL - 1);
if(d != 0)
{
py = y->dp;
shift = (size_t)(biL - d);
r = 0;
for(i = 0; i < y->used; i++)
{
rr = (*py >> shift);
*py = (*py << d) | r;
py++;
r = rr;
}
if(r != 0)
y->dp[y->used++] = r;
}
clean:
return ret;
}
First, the algorithm checks and increases the accuracy of y. Then, if the number of digits shifted to the left is greater than or equal to the number of BITs, call the bn_lshd function to shift the number of digits to the left, then check whether there are any remaining bits: d = count & (biL-1). If d is not 0, it indicates there are still the remaining bits, based on the principle of 1-bit left shifting, extract the remaining bit and move it to the left to complete the n-bit left shifting operation.
Shift n places to the right:
int bn_rshift(bignum *y, const bignum *x, size_t count)
{
int ret = 0;
size_t i, d, shift;
bn_digit r, rr, *py;
if(y != x)
BN_CHECK(bn_copy(y, x));
if(count >= biL) BN_CHECK(bn_rshd(y, count / biL));
d = count & (biL - 1);
if(d != 0)
{
py = y->dp + y->used - 1;
shift = (size_t)(biL - d);
r = 0;
for(i = y->used; i > 0; i--)
{
rr = *py;
*py = (*py >> d) | (r << shift);
py--;
r = rr;
}
}
bn_clamp(y);
clean:
return ret;
}
Like shifting n places to the left, first shift the right of the entire digit, and then move the remaining bit to the right based on the principle of shifting 1 bit to the right. Note that after the right shift, compress the excess bit to update the used value.
★Time Complexity Analysis
The shift operations mentioned in this article are completed in a loop. The complexity of the algorithm is related to the precision and size of bignum, and the time complexity is roughly O (n ).
★Summary
The shift operation is very effective for some special calculations, such as multiplying by or dividing by 2. Therefore, it is best to use shift operations instead of multiplication or division when such calculation is encountered. After completing the bitwise operation of a large integer, we will begin to talk about four arithmetic operations. The next article will introduce the absolute value addition.
[Back to the directory of this series]
Copyright Notice
Original blog, reproduced must contain this statement, to maintain the integrity of this article, and in the form of hyperlink to indicate the author Starrybird and the original address of this article: http://www.cnblogs.com/starrybird/p/4357222.html