如题思路简单做起来容易出错的大整数四则运算,
加减乘的思路比较相似,先对应为加减乘后处理进位或借位,
加法和减法也可以带着进位边加减边处理进位 ,乘法用一个错位的循环刚好是我们手算得过程,所以最后处理进位要方便些,
对于除法想了一会,上网搜了看大都是模拟手算的过程,移位后对应位做减法 ,减到负数为止,减法的次数-1就是对应位的商值 跳出外循环的条件是余数小于除数
写了一整个下午才写出来。。。动手能力有待提高。基本上一个运算符一个小时,除法做的最久。。。以后多写几次 速度应该会提高吧。
开始没有写 类 直接写的几个函数 后来为了响应课堂所学的面向对象 把它封装到了一个bigNum类中。。
#include <iostream>
using namespace std;
class bigNum
{
public:
string result,big;
string add(const string &a_,const string &b_);
string sub(const string &a_,const string &b_);
string mul(const string &a_,const string &b_);
string div(const string &a_,const string &b_);
string operator + (const bigNum &B_);
string operator - (const bigNum &B_);
string operator * (const bigNum &B_);
string operator / (const bigNum &B_);
};
string bigNum::operator + (const bigNum &B_)
{
add(big,B_.big);
return result;
}
string bigNum::operator - (const bigNum &B_)
{
sub(big,B_.big);
return result;
}
string bigNum::operator * (const bigNum &B_)
{
mul(big,B_.big);
return result;
}
string bigNum::operator / (const bigNum &B_)
{
div(big,B_.big);
return result;
}
string bigNum::add(const string &a_,const string &b_)
{
string a,b,c_tem;
int *tem;
char *re;
unsigned i,j;
a = a_ ;b = b_;
if(a.length() < b.length()){c_tem = a; a = b; b = c_tem;}
tem = new int[a.length() + 1];//结果最大为较长的数的长度加1
re = new char[a.length() + 2];//存放中间结果的字符串 多一个用了存放 '\0'
for(i = 0;i < a.length() + 1;i++)tem[i] = 0;//数组初始化
for(i = 0;i < a.length() + 2;i++)re[i] = '\0';
for(i = 0,j = 0;i < a.length();i++)
{
if(i >= (a.length() - b.length()))
tem[i+1] = (a[i] - 48) + (b[j++] -48);
else tem[i+1] = a[i] - 48;
}
for(i = a.length();i > 0;i--)//这个地方必须从低位开始处理进位
{
if(tem[i] >= 10)
{
tem[i-1] += 1;
tem[i] -= 10;
}
}
i = 0;
while(tem[i] == 0)i++;//找到第一个不为0的位置
for(j = 0;i < a.length() + 1;i++,j++)//跳过多少个0 相应的结果就少几位
re[j] = tem[i] + 48;
result = re;
delete []re;
delete []tem;
return result;
}
string bigNum::sub(const string &a_,const string &b_)
{
string a,b,c_tem;
unsigned sign = 0,i,j;
int *tem ;
char *re ;
a = a_; b = b_;
if(a.length() < b.length()){c_tem = a; a = b; b = c_tem;sign = 1;}
else if(a == b){result = "0";return result;}
else if(a.length() == b.length() && a < b){c_tem = a; a = b; b = c_tem;sign = 1;}
tem = new int[a.length()];
re = new char[a.length() + 1];//存放 负号的位置
for(i = 0;i < a.length();i++)tem[i] = 0;
for(i = 0;i < a.length() + 1;i++)re[i] = '\0';
for(i = 0 ,j = 0;i < a.length();i++)
{
if(i >= (a.length() - b.length()))tem[i] = a[i] - b[j++];
else
tem[i] = a[i] -48;
}
for(i = a.length() -1;i > 0 ;i--)//和加法一样 处理借位
{
if(tem[i] < 0)
{
tem[i-1] -= 1;
tem[i] += 10;
}
}
i = 0;
while(tem[i] == 0)i++;//过滤掉 零
if(sign)
{
for(j = 1; i < a.length();i++,j++)re[j] = tem[i] + 48;
re[0] = '-';
}
else
for(j = 0; i < a.length();i++,j++)re[j] = tem[i] + 48;
result = re;
delete []tem;
delete []re;
return result;
}
string bigNum::mul(const string &a_,const string &b_)
{
unsigned i,j;
string a,b,c_tem;
int *tem;
char *re;
a = a_,b = b_;
tem = new int[a.length() + b.length()];
re = new char[a.length() + b.length() + 1];
for(i = 0;i < a.length() + b.length();i++)tem[i] = 0;
for(i = 0;i < a.length() + b.length() + 1;i++)re[i] = '\0';
for(i = 0;i < a.length();i++)
for(j = 0;j < b.length();j++) //乘法的竖式计算过程
tem[i + j + 1] += (a[i] - 48) * (b[j] - 48);
for(i = a.length() + b.length() - 1;i > 0;i--)//处理进位
{
if(tem[i] >= 10)
{
tem[i-1] += tem[i] / 10;
tem[i] %= 10;
}
}
i = 0;
while(tem[i] == 0)i++;
for(j = 0;i < a.length() + b.length();i++,j++)
re[j] = tem[i] + 48;
result = re;
delete []re;
delete []tem;
return result;
}
string bigNum::div(const string &a_,const string &b_)
{
string a,b,c_tem,mod,test;
char *re;
unsigned i,len_a,len_b;
int tem = -1,n_re = 0,k = 0;
a = a_; b = b_;
if(a.length() < b.length()){result = "0";return result;}
else if(a.length() == b.length())
{
if(a < b)
{
result = "0";
return result;
}
else if(a == b)
{
result = "1";
return result;
}
}
re = new char[a.length() - b.length() + 2];
for(i = 0; i < a.length() - b.length() + 2;i++)re[i] = '\0';
len_a = a.length();len_b = b.length();
while(1)
{
b = b_;
mod = a;
for(i = 0;i < len_a - len_b - k;i++)b.insert(i + len_b ,"0");//b左移补零
while(1)
{
c_tem = mod;
mod = sub(mod,b);
tem++;
if(mod[0] == '-')
{
re[n_re] = tem + 48;
a = c_tem;
n_re++;
k++;
tem = -1;
break;
}
}
test = sub(a,b_);//退出条件
if(test[0] == '-' || test == "0")break;
}
for(i = n_re;i < len_a - len_b + 1;i++) re[i] = '0';
result = re;
delete []re;
return result;
}
int main()
{
char x;
bigNum A,B;
while(cin>>A.big>>x>>B.big)
{
switch(x)
{
case '+':cout<<(A + B);break;
case '-':cout<<(A - B);break;
case '*':cout<<(A * B);break;
case '/':cout<<(A / B);break;
}
cout<<endl;
}
return 0;
}
参考网页
大数乘法
http://sumile.blog.hexun.com/62509182_d.html
http://www.cnblogs.com/hicjiajia/archive/2010/09/26/1836337.html
mooc论坛的程序设计实习讨论
“可以使用类似机器运算除法的方法,实际做的是减法。
假设12345除13,首先找到1300(再扩大10倍就大于12345了),从12345中反复减去1300,9次后不够减,则第一位商是9,再从余数中反复减去130,得到第二位商,以此类推。”
大数除法应该是最难实现的了,假设数组a除以b,实现它有两种方式:
1:用a-=b,循环,直到a<b,记下来执行多少次,则为计算结果;
2:模拟现实,我们手算时是怎么算的,程序就怎么写,
假设a={2 4 2 3 1},b={2 3},结果result={0 0 0 0 0}:
先取a[0]a[1]即24,减去b一次,得a={0 1 2 3 1},result={0 1 0 0 0};
再取a[1]a[2]即12,发现它小于b,则多取一位,取a[1]a[2]a[3]即123,减b五次,得a={0 0 0 8 1},result={0 1 0 5 0};
再取a[3]a[4]即81,减b三次,得a={0 0 0 1 2},result={0 1 0 5 3}。
如果按照方法一计算,效率是无法容忍的。
假如用11111除以1,方法一要计算11111次,而方法二则只要5次。
下面是方法二的代码,经过了简单的测试(没做数据校验,假设数据都是正常的):
http://blog.csdn.net/sunmenggmail/article/details/7532522
加法:O(n)
类似于手算的方法,一个for循环小的那个数,然后每一位对应相加,有进位的就把前一位的数加一
减法:O(n)
同理加法,只是把每一位变成对应相减,如果对应相减得到是负数,该位+10,把前一位减一
乘法:O(n^2)
我是用的手算法,两个for,一个循环乘数一个循环被乘数,用乘数的某一位与被乘数的每一位相乘,结果/10进行进位,%10进行保留
除法:O(nlogn)
这个特别难,我想了2天= =。
用的是这里论坛里另外一个po主的算法,即假设A/B(w.l.o.g. A>B),首先计算两个数的位差即,假设A是10位,B是5位,那么位差为5,讲B向左平移5位(我自己写了一个成员函数,没有重构<<,一开始重构的,但是老错,就改了),然后while一下,用A-B00000直到结果不大于0(一开始用的为负,结果发现在能整除的时候的商的个位数老错,才发现其实可以等于0),记录叠减次数,存进商的位差(5)那一位,然后向左平移4位继续上面的步骤。
可以使用类似机器运算除法的方法,实际做的是减法。
假设12345除13,首先找到1300(再扩大10倍就大于12345了),从12345中反复减去1300,9次后不够减,则第一位商是9,再从余数中反复减去130,得到第二位商,以此类推。
基本的思想是反复做减法,看看从被除数里最多能减去多少个除数,商就是多少。一个
一个减显然太慢,如何减得更快一些呢?以7546 除以23 为例来看一下:开始商为0。先减
去23 的100 倍,就是2300,发现够减3 次,余下646。于是商的值就增加300。然后用646
减去230,发现够减2 次,余下186,于是商的值增加20。最后用186 减去23,够减8 次,
因此最终商就是328。
所以本题的核心是要写一个大整数的减法函数,然后反复调用该函数进行减法操作。
计算除数的10 倍、100 倍的时候,不用做乘法,直接在除数后面补0 即可。
http://www.cnblogs.com/c840136/articles/2168405.html
http://wenwen.soso.com/z/q24755485.htm
http://www.melory.me/2013/03/09/%E5%A4%A7%E6%95%B0%E9%99%A4%E6%B3%95%E8%BF%90%E7%AE%97/
http://www.melory.me/2013/03/09/%E5%A4%A7%E6%95%B0%E9%99%A4%E6%B3%95%E8%BF%90%E7%AE%97/
来个简单的时间分析, 如有不对地方,轻拍。
+, 没有什么可说的, O( max( m, n)), m, n 分别是 operand1, operand2 的 位数, 这里就100 吧。
-, 和加一样, 或更间单, 比如, 99999999999999999999999 - 1, 只要二次, 但, 100000000000 - 1, 就要n 次。
X, 模拟手算 时间是 O( m * n), m, n 这里最大算一百吧, 时间就时 O(n ^ 2), n = 100. 快一点的“karatsuba algorithms" O(n ^ ( 3/2)), 比较好实现。 再快的我就不太懂了, 据说能达到O(n*log(n)).
/, 多种实现方法:
1) 98000000 / 12 =》 98000000 - 1200000 =》 也就是把12 补位后连减, 直到余数小于12。 时间 O(n ^ 2), 因为, 最坏每位数都要减, 如 999999999/1, 每次都用减法, 时间是, O(1 + 2 + 3 + 4 + ... + n ) = O(n ^ 2).
2) 模拟手算 时间是 O( (n - m + 1) * m), n 是被除数的位数长度, m 是除数的位数长度, 如 9999999 / 11, 这里 N = 7, m = 2. 如果m 很小, 会很快, 如果m 很大, 会很快, m = n / 2 时会比较慢。 在求 99 / 11 时就用连减。
3) 试商是最慢的一种,因为它用到 X, 如用X的时间O(N^2), 那最快的利用试商法 也要 O((n - m) * m * (log( 10 ^ (n - m))),
因为,假如 n 是被除数的位数长度, m是除数的位数长度, 用binary search 来试商时间也要 O(log(10 ^ (n - m)), 如果m = n / 2, 那就会很慢了, 还有就是没有除法,我真不知如何用 binary search. 如用X的时间是O(N^(3/2)), 运行时间也要, O(( (n - m) * m ) ^ ( 3 /2 ) * (log(10 ^ (n - m))).
所以除法最好还是被用到乘法。
https://class.coursera.org/pkupop-001/forum/thread?thread_id=463
我主要利用移位与减法来进行除法运算:
比如123456/12
1.先将12移位4位(6-2=4)变成120000,然后用123456来减,一直减到负数为止;
2.然后将12移位3位,变成12000,然后用第一步减剩下的数(3456)去减12000,一直减到负数为止
3.以此类推,一直到12移位0为止。
大家有没有更简单的方法???
https://class.coursera.org/pkupop-001/forum/thread?thread_id=404