四位压缩高精度学习笔记
最近又学习了很多关于高精度的知识,也学习了C++的重载运算符等知识,这里根据高精度模板写下非负整数的四位压缩一些模板函数。
一、什么是四位压缩?
首先简单说一下高精度,就是int long
等整数类型有上限,而double float
等小数类型又会有精度丢失。高精度就是拓展内存来存更大范围的无精度丢失的数(大多记录整数),这个更详细的概念可以去查查。
那么我们怎么拓展内存呢?有个很常用的方法就是开一个数组,比如说int a[25]
,在每个单元存放
0
−
9
0-9
0−9的整数,它就可以存放
25
25
25位,这个范围已经超越long long
类型,就可以满足一些需求了。
既然我们可以开数组来储存数字了,还加个这个四位压缩是个什么东东?别急,你看,一个int
类型有
4
4
4字节
32
32
32位,可以存
2
31
−
1
2^{31}-1
231−1大小的数,为
1
0
9
10^9
109数量级,你就用来存放
0
−
9
0-9
0−9是不是有点浪费了呢,我们能不能有效利用这些空间呢?
这就是接下来所讲的四位压缩,就是在一个int
单元里存放四位数字,即
0
−
9999
0-9999
0−9999(负数也可以存,这里先不考虑了)。为什么一定是四位,不能是五位吗?一个int
的最大数量级为
1
0
9
10^9
109,你如果开五位的话,在乘法运算中,比如
99999
×
99999
99999\times99999
99999×99999,这一定是超限的,所以不能开五位,不然就会出错。
四位压缩有什么好处呢?我原先开的int a[25]
本来只能存到
1
0
24
10^{24}
1024数量级,但我现在就能存到
1
0
99
10^{99}
1099数量级,内存利用效率上有了很大的提升;而且在同等数量级的运算下,比方说
1
0
24
10^{24}
1024,使用四位压缩的加法就只用for
6
6
6次,而原来的高精度就需要for
24
24
24次,说不定少一点这些时间就能AC了。
二、板子
在C++里建立一个类
const int maxlen = 105, maxmod = 10000;
class bigint{
private:
//记录位数,这里是数组位数
int len;
//一个数组,放在动态内存里防止爆空间
vector<int> num;
public:
//默认构造函数
bigint();
//从long long读入
bigint(long long x);
//拷贝构造函数
bigint(const bigint& other);
//析构函数,做题时没啥用
~bigint();
//重载赋值
bigint& operator=(const bigint& other);
//重载等于
friend bool operator==(const bigint& a, const bigint& b);
//重载大于
friend bool operator>(const bigint& a, const bigint& b);
//重载小于
friend bool operator<(const bigint& a, const bigint& b);
//重载加法
friend bigint operator+(const bigint& a, const bigint& b);
//重载加等
friend bigint& operator+=(bigint& a, const bigint& b);
//重载乘法
friend bigint operator*(const bigint& a, const bigint& b);
//重载乘等
friend bigint& operator*=(bigint& a, const bigint& b);
//重载输出流
friend ostream& operator<<(ostream& out, const bigint& b);
};
下面我们一个个来看这些函数:
重载输出流
ostream& operator<<(ostream& out, const bigint& b) {
out << b.num[b.len];//头部先出,没有多余的0
for (int i = b.len - 1; i >= 1; i--) {
if (b.num[i] == 0) {//如果是0,4位全是0
out << "0000";
continue;
}
for (int k = 10; k * b.num[i] < maxmod; k *= 10)//循环,少几个0就出几个
out << '0';
out << b.num[i];
}
return out;
}
其实这个输出是四位压缩的重头,四位压缩与普通高精度的区别主要在这里,就是得判断出几个0,其它的地方其实很多是将进位的10换成maxmod
。
默认构造函数
bigint::bigint() : len(1), num(maxlen, 0) {}
默认长度为1,否则没有输出;要开长度可以自行调整maxlen
。
这里试一下默认构造函数的输出:
int main() {
bigint a;
cout << a;
return 0;
}
输出:
0
因为a中num
和len
都属于private
,不能直接访问,可以将它们调到public
里就能使用vector
的成员函数了。
构造函数2
bigint::bigint(long long x) : num(maxlen, 0) {
if (x == 0) {
len = 1;
}
else {
len = 0;
while (x) {
num[++len] = x % maxmod;
x /= maxmod;
}
}
}
其实本来没有加x==0
的判定的,在做题的时候用了赋值重载比如x=0
,它自动就会设置长度为1,后面查出来是这的问题。
样例:
int main() {
bigint a(123);
cout << a;
return 0;
}
输出:
123
拷贝构造函数
bigint::bigint(const bigint& other) : len(other.len), num(other.num) {}
样例:
int main() {
bigint a(123);
bigint b(a);
cout << b;
return 0;
}
输出:
123
重载赋值
bigint& bigint::operator=(const bigint& other) {
if (this == &other) return *this;//如果是自己的话就不用重复操作了
len = other.len;
num = other.num;
return *this;
}
有了重载赋值之后,就会有很有意思的事情了,比如
int main() {
bigint a = 123;
cout << a;
return 0;
}
输出:
123
你可能会好奇,这个赋值怎么能通过呢?一面是bigint
,另一面是int
,怎么回事?这里有两个过程,一个是int
到long long
的转化,然后就是前面的构造函数1发挥作用了,这里临时生成了一个bigint
的数据,然后赋值给了a。
重载等于
bool operator==(const bigint& a, const bigint& b) {
if (a.len != b.len) return 0;
for (int i = a.len; i > 0; --i) {
if (a.num[i] != b.num[i]) return 0;
}
return 1;
}
样例:
int main() {
bigint a = 123, b = 123;
cout << (a == b);
return 0;
}
这里有符号优先级问题需要注意一下.
输出:
1
重载大于
bool operator>(const bigint& a, const bigint& b) {
if (a.len != b.len) return a.len > b.len;
for (int i = a.len; i > 0; --i) {
if (a.num[i] != b.num[i]) return a.num[i] > b.num[i];
}
return 0;
}
样例:
int main() {
bigint a = 125, b = 123;
cout << (a > b);
return 0;
}
输出:
1
重载小于
bool operator<(const bigint& a, const bigint& b) {
return b > a;
}
实际上调用大于就可以了,但是C++的面向对象函数比如要使用max(type,type)
还是需要给出小于的定义的.
还有大于等于就是:!(a<b)
;
小于等于:!(a>b)
。
重载加法
bigint operator+(const bigint& a, const bigint& b) {
bigint res;
res.len = max(a.len, b.len);
int carry = 0;
for (int i = 1; i <= res.len; ++i) {
res.num[i] = a.num[i] + b.num[i] + carry;
carry = res.num[i] / maxmod;
res.num[i] %= maxmod;
}
if (carry) res.num[++res.len] = carry;
return res;
}
这里可以发现,比起正常的计算,就是将10替换成了maxmod
。
样例:
int main() {
bigint a = 125, b = 123;
cout << a + b;
return 0;
}
输出:
248
重载加等
bigint& operator+=(bigint& a, const bigint& b) {
a = a + b;
return a;
}
重载乘法
bigint operator*(const bigint& a, const bigint& b) {
bigint res;
res.len = a.len + b.len - 1;//设置最高位
for (int i = 1; i <= a.len; ++i) {//轮换着加乘
for (int j = 1; j <= b.len; ++j) {
res.num[i + j - 1] += a.num[i] * b.num[j];
}
}
for (int i = 1; i <= res.len; ++i) {//考虑进位
res.num[i + 1] += res.num[i] / maxmod;
res.num[i] %= maxmod;
}
while (res.num[res.len + 1] > 0) {//进位
++res.len;
res.num[res.len + 1] += res.num[res.len] / maxmod;
res.num[res.len] %= maxmod;
}
while (res.len > 1 && res.num[res.len] == 0) --res.len;//超出的部分删去
return res;
}
样例:
int main() {
bigint a = 125, b = 123;
cout << a * b;
return 0;
}
输出:
15375
重载乘等
bigint& operator*=(bigint& a, const bigint& b) {
a = a * b;
return a;
}
这些就是相关的模板了。少了减法、负数、除法等,但是感觉现在做题还基本用不太到,这些部分就留给读者了!
最后的整个可复制模板:
const int maxlen = 105, maxmod = 10000;
class bigint{
private:
int len;
vector<int> num;
public:
bigint();
bigint(long long x);
bigint(const bigint& other);
~bigint();
bigint& operator=(const bigint& other);
friend bool operator==(const bigint& a, const bigint& b);
friend bool operator>(const bigint& a, const bigint& b);
friend bool operator<(const bigint& a, const bigint& b);
friend bigint operator+(const bigint& a, const bigint& b);
friend bigint& operator+=(bigint& a, const bigint& b);
friend bigint operator*(const bigint& a, const bigint& b);
friend bigint& operator*=(bigint& a, const bigint& b);
friend ostream& operator<<(ostream& out, const bigint& b);
};
bigint::bigint() : len(1), num(maxlen, 0) {}
bigint::~bigint() {}
bigint::bigint(long long x) : num(maxlen, 0) {
if (x == 0) {
len = 1;
}
else {
len = 0;
while (x) {
num[++len] = x % maxmod;
x /= maxmod;
}
}
}
bigint::bigint(const bigint& other) : len(other.len), num(other.num) {}
bigint& bigint::operator=(const bigint& other) {
if (this == &other) return *this;
len = other.len;
num = other.num;
return *this;
}
bool operator==(const bigint& a, const bigint& b) {
if (a.len != b.len) return 0;
for (int i = a.len; i > 0; --i) {
if (a.num[i] != b.num[i]) return 0;
}
return 1;
}
bool operator>(const bigint& a, const bigint& b) {
if (a.len != b.len) return a.len > b.len;
for (int i = a.len; i > 0; --i) {
if (a.num[i] != b.num[i]) return a.num[i] > b.num[i];
}
return 0;
}
bool operator<(const bigint& a, const bigint& b) {
return b > a;
}
bigint operator+(const bigint& a, const bigint& b) {
bigint res;
res.len = max(a.len, b.len);
int carry = 0;
for (int i = 1; i <= res.len; ++i) {
res.num[i] = a.num[i] + b.num[i] + carry;
carry = res.num[i] / maxmod;
res.num[i] %= maxmod;
}
if (carry) res.num[++res.len] = carry;
return res;
}
bigint& operator+=(bigint& a, const bigint& b) {
a = a + b;
return a;
}
bigint operator*(const bigint& a, const bigint& b) {
bigint res;
res.len = a.len + b.len - 1;
for (int i = 1; i <= a.len; ++i) {
for (int j = 1; j <= b.len; ++j) {
res.num[i + j - 1] += a.num[i] * b.num[j];
}
}
for (int i = 1; i <= res.len; ++i) {
res.num[i + 1] += res.num[i] / maxmod;
res.num[i] %= maxmod;
}
while (res.num[res.len + 1] > 0) {
++res.len;
res.num[res.len + 1] += res.num[res.len] / maxmod;
res.num[res.len] %= maxmod;
}
while (res.len > 1 && res.num[res.len] == 0) --res.len;
return res;
}
bigint& operator*=(bigint& a, const bigint& b) {
a = a * b;
return a;
}
ostream& operator<<(ostream& out, const bigint& b) {
out << b.num[b.len];
for (int i = b.len - 1; i >= 1; i--) {
if (b.num[i] == 0) {
out << "0000";
continue;
}
for (int k = 10; k * b.num[i] < maxmod; k *= 10)
out << '0';
out << b.num[i];
}
return out;
}