C++ 大整数类(加减乘除取余乘方)

附带详细说明,复杂度分析

复杂度分析说明

文中用a表示左操作数,b表示右操作数,n代表操作数位数

整体思路

首先c++存储大整数有3个思路
思路1

采用十进制vector或者string来存储0-9,加减乘除算法全模拟人工实现
实际上建议用deque来存储
因为加法结果从低位产生到高位
除法结果却是从高位产生到低位
需要从前后和插入结果需要push_back()和push_front()
优点

  • 存储的就是10进制,输出方便
  • 思路简单明了

缺点

  • 实现复杂,乘法实现最好映射9*9乘法表,自己变成数字在还原回来的话每一次都需要遍历2遍数组
  • 存储效率不高char8bit只用了4bit不到

算法效率应该不高

思路2
使用2进制存储bit数组deque实现(原因同上),加减乘除模拟计算机底层位操作,自己实现移位还有所有位操作再来构建加减乘除
优点

  • 存储效率拉满

缺点

  • 致命缺陷,每次输出结果要从2进制转化为10进制速度慢(n次除法取余)

效率应该一般自己实现的位操作(依靠循环遍历),没用计算机自带的运算器
思路3
ps:uint32_t=unsigned int ,uint64_t=unsigned long long
思路2改进
使用2^32进制存储用deque<uint32_t>实现,(为什么不用uint64_t?因为uint32_t乘法进位要用uint64_t来存)加减乘除使用语言自带的±*/自己处理进位
优点

  • 存储效率拉满
  • 速度较快(使用计算机自带的±*/)
    缺点
  • 致命缺陷,每次输出结果为16进制转化为10进制速度慢

我的思路
思路3再改进
使用10^9 进制存储用deque<uint32_t>实现,加减乘除使用语言自带的±*/自己处理进位(就是自己规定uint32_t的值不超过10^9)
相比3牺牲点存储效率,改善了缺陷,10^9进制可以拼接成10进制输出
我的实现只处理正整数,因为实现了减法所以也影响不是很大只是觉得负数麻烦想自己在外面控制下也能解决问题就没有实现
有想法的同学可以在类里加一个static bool flag=0 自己完善;

实现雏形

每个节点最大值为10^9次方

class BigInt {
	friend ostream& operator<<(ostream& os, const BigInt& x);
	friend istream& operator>>(istream& is, BigInt& x);
	deque<uint32_t> number;
	static const uint32_t LIMIT = 1000000000;
	}

之后重载需要的运算符就好

构造函数设计

设计成string输入直观方便
我是高位在前低位在后
例如BigInt a(“1987654321”);构造后
其中的结构为a.number[0]=1;a.number[1]=987654321;
所以使用push_front()

BigInt() {}
	BigInt(const string& s) {
		stringstream sstr;
		string ss; 
		ss.reserve(s.size()*2);//预留足够出空间防止反复扩容
		size_t i = s.size();
		while (i >= 9) {//每隔九个数字加一个空格
			i -= 9;
			ss += s.substr(i, 9);//从低位往高位9位一组分割写入
			ss.push_back(' ');
		}
		if (i)ss += s.substr(0, i);
		sstr << ss;
		uint64_t temp;
		while (sstr >> temp)
			number.push_front(temp);
		while (number.size() > 1 && number.front() == 0)//处理字符串的最前多余的0
			number.pop_front();
	}
	BigInt(initializer_list<uint32_t> list) {
		number.assign(list);
	}
	BigInt(deque<uint32_t>::const_iterator beg, deque<uint32_t>::const_iterator end) {
		number.assign(beg, end);
	}
	~BigInt() {}

输入输出

直接用cout输出即可,注意除了第一个(最高位)以外其余数要用0补全9位
输入调用输入string的构造函数

ostream& operator<<(ostream& os, const BigInt& x) {
	for (auto p = x.number.begin(); p != x.number.end(); ++p) {
		if (p != x.number.begin())os << setw(9) << setfill('0');
		os << *p;
	}
	return os;
}
istream& operator>>(istream& is, BigInt& x) {
	string temp;
	is >> temp;
	x = { temp };
	return is;
}

<符号实现

下面的其他符号的实现会用到<号
deque<uint32_t>中首先谁位数多谁大,位数一样就从最高位往最低位比较大的就大
因为最前面的是最高位所以正好可以使用deque自带的<一步到位
<实现了其他的也都出来了一起实现了

bool operator<(const BigInt& b) const {
		if (this->number.size() < b.number.size())
			return true;
		else if (this->number.size() == b.number.size() && this->number < b.number)
			return true;
		else return false;
	}
	bool operator>(const BigInt& b)const {
		return b < *this;
	}
	bool operator==(const BigInt& b)const {
		return !(*this < b || *this > b);
	}
	bool operator>=(const BigInt& b)const {
		return !(*this < b);
	}
	bool operator<=(const BigInt& b)const {
		return !(*this > b);
	}

加法实现

挨个加起来处理好进位 -------------------复杂度max(an,bn)
从低位处理到高位所以是反向迭代器

