char是8位的,short是16位的,int是32位的,最大的long long是64位的,也就是说基本的数据类型无法表示超过2的64次方-1的数
如今又到了找工作的时候了,面试手撕题少不了,听说这个手搓大整数也有过面试题,这里先搓一手,免得以后执手相看泪眼
其实Java有这个大整数类,手搓大整数类可以说是一个比较综合的应用,需要使用到重载运算符,包括输入输出、四则运算等等,还需要字符串的运算,其中比较运算符的实现是十分巧妙的,这里的思维应用、逻辑运算都是很好的训练,这里只实现了大整数相加的功能,源码已开源在MaolinYe/BigInteger: 使用C++11实现的一个大整数类 (github.com)
欢迎大家指正修改
目录
基本思路
重载赋值运算符
重载输入输出运算符
重载加运算符
重载比较运算符
基本思路
实现大整数有两种方法,一种是将大数当成字符来处理,手动计算加减乘除,另一种则是将大数分成多个小部分用基本类型存储处理
我们这里实现第二种方法的,目前版本是支持非负整数
基本思路是将一个大整数切分成几段小的用int存储,用vector容器来存储每段,例如
1111222233334444 integer[1]=11112222 integer[0]=33334444
重载赋值运算符
重载赋值运算符,实现从基本数据类型到大整数的转换,通过和base取余切分出段
class BigInteger {static const int width = 8; // 切分段的长度static const int base = 1e8; // 切分段的数量级vector<int> integer; // 存储各个段int segments = 0; // 切分的段数BigInteger operator=(long long num) { // 重载赋值运算符,从基本数据类型转换大整数存储integer.clear();do {integer.emplace_back(num % base);num /= base;segments++;} while (num > 0);return *this;}
};
实现默认构造函数和带参构造函数,通过重载的赋值运算符直接赋值
explicit BigInteger(long long num = 0) {*this = num;}
继续重载赋值运算符,实现从字符串到大整数的转换,首先计算出这个大整数有几段,这里的计算方法十分巧妙,如果直接除以width当不是整除的时候会少数一段,所以先减一再除最后加一的方法可以适用整除和不整除的情况
然后需要计算每段的开始和结束的位置,因为vector存储段的顺序是从后往前,所以这里计算段的位置也是从后往前,然后通过string取子串,再通过stoi函数将字符串转成整型存储
BigInteger operator=(const string &num) { // 重载赋值运算符,从字符串类型转大整数存储integer.clear();int length = num.size();segments = (length - 1) / width + 1; // 这里计算段数的方法十分巧妙for (int i = 0; i < segments; i++) {int end = length - i * width; // 计算每段结束的地方,从最后一段开始int start = max(0, end - width); // 计算每段开始的地方integer.emplace_back(stoi(num.substr(start, end - start))); // 字符串转整数}return *this;}
重载输入输出运算符
接着重载输出运算符,重载输出运算符可以通过友元函数和成员函数两种方法实现,我们这里通过友元函数的方法实现,倒序输出vector
ostream &operator<<(ostream &out) { // 重载输出运算符,vector倒着输出for (auto it = integer.rbegin(); it != integer.rend(); it++) {cout << *it;}return out;}
重载输入运算符,将输入当成字符串,通过重载的赋值运算符直接赋值
istream &operator>>(istream &in) { // 重载输入运算符,当成字符串输入,用重载的赋值运算符直接赋值string num;in >> num;*this = num;return in;}
重载加运算符
加
大整数加运算的基本思想是将各个段相加,需要处理段相加的时候出现的进位情况,由于int型可以表示的范围大概是10个数量级,而我们给每段设置的位数是8,因此可以使用int型来存储段加的结果,然后继续存储加结果和base取余的结果,计算出和base整除的结果,留到下一个段继续相加
BigInteger operator+(const BigInteger &bigInteger) const {BigInteger result;for (int i = 0, carry = 0; carry || i < segments || i < bigInteger.segments; i++) {if (i < segments)carry += integer[i];if (i < bigInteger.segments)carry += bigInteger.integer[i];result.integer.emplace_back(carry % base);result.segments++;carry = carry / base;}return result;}
顺便实现+=
BigInteger operator+=(const BigInteger &bigInteger) {*this = *this + bigInteger;return *this;}
重载比较运算符
这个比较两个大整数的实现比较巧妙
我们先实现一个重载小于的判断,先比较两个大整数的段数,如果段数不同直接返回段数的比较就行,如果段数相同,由于大整数的低位存储在vector的前面,所以从后面段开始比较高位,如果高位不相同,那么返回高位的比较结果就行,如果都相同,那么说明相等
bool operator <(const BigInteger&bigInteger) const{if(segments!=bigInteger.segments)return segments<bigInteger.segments;for(int i=segments-1;i>=0;i--){ // 倒着比较,因为低位在vector的前面,先比较高位if(integer[i]!=bigInteger.integer[i])return integer[i]<bigInteger.integer[i];}return false; // 相等}
然后接下来的大于、大于等于、小于等于、不等于、等于都可以通过小于实现
bool operator>(const BigInteger &bigInteger) const {return bigInteger < *this; // a大于b等价于b小于a}bool operator<=(const BigInteger &bigInteger) const {return !(bigInteger < *this); // a<=b等价于b不小于a}bool operator>=(const BigInteger &bigInteger) const {return !(*this < bigInteger); // a>=b等价于a不小于b}bool operator!=(const BigInteger &bigInteger) const {return *this < bigInteger || bigInteger < *this; // a不等于b等价于a大于b或者小于b}bool operator==(const BigInteger &bigInteger) const {return !(*this < bigInteger) && !(bigInteger < *this); // a等于b等价于a既不大于b也不小于b}
巧妙