每日一練——大數加減乘除運算實現(網易筆試題)
阿新 • • 發佈:2019-01-25
前幾天做網易筆試題時最後一道題是設計一個大數類,實現加減運算,因為做這道題時只有5分鐘,結果我剛把類寫出來,考試時間就結束了。其實在去年12月我寫過一個RSA的加解密程式,其中就用到了大數的加減乘除運算(當然這只是RSA用到的一小部分),沒有把最後一題寫上去實在太可惜了(>﹏<。)~,今天把我設計的大數類貼出來,後面順便附上面試時經常會讓手寫的加、減、乘程式碼。
一般的,我們會用一個很大的陣列來表示一個大數,陣列中的每一個數(0~9)來代表大數中的某一位,比如big_num[1000],那麼這個數能夠表示1000位大數,後來又人想到用陣列中的每個數來表示0~99999999,這樣陣列只需要1000 / 8 = 125個元素即可實現。那麼問題來了,一個unsigned long能表示的範圍是0~4294967295也就是十六進位制的0~0xFFFFFFFF,為什麼我們不充分利用unsigned long能表示範圍的的每一個數呢?
#define MAXLEN 64 typedef unsigned char BYTE; class big_int { public: ...... void set_zero();//大數清零 void modify_bit();//修正大數位數 big_int& mov(big_int &obj);//大數賦值大數 int cmp(big_int &obj);//兩個大數進行比較,大於返回1,等於返回0,小於返回-1 big_int& add(big_int &num1, big_int &num2);//大數加大數 A=B+C big_int& add(big_int &num);//大數加大數 A+=B big_int& Sub(big_int &num1, big_int &num2);//大數減大數 A=B-C big_int& Sub(big_int &num);//大數減大數 A-=B big_int& mul(big_int &num1, big_int &num2);//大數乘大數 A=B*C big_int& mul(big_int &num);//大數乘大數 A*=B big_int& div(big_int &num1, big_int &num2);//大數除大數 A=B/C big_int& div(big_int &num);//大數除大數 A/=B ...... public: int sign;//正數為1,負數為0 uint32_t data[MAXLEN];//陣列中每一個數代表大數2^32次方進位制的每一位 int length;//大數使用int的長度 int bit;//大數的二進位制位位數 };
//大數加大數A=B+C big_int& big_int::add(big_int &num1, big_int &num2) { uint32_t carry = 0; uint64_t tmp;//用於存放兩個無符號整形數(32位)的和,結果可能超過32位 set_zero(); length = num1.length > num2.length ? num1.length : num2.length; for (int i = 0; i < length; i++) { tmp = (uint64_t)num1.data[i] + (uint64_t)num2.data[i] + (uint64_t)carry; data[i] = (uint32_t)(tmp & 0x00000000FFFFFFFF);//data[i] = tmp % 0x100000000 carry = (uint32_t)(tmp >> 32);//carry = tmp / 0x100000000) } if (carry != 0) { data[length++] = carry; } modify_bit(); return *this; } //大數減大數 在減之前確保被減數大於減數A=B-C big_int& big_int::sub(big_int &num1, big_int &num2) { uint32_t carry = 0; uint64_t tmp; set_zero(); if (num1.cmp(num2) < 0)//如果被減數小於減數,交換兩數再相減,符號位為負 { big_int bi_tmp; bi_tmp.mov(num1); num1.mov(num2); num2.mov(bi_tmp); sign = -1; } int length = num1.length > num2.length ? num1.length : num2.length; for (int i = 0; i < length; i++) { if ((uint64_t)num1.data[i] >= (uint64_t)num2.data[i] + (uint64_t)carry) //如果obj.data[i] = 0xFFFFFFFF, carry = 1,相加會溢位,所以強轉為uint64_t { data[i] = num1.data[i] - num2.data[i] - carry; carry = 0; } else//minuend.data[i] < obj.data[i] + carry { tmp = num1.data[i] | 0x100000000;//tmp = num1.data[i] + 0x100000000; tmp = tmp - num2.data[i] - carry;//tmp一定小於0x100000000 data[i] = (uint32_t)tmp; carry = 1; } } modify_bit(); return *this; } //兩個大數相乘 big_int& big_int::mul(big_int &num1, big_int &num2) { uint32_t carry; uint64_t tmp;//兩個32位二進位制數相乘結果不會超過64位 big_int bi_tmp; set_zero(); for (int i = 0; i < num2.length; i++) { carry = 0; bi_tmp.set_zero(); for (int j = 0; j < num1.length; j++) { tmp = (uint64_t)num1.data[j] * (uint64_t)num2.data[i] + (uint64_t)carry; bi_tmp.data[bi_tmp.length++] = (uint32_t)(tmp & 0x00000000FFFFFFFF); //等價於bi_tmp.data[bi_tmp.length++] = tmp % 0x100000000 carry = (uint32_t)(tmp >> 32);//等價於carry = tmp / 0x100000000) } if (carry != 0) { bi_tmp.data[bi_tmp.length++] = carry; } bi_tmp.left_move_len(i); add(bi_tmp); } modify_bit(); return *this; } //大數除大數,採用試商的方法 big_int& big_int::div(big_int &num1, big_int &num2) { big_int bi_tmp, bi_dividend; int len; uint64_t dividend_num; uint32_t div_num; bi_dividend.mov(num1); set_zero(); while (bi_dividend.cmp(num2) > 0) { if (bi_dividend.data[bi_dividend.length-1] > num2.data[num2.length-1]) { len = bi_dividend.length - num2.length; //這裡採用五入的試商方法 div_num = bi_dividend.data[bi_dividend.length-1] / (num2.data[num2.length-1]+1); } else if (bi_dividend.length > num2.length) { len = bi_dividend.length - num2.length - 1; dividend_num = (uint64_t)bi_dividend.data[bi_dividend.length-1]; dividend_num = (dividend_num << 32) + bi_dividend.data[bi_dividend.length-2]; if (num2.data[num2.length-1] == 0xFFFFFFFF) { div_num = (uint32_t)(dividend_num >> 32);//dividend_num / 0x100000000 等價於 dividend_num >> 32 } else { div_num = (uint32_t)(dividend_num / (uint64_t)(num2.data[num2.length-1]+1)); } } else//被除數最高位等於除數最高位 被除數位數等於除數位數 { add(1);//被除數除以除數的商一定為1 break; } bi_tmp.mov(div_num); bi_tmp.left_move_len(len); add(bi_tmp); bi_tmp.mul(num2); bi_dividend.sub(bi_tmp); } if (bi_dividend.cmp(num2) == 0) { add(1); } modify_bit(); return *this; }
在面試和筆試中當然不能這麼玩,附上面試時讓手寫的帶符號的加減乘程式碼。
//無符號加法,且num1 >= num2
string unsigned_add(string num1, string num2)
{
int len1 = num1.size();
int len2 = num2.size();
int carry = 0;
string result = "";
int tmp;
while (num1.size() != num2.size())//加0補齊
{
num2 = '0' + num2;
}
for (int i = len1 - 1; i >= 0; i--)
{
tmp = num1[i] - '0' + num2[i] - '0' + carry;
result += tmp % 10 + '0';
carry = tmp / 10;
}
if (carry)
{
result += carry + '0';
}
reverse(result.begin(), result.end());
return result;
}
//無符號減法,且num1 >= num2
string unsigned_sub(string num1, string num2)
{
int len1 = num1.size();
int len2 = num2.size();
int carry = 0;
string result = "";
while (num1.size() != num2.size())//加0補齊
{
num2 = '0' + num2;
}
for (int i = len1 - 1; i >= 0; i--)
{
if (num1[i] >= num2[i] + carry)
{
result += num1[i] - num2[i] - carry + '0';
carry = 0;
}
else
{
result += 10 + num1[i] - num2[i] - carry + '0';
carry = 1;
}
}
while (result[result.size() - 1] == '0')//去掉多餘的0
{
result = result.substr(0, result.size() - 1);
}
reverse(result.begin(), result.end());
return result;
}
//無符號乘法
string unsigned_mul(string num1, string num2)
{
int len1 = num1.size();
int len2 = num2.size();
int cur, carry, tmp;
string result = "";
while (result.size() != len1 + len2)
{
result = '0' + result;
}
for (int i = len1 - 1; i >= 0; i--)
{
cur = len1 - 1 - i;
carry = 0;
for (int j = len2 - 1; j >= 0; j--)
{
tmp = result[cur] - '0' + (num1[i] - '0') * (num2[j] - '0') + carry;
result[cur] = tmp % 10 + '0';
carry = tmp / 10;
cur++;
}
if (carry)
{
result[cur++] = carry + '0';
}
}
while (result[result.size() - 1] == '0')//去掉多餘的0
{
result = result.substr(0, result.size() - 1);
}
reverse(result.begin(), result.end());
return result;
}
//把符號位從string中分離開來
int separate(string &num)
{
if (num[0] == '-')
{
num = num.substr(1, num.size());
return -1;
}
else
{
return 1;
}
}
//帶符號的加法
string add(string num1, string num2)
{
int sign1, sign2, tmp;
sign1 = separate(num1);
sign2 = separate(num2);
string result;
if (num1.size() < num2.size() || num1.size() == num2.size() && num1.compare(num2) < 0)
{
num1.swap(num2);
tmp = sign1;
sign1 = sign2;
sign2 = tmp;
}
if (sign1 == -1 && sign2 == 1)
{
result = '-' + unsigned_sub(num1, num2);
}
else if (sign1 == 1 && sign2 == -1)
{
result = unsigned_sub(num1, num2);
}
else if (sign1 == 1 && sign2 == 1)
{
result = unsigned_add(num1, num2);
}
else
{
result = '-' + unsigned_add(num1, num2);
}
return result;
}
//帶符號的減法
string sub(string num1, string num2)
{
int sign1, sign2, tmp;
sign1 = separate(num1);
sign2 = separate(num2);
string result;
if (num1.size() < num2.size() || num1.size() == num2.size() && num1.compare(num2) < 0)
{
num1.swap(num2);
tmp = sign1;
sign1 = sign2 * (-1);
sign2 = tmp * (-1);
}
if (sign1 == -1 && sign2 == 1)
{
result = '-' + unsigned_add(num1, num2);
}
else if (sign1 == 1 && sign2 == -1)
{
result = unsigned_add(num1, num2);
}
else if (sign1 == 1 && sign2 == 1)
{
result = unsigned_sub(num1, num2);
}
else
{
result = '-' + unsigned_sub(num1, num2);
}
return result;
}
//帶符號的乘法
string mul(string num1, string num2)
{
int sign1, sign2, tmp;
sign1 = separate(num1);
sign2 = separate(num2);
string result;
if (num1.size() < num2.size() || num1.size() == num2.size() && num1.compare(num2) < 0)
{
num1.swap(num2);
tmp = sign1;
sign1 = sign2;
sign2 = tmp;
}
result = unsigned_mul(num1, num2);
if (sign1 * sign2 == -1)
{
result = '-' + result;
}
return result;
}