BigInt operator+(const BigInt& b) const {
		BigInt res;
		auto ap = this->number.rbegin();
		auto bp = b.number.rbegin();
		uint32_t s = 0;
		uint32_t carry = 0;
		while (ap != this->number.rend() || bp != b.number.rend())
		{
			if (ap == this->number.rend()) {//左操作数已经到结尾
				s = *bp + carry;
				carry = s / (LIMIT);//处理溢出10^9的进位
				res.number.push_front(s % LIMIT);//计算结果本位
				++bp;
			}
			else if (bp == b.number.rend()) {//右操作数已经到结尾
				s = *ap + carry;
				carry = s / (LIMIT);
				res.number.push_front(s % LIMIT);
				++ap;
			}
			else {
				s = *bp + *ap + carry;
				carry = s / (LIMIT);
				res.number.push_front(s % LIMIT);
				++ap;
				++bp;
			}
		}
		if (carry)res.number.push_front(carry);//处理最后的进位
		return res;
	}
	BigInt& operator+=(const BigInt& b) {
		return *this = *this + b;
	}

乘法实现

乘法实现使用到上面的实现的加法
先实现BigInt x uint32_t ---------------------------------复杂度 (an)
再将BigInt a x BigInt b拆分为bn个BigIntuint32_t
再移位相加调用BigInt + BigInt ----------------------复杂度 (an x bn)
32位数
32位数再加上进位绝不会超过64位的极限自己可以验证一下

BigInt operator*(const uint32_t& b) const {
		BigInt res;//最前方为最高位,最高位为0则BigInt为0
		if (this->number.front() == 0 || b == 0)
			return res = { 0 };
		uint32_t carry = 0;
		uint64_t s;//使用64位防止溢出
		for (auto ap = this->number.rbegin(); ap != this->number.rend(); ++ap) {
			s = (uint64_t)*ap * b + carry;
			carry = s / LIMIT;
			res.number.push_front(s % LIMIT);
		}
		if (carry)res.number.push_front(carry);
		return res;
	}
	BigInt& operator*=(const uint32_t& b) {
		return *this = *this * b;
	}
	BigInt operator*(const BigInt& b) const {
		BigInt res;
		if (this->number.front() == 0 || b.number.front() == 0)
			return res = { 0 };
		int shift = 0;//移位计数
		for (auto bp = b.number.crbegin(); bp != b.number.crend(); ++bp) {
			BigInt temp;
			temp = *this * *bp;//调用上面的BigInt*uint32_t
			if (temp.number.front())//BigInt不为0
				for (int i = 0; i < shift; ++i)
					temp.number.push_back(0);
			++shift;
			res += temp;
		}
		return res;
	}
	BigInt& operator*=(const BigInt& b) {
		return *this = *this * b;
	}

减法实现

挨个相减处理借位没什么好说的
调用上面的符号重载< 和=处理特殊情况
因为BigInt只表示正整数所以减法只能左边的操作数大于右操作数否则丢出异常-----------------------------------------------------------复杂度 max(an,bn)

BigInt operator-(const BigInt& b) const {//由调用者保证左边大于右边
		BigInt res;
		if (*this < b) { cout << "输入错误 小数减大数" << endl; throw "MinusOverFlow"; }
		if (*this == b) return res = { 0 };
		auto ap = this->number.rbegin();
		auto bp = b.number.rbegin();
		uint32_t s = 0;
		uint32_t carry = 0;
		while (ap != this->number.rend() || bp != b.number.rend())
		{
			if (bp == b.number.rend()) {//只可能右边先结束
				if (*ap >= carry) {//不用借位
					s = *ap - carry;
					carry = 0;
				}
				else
				{
					s = *ap - carry + LIMIT;
					carry = 1;
				}
				++ap;
			}
			else {
				if (*ap >= (*bp + carry)) {//不用借位
					s = *ap - *bp - carry;
					carry = 0;
				}
				else {
					s = *ap - *bp - carry + LIMIT;
					carry = 1;
				}
				++ap;
				++bp;
			}
			if(s)res.number.push_front(s);
		}
		return res;
	}
	BigInt& operator-=(const BigInt& b) {
		return *this = *this - b;
	}

除法取余实现

