1. 创作前
1.1 关于C++
C++本身有内建的整数类型,但这些类型能表示的数的范围非常有限,比如 unsigned long long
只能表示0~18446744073709551615的数;
1.2 关于网络上的高精度整数类
CSDN上有很多创作者开发的高精度整数类,但是大多数都是用一个char型数组存储每一位数字(甚至有的用整型数组存储每一位数字),但这种存储方式浪费空间,因为一个char型的变量有256种状态,而你用了能表示256种状态的char只表示了10种状态(
1.3 著名的库
1.3.1 Java BigInteger
BigInteger是JDK里自带的高精度整数类,相信写过Java的小伙伴们都不陌生吧~
1.3.2 libgmp
GMP(The GNU Multiple Precision Arithmetic Library)又叫GNU多精度算术库,是一个提供了很多操作高精度的大整数,浮点数的运算的算术库,主要应用领域是密码学的应用和研究、互联网安全应用、代数系统、计算代数研究等。
GMP是个非常强大的库,提供了很多高效的算法,并且进行了目标机器优化!
所以一般来说我们写的程序运行速度是远远不如GMP的。。。
1.4 创作目的
- 提供一个小巧到可以随意嵌入你的课设的较完整的高精度整数实现
- 锻炼写项目能力和debug能力
- 了解并运用与高精度整数相关的算法
2. 开发环境和项目搭建
2.1 开发环境
操作系统:Windows 11
IDE:CLion 2023.1
代码编写辅助工具:Code Whisperer
2.2 项目搭建
- 打开CLion,选择文件-新建-项目,如图所示
C++标准选择C++23,因为可能会用到C++新标准的东西
- 在项目面板上任意位置右键,新建 - C++类,类的名称填iBigInteger,别的保持默认值
3. 正式编写代码
3.1 数的存储方式
在Java BigInteger中,数以二进制的形式存储在int型数组中,每个int存储大整数的32个二进制位,这样可以保证内存空间被充分利用;
在我们的iBigInteger中,沿用这种做法,但是我们用了更方便功能更全的std::vector代替数组(不用担心std::vector的运行时开销比数组大,因为C++有个零开销抽象原则;
另外,用一个bool型变量存储数的符号,即这个数是正数还是负数;
因为有了单独的“符号位”,所以存储在vector中的数都是正的,所以vector的类型参数为unsigned int;
综上所述,class iBigInteger
中所有的数据成员有:
- std::vector ,命名为v;
- bool,命名为_flag;
#ifndef IBIGINTEGER_DEMO_IBIGINTEGER_H
#define IBIGINTEGER_DEMO_IBIGINTEGER_H
#include <vector>
class iBigInteger {
std::vector<unsigned> v;
bool _flag;
};
#endif //IBIGINTEGER_DEMO_IBIGINTEGER_H
3.2 构造函数
3.2.1 iBigInteger()
符号为true
,v
中构造一个元素0;
iBigInteger::iBigInteger() {
v.emplace_back(0);
_flag = true;
}
3.2.2 iBigInteger(int)
和iBigInteger(unsigned int)
unsigned int
:因为vector中的元素恰好是unsigned int
,所以_flag
为true
,v
中构造一个元素,它的值等于参数;iBigInteger::iBigInteger(unsigned i) { v.emplace_back(i); _flag = true; }
int
:如果参数大于0,则v
中构造一个元素,它的值等于参数,同时_flag
为true
;如果参数小于0,则v
中构造一个元素,它的值等于参数的相反数,同时_flag
=false
;iBigInteger::iBigInteger(int i) { v.emplace_back(i > 0 ? i : -i); if (i > 0) _flag = true; else _flag = false; }
3.2.3 iBigInteger(long long)
和iBigInteger(unsigned long long)
unsigned long long
(设形参名为i):_flag
为true;- 如果参数=0,则
v
中构造0; - 如果参数不等于0:
v
中构造一个数,值等于i&4294967295(即i的最后32位)- 如果i>>32(i的前32位)不等于0,则
v
中构造第二个数,值等于i>>32
iBigInteger::iBigInteger(unsigned long long i) { if (i == 0) { v.emplace_back(0); } else { v.emplace_back(i & (unsigned) 0xffffffff); if ((i >> 32) > 0)v.emplace_back(i >> 32); } _flag = true; }
long long
:(设形参名为i):- 如果参数=0,
_flag
为true
,v
中构造0; - 如果参数>0,
_flag
为true
;v
中构造一个数,值等于i&4294967295(即i的最后32位)- 如果i>>32(i的前32位)不等于0,则
v
中构造第二个数,值等于i>>32
- 如果参数<0,
_flag
为false
;- 建立一个临时变量temp,等于
i
的相反数; v
中构造一个数,值等于temp&4294967295(即i的最后32位)- 如果temp>>32(i的前32位)不等于0,则
v
中构造第二个数,值等于temp>>32
- 建立一个临时变量temp,等于
iBigInteger::iBigInteger(long long i) { if (i == 0) { v.emplace_back(0); } if (i > 0) { v.emplace_back(i & (unsigned) 0xffffffff); int n = (int) (i >> 32); if (n > 0)v.emplace_back(n); _flag = true; } else { long long temp = -i; v.emplace_back(temp & (unsigned) 0xffffffff); int n = (int) (temp >> 32); if (n > 0)v.emplace_back(n); _flag = false; } }
- 如果参数=0,
3.2.4 iBigInteger(const iBigInteger&)
没啥好说的,v
复制一份,_flag
复制一份……
iBigInteger::iBigInteger(const iBigInteger &i) {
v = i.v;
_flag = i._flag;
}
3.2.5 iBigInteger::iBigInteger(const std::string &s, int radix)
算是挺复杂的一个东西吧……
3.2.5.1 处理字符串
我们的字符串里面可能有一些其他字符,所以要对它进行一些处理,把符合条件的字符转移到新的字符数组内,方便后续的构造;
- 先获取字符串长度:
const char *basicString = s.c_str(); size_t stringLength = std::strlen(basicString);
- 判断数的正负:
判断原字符串的第一个字符:如果为'-'
,则符号为false;否则符号为truechar firstChar = basicString[0]; if (firstChar == '-') { _flag = false; } else { _flag = true; }
- 构建新字符数组
dealtString
:
新字符数组的长度等于原字符串的长度;char *dealtString = new char[stringLength]; for (int i = 0; i < stringLength; i++) dealtString[i] = 0;
- 辅助方法
accordWithRadix(int c, int radix)
:
根据习惯,我们平常使用2~16进制数,所以如果radix不在2和16之间直接构造0返回:
对于在2~10之间的进制radix,我们用0 ~ radix-1这radix个数字来表示数,如用’0’和’1’两个数字表示二进制数;
而对于11~16之间的进制,我们用a表示10,b表示11,c表示12,d表示13,e表示14,d表示15
所以对于小等于10的radix
,字符在’0’和’0’+radix-1范围内(c >= '0' && c < ('0' + radix)
)即为合法;
对于大于10小等于16的radix
,字符在’0’和’9’范围内或’a’到’a’+radix-11范围内(c >= '0' && c <= '9' || c >= 'a' && c < ('a' + radix - 10)
)合法;bool iBigInteger::accordWithRadix(int c, int radix) { if (radix <= 10) { return c >= '0' && c < ('0' + radix); } else if (radix <= 16) { return c >= '0' && c <= '9' || c >= 'a' && c < ('a' + radix - 10); } return false; }
- 转移字符:
为了方便后续转二进制,我们将字符串内的合法字符倒序塞入dealtString
中:
新建变量dealtLength
表示已经塞进dealtString
的字符个数;
当合法字符为’0’~‘9’时,塞入原字符就好;
当合法字符为’a’~'f’时,做一下特殊处理:
因为0~9的ASCII为48~57,所以我们希望表示10的’a‘被处理后的字符所对应的ASCII为58,同理’b‘~‘f’分别为59~63,这样的话每个存储在dealtString
里面的字符减去’0’就是数字所代表的真实值,便于运算;
而’a’的ASCII为97,所以当字符为‘a’~‘f’时,把字符减去97再加58(冒号的ASCII为58),[97,102]就被映射到了[58,63]
最后在dealtString
末尾填’\0’;int dealtLength = 0; for (int i = (int) stringLength - 1; i >= 0; i--) { if (accordWithRadix(basicString[i], radix)) { if (basicString[i] >= '0' && basicString[i] <= '9') { dealtString[dealtLength++] = basicString[i]; } if (basicString[i] >= 'a' && basicString[i] <= 'f') { dealtString[dealtLength++] = (char) (basicString[i] - 'a' + ':'); } } } dealtString[dealtLength] = '\0';
3.2.5.2 构建
-
n进制转二进制的方法:
点我跳转 -
辅助方法
check0(const char *str, int length)
:
检验在依次除以2后,dealtString
里面的所有字符是否均为0;
因为dealtLength
是一个字符数组,在传参时会隐式转换为指向首字符的指针,所以还要传一个长度;bool iBigInteger::check0(const char *str, int length) { for (int i = length - 1; i >= 0; i--) { if (str[i] != '0') { return false; } } return true; }
-
辅助方法
getEvenOrOdd()
:
检验除以2的余数;
没什么好说的,因为字符串是倒置的,所以判断第一个字符所表示的数字除以2的余数就好;bool iBigInteger::getEvenOrOdd(const char *str) { return (str[0] - '0') % 2 == 0; }
因为 ‘0’=48是偶数,所以可以改为
bool iBigInteger::getEvenOrOdd(const char *str) { return str[0] % 2 == 0; }
-
辅助方法
divideBy2()
:
把dealtString
所表示的数除以2;void iBigInteger::divideBy2(char *str, int radix) { bool carry = false; for (int i = (int) strlen(str) - 1; i >= 0; --i) { int temp = str[i] - '0'; str[i] = (char) ((str[i] - '0' + (carry ? radix : 0)) / 2 + '0'); carry = false; if (temp % 2 == 1) carry = true; } }
-
将二进制数位填充到
v
中:
创建变量alreadyInputDigit
,表示已经存储在v
中的二进制位个数;v.emplace_back(0); long long alreadyInputDigit = 0; while (!check0(dealtString, dealtLength)) { v[alreadyInputDigit / 32] += (getEvenOrOdd(dealtString) ? 0 : 1) << (alreadyInputDigit % 32); alreadyInputDigit++; divideBy2(dealtString, radix); if (alreadyInputDigit % 32 == 0 && !check0(dealtString, dealtLength)) { v.emplace_back(0); } }
下面给出全部的构造函数代码:
iBigInteger::iBigInteger(const std::string &s, int radix) {
if (radix < 2 || radix > 16) {
v.emplace_back(0);
_flag = true;
return;
}
const char *basicString = s.c_str();
size_t stringLength = std::strlen(basicString);
char firstChar = basicString[0];
if (firstChar == '-') {
_flag = false;
} else {
_flag = true;
}
char *dealtString = new char[stringLength];
for (int i = 0; i < stringLength; i++)
dealtString[i] = 0;
int dealtLength = 0;
for (int i = (int) stringLength - 1; i >= 0; i--) {
if (accordWithRadix(basicString[i], radix)) {
if (basicString[i] >= '0' && basicString[i] <= '9') {
dealtString[dealtLength++] = basicString[i];
}
if (basicString[i] >= 'a' && basicString[i] <= 'f') {
dealtString[dealtLength++] = (char) (basicString[i] - 'a' + ':');
}
}
}
dealtString[dealtLength] = '\0';
v.emplace_back(0);
int alreadyInputDigit = 0;
while (!check0(dealtString, dealtLength)) {
v[alreadyInputDigit / 32] += (getEvenOrOdd(dealtString) ? 0 : 1) << (alreadyInputDigit % 32);
alreadyInputDigit++;
divideBy2(dealtString, radix);
if (alreadyInputDigit % 32 == 0 && !check0(dealtString, dealtLength)) {
v.emplace_back(0);
}
}
delete[] dealtString;
}
4. 下期预告
重载运算符:operator+
operator-
and so on…
5. github存储库
点击这里
求star!!!