除法取余的话就比较难找到好的实现
BigInt/uint32_t BigInt%uint32_t和可以利用计算机自带的除法和取余挨个计算处理进位 --------------------------------------------复杂度为(an)
其他的我暂未找到更好的实现
BigInt a/BigInt b采用估测法

  1. 直接估测用2分法逼近答案
    范围为1-a;r为左边界 l为右边界 m为估测值都是BigInt类型
    结束条件为bm<a和b(m+1)>a
    理论上log2(a)次估测就能结束算法但是本人实测却效率不理想虽然相对小数据的能用,但是效果却不如2(实现过亲测不如2方法)
    后来发现原因是每次估测需要计算b*m类型为BigInt x BigInt 一次
    m=(r+l)/2-1|+1类型为(BigInt x BigInt)/uint32_t-BigInt 一次
    效率为log2(a) x (an x bn)
  2. 模拟人工除法
    举个例子9899/99,例子中每一位数对应到BigInt类代表一个uint32_t
    挨个扫描9899 直到大于除数99
    所以第一步计算989/99 类型都是BigInt
    这里照上面估测2分实现范围为1-10^9
    r为左边界 l为右边界 m为估测值 类型都为uint32_t原因如下
    可以发现扫描出来的数最多比除数99多一位所以估测值m(商)只需要一位十进制(对应到类中就是最大值10^9所以可以用uint32_t存储)
    结束条件为bm<a和b(m+1)>a
    理论上log2(10^9)<30次内结束
    每次估测计算b x m和b类型BigInt x uint32_t,
    计算 m=(r+l)/2-1|+1单都是uint32_t操作时间忽略不计
    ---------------------------------------------整体算法复杂度为(30bn)
    估计出m为9后算出余数989-99*9=98
    再继续扫描拼接除98 “+”9 =989再计算989/99=9 余数为98
    扫描至结尾算法结束9899/99商为99 余数为98
    实测
    BigInt x, y;
    string a(100000, ‘9’), b(100000, ‘2’);
    cout << (BigInt{ a } /BigInt{b}) << endl;
    能在5秒内完成
    BigInt x, y;
    string a(10000, ‘9’), b(1, ‘2’);
    cout << (BigInt{ a } /BigInt{b}) << endl;
    10000个9/1个2能在2秒内完成
pair<BigInt, uint32_t>divide(const uint32_t& b)const {
		BigInt res;
		uint64_t carry = 0;
		if (b == 0) { cout << "输入错误 除或者取余0" << endl; throw "DivideByZero"; }
		for (auto ap = this->number.begin(); ap != this->number.end(); ++ap) {
			if (b > * ap&& carry == 0) {//有余数必然大
				carry = *ap;
				if(!res.number.empty())res.number.push_back(0);//商0
				continue;
			}
			else
			{
				carry *= LIMIT;
				carry += *ap;
				res.number.push_back(carry / b);//直接除法计算商
				carry %= b;//取余计算余数
			}
		}
		if (res.number.empty())res = { 0 };//防止BigInt<b因为条件无法判断没写BigInt<uint32_t的运算符
		return make_pair(res, carry);
	}

pair<BigInt, BigInt> divide(const BigInt& b) const {//性能薄弱点 
		BigInt res;
		BigInt carry;
		if (b.number.front() == 0) { cout << "输入错误 除或者取余0" << endl; throw "DivideByZero"; }
		for (auto ap = this->number.begin(); ap != this->number.end(); ++ap) {
			carry.number.push_back(*ap);
			if (carry < b) {
				if(!res.number.empty())res.number.push_back(0);//商0
				continue;
			}
			else if (b < carry) {
				uint32_t l = 1, r = LIMIT - 1, m;
				BigInt cur, next;
				while (1)
				{
					m = (l + r) / 2;
					cur = { b * m };
					next = { cur + b };
					if (cur < carry && carry < next)break;
					if (cur < carry)
						l = m + 1;
					else if (carry < cur)
						r = m - 1;
					else break;
				}
				res.number.push_back(m);
				carry -= cur;
			}
			else {//b=carry
				res.number.push_back(1);
				carry.number.clear();
			}
		}
		if (carry.number.empty())carry = { 0 };
		if (res.number.empty())res = { 0 };
		return make_pair(res, carry);
	}
	BigInt operator/(const uint32_t& b)const {
		return divide(b).first;
	}
	BigInt& operator/=(const uint32_t b) {
		return *this = *this / b;
	}
	BigInt operator/(const BigInt& b)const {
		return divide(b).first;
	}
	BigInt& operator/=(const BigInt& b) {
		return *this = *this / b;
	}
	uint32_t operator%(const uint32_t& b)const {
		return divide(b).second;
	}
	BigInt& operator%=(const uint32_t& b) {
		return *this = BigInt({ *this % b });
	}
	BigInt operator%(const BigInt& b) const {
		return divide(b).second;
	}
	BigInt& operator%=(const BigInt& b) {
		return *this = *this % b;
	}

乘方实现

采用快速幂递归实现
因为b太大根本算不完(时间太长)所以没必要实现BigInt^=BigInt
进行最多log2(2^32)次方次递归每次都要自己乘自己BigInt x BigInt
--------------------------------- 复杂度32(an)^2

BigInt& operator^=(const uint32_t& b) {
		if (b == 1)return *this;
		if (b & 1) {
			BigInt a = *this;
			*this *= *this;
			*this ^= b / 2;
			*this *= a;
		}
		else {
			*this *= *this;
			*this ^= b / 2;
		}
		return *this;
	}
	BigInt operator^(const uint32_t& b)const {
		BigInt res = { *this };
		return res ^= b;
	}

代码

整体代码放在了GitHub上了
都看到了这里麻烦点个赞留个言再走呀同学们秋梨膏
链接在此-ps:点个星号也行

发布了8 篇原创文章 · 获赞 6 · 访问量 537

猜你喜欢

转载自blog.csdn.net/qq_41433566/article/details/105346075