高精度算法基础,实战与进阶
高精度算法的本质就是用数组模拟超大数据的计算,用算法模拟进位操作。
一,高精度算法的基础,初始化函数。
c++
int init(int a[], string &s) {
int len = s.size();
for (int i = 0; i < len; i++) {
a[i] = s[len - 1 - i] - '0';
}
return len;
}
先将超大数作为字符串输入,然后将大数由低位到高位存储到int数组中。然后将数组长度返回,这里可以将string作为引用传递或在初始化函数中声明和赋值。
二,整数高精度算法
1,高精度加法
其实我们要进行高精度加法,一般要准备三个数组,两个存储加法的两个参数,一个数组存储结果。我们由小到大依次将两个参数数组对应的值加到结果数组中,如果结果数组满足进位条件,则让结果数组的下一位+1...,由于整数数组自动初始化为0值,最后要进行消0操作,去除没有用到的数组中的0。最后从高位向低位输出。
c++
int hadd() {
int lena = init(a);
int lenb = init(b);
int lenc = max(lena, lenb);
for (int i = 0; i < lenc; i++) {
c[i] += a[i] + b[i];
//进位
if (c[i] >= 10) {
c[i + 1]++;
c[i] %= 10;
}
}
//加的时候可能会进位,减的时候只可能会减小。
while (c[lenc] == 0 && lenc > 0) {
lenc--;
}
for (int i = lenc; i >= 0; i--) {
cout << c[i];
}
return 0;
}
2,高精度减法
减法的思路和加法类似,开始前需要进行大小判断,如果减法的结果是负数要加"-"。借位操作可以让结果数组的下一位-1。
c++
int hsub() {
string s1, s2;
int lena = init(a, s1);
int lenb = init(b, s2);
int lenc = max(lena, lenb);
if (lena < lenb || (lena == lenb && s1 < s2)) {
swap(a,b);
cout<<"-";
}
for (int i = 0; i < lenc; i++) {
c[i] += a[i] - b[i];
if (c[i] < 0) {
c[i + 1]--;
c[i] += 10;
}
}
//因为减法的最高位不会扩大,所以不用判断结果数组的lenc位上不用进行除0判断。
lenc--;
while (c[lenc] == 0 && lenc > 0) {
lenc--;
}
for (int i = lenc; i >= 0; i--) {
cout << c[i];
}
return 0;
}
3,高精度乘法
乘法的实现模拟了列竖式的做法,先用两层循环处理需要的所有"下标",之后再逐一进行进位操作。
c++
int hmuti() {
int lena = init(a);
int lenb = init(b);
int lenc = lena + lenb;
for (int i = 0; i < lena; i++) {
for (int j = 0; j < lenb; j++) {
c[i + j] += a[i] * b[j];
}
}
for (int i = 0; i < lenc; i++) {
if (c[i] >= 10) {
c[i + 1] += c[i] / 10;
c[i] %= 10;
}
}
while (c[lenc] == 0 && lenc > 0) {
lenc--;
}
for (int i = lenc; i >= 0; i--) {
cout << c[i];
}
return 0;
}
4,高精度除法
高精度除法只能计算一个超大数除以低精度数字,计算过程类似于列式除法。
c++
int hd() {
string s1;
cin >> s1 >> b; // 输入被除数和除数
int lena = init(a, s1); // 初始化被除数
int lenc = 0; // 结果的位数
int x = 0; // 余数
// 从最高位开始逐位计算
for (int i = lena - 1; i >= 0; i--) {
x = x * 10 + a[i]; // 当前位的值加上余数
c[lenc++] = x / b; // 计算当前位的商
x %= b; // 更新余数
}
// 去除前导零
int start = 0;
while (start < lenc && c[start] == 0) {
start++;
}
// 输出结果
if (start == lenc) {
cout << "0"; // 如果结果是0
}
else {
for (int i = start; i < lenc; i++) {
cout << c[i];
}
}
cout << endl;
return 0;
}
三,高精度算法实战:阶乘之和
题源:洛谷P1009
描述:用高精度计算出 S =1!+2!+3!+⋯+n !(n≤50)。
其中 !
表示阶乘,定义为 n !=n ×(n −1)×(n−2)×⋯×1。例如,5!=5×4×3×2×1=120。
这是一道纯coding问题,可以作为coding练习。
c++
int n;
int cur[90], last[90], nex[90], result[90];
int lcur, llast = 1, lnext = 0, len_ans, fsize = 0;
int main() {
cin >> n;
last[0] = 1; // 初始化
for (int i = 1; i <= n; i++) { // 计算 i 的阶乘
memset(nex, 0, sizeof(nex)); // 初始化 nex 数组
lcur = 0; // 初始长度
int p = i; // 1 - n 每个数字的开端
while (p > 0) { // 把 i 存进 cur 数组(按位)
cur[lcur++] = p % 10;
p /= 10;
}
// 高精度乘法
for (int j = 0; j < lcur; j++) {
for (int k = 0; k < llast; k++) {
nex[j + k] += cur[j] * last[k];
}
}
lnext = lcur + llast - 1; // 更新 lnext
for (int j = 0; j < lnext; j++) { // 进位检查
if (nex[j] >= 10) {
nex[j + 1] += nex[j] / 10;
nex[j] %= 10;
}
}
if (nex[lnext]) {
lnext++; // 看最高位要不要进位
}
len_ans = llast;
llast = lnext;
fsize = max(fsize, lnext); // 更新 fsize
for (int k = 0; k < lnext; k++) {
last[k] = nex[k]; // 将 nex 数组的值复制到 last 数组
}
// 高精度加法
for (int j = 0; j < fsize; j++) {
result[j] += last[j];
if (result[j] >= 10) {
result[j + 1] += result[j] / 10;
result[j] %= 10;
}
}
if (result[fsize]) {
fsize++; // 如果最高位有进位,增加 fsize
}
}
while (fsize > 0 && result[fsize] == 0) {
fsize--; // 去掉前导零
}
for (int i = fsize; i >= 0; i--) {
cout << result[i]; // 倒序输出
}
return 0;
}
四,高精度算法扩展------带小数的高精度加减法。
1,高精度小数加法
我的思路是限定100位整数和5位小数,用数组将他们分区存储起来。并且优化了进位判断,将进位值设立为单独的变量,在下一次计算中更新,很好地连接了小数和整数部分。
c++
class Myadd {
private:
int a[106] = { 0 }, b[106] = { 0 }, c[106] = { 0 };
public:
void init(int num[], string& str) {
int len = str.size();
bool hasDot = false; //是否存在小数点
int dotpos = 0; //小数点在字符串中的位置
//查找小数点的位置
for (int i = 0; i < len; i++) {
if (str[i] == '.') {
dotpos = i;
hasDot = true;
break;
}
}
//处理整数部分
int index = 0;
for (int i = dotpos - 1; i >= 0; i--) {
num[index++] = str[i] - '0';
}
//处理小数部分
if (hasDot) {
//小数部分占用数组的100下标之后
index = 100;
for (int i = dotpos + 1; i < len; i++) {
num[index++] = str[i] - '0';
}
}
}
string add(string& str1, string& str2) {
init(a, str1);
init(b, str2);
//设置carry,处理进位,便于关系处理
int carry = 0;
//处理小数部分
for (int i = 105; i >= 100; i--) {
c[i] = a[i] + b[i] + carry;
carry = c[i] / 10;
c[i] %= 10;
}
for (int i = 0; i < 100; i++) {
c[i] = a[i] + b[i] + carry;
carry = c[i] / 10;
c[i] %= 10;
}
int zeroInt = 0;
for (int i = 99; i >=0; i--) {
if (c[i] == 0) {
zeroInt++;
}
else {
break;
}
}
int zeroFloat = 0;
for (int i = 104; i >= 100; i--) {
if (c[i] == 0) {
zeroFloat++;
}
else {
break;
}
}
string result;
if (carry > 0) {
result += to_string(carry);
}
//添加整数部分
for (int i = 99 - zeroInt; i >= 0; i--) {
result += to_string(c[i]);
}
if (result.empty()) {
result = "0";
}
//添加小数部分
result += ".";
for (int i = 100; i < 105 - zeroFloat; i++) {
result += to_string(c[i]);
}
return result;
}
};
2,高精度小数减法
这里要注意两数大小判断,和是否借位的true/false判断。
c++
class Mysub {
private:
int a[105] = { 0 }, b[105] = { 0 }, c[105] = { 0 };
public:
void init(int num[], const std::string& str) {
int len = str.size();
bool hasDot = false; // 是否存在小数点
int dotpos = 0; // 小数点在字符串中的位置
// 查找小数点的位置
for (int i = 0; i < len; i++) {
if (str[i] == '.') {
dotpos = i;
hasDot = true;
break;
}
}
// 处理整数部分
int index = 0;
for (int i = dotpos - 1; i >= 0; i--) {
num[index++] = str[i] - '0';
}
// 处理小数部分
if (hasDot) {
// 小数部分占用数组的100下标之后
index = 100;
for (int i = dotpos + 1; i < len; i++) {
num[index++] = str[i] - '0';
}
}
}
bool isGreaterOrEqual(const std::string& str1, const std::string& str2) {
// 去掉小数点
std::string s1 = str1, s2 = str2;
s1.erase(std::remove(s1.begin(), s1.end(), '.'), s1.end());
s2.erase(std::remove(s2.begin(), s2.end(), '.'), s2.end());
// 补齐长度
while (s1.size() < s2.size()) s1 = "0" + s1;
while (s2.size() < s1.size()) s2 = "0" + s2;
return s1 >= s2;
}
std::string sub(const std::string& str1, const std::string& str2) {
bool isNegetive = false;
if (!isGreaterOrEqual(str1, str2)) {
isNegetive = true;
init(a, str2);
init(b, str1);
}
else {
init(a, str1);
init(b, str2);
}
// 判断是否借位
bool borrow = false;
// 处理小数部分
for (int i = 104; i >= 100; i--) {
c[i] = a[i] - b[i] - borrow;
if (c[i] < 0) {
c[i] += 10;
borrow = true;
}
else {
borrow = false;
}
}
// 处理整数部分
for (int i = 0; i < 100; i++) {
c[i] = a[i] - b[i] - borrow;
if (c[i] < 0) {
c[i] += 10;
borrow = true;
}
else {
borrow = false;
}
}
// 去除整数部分前导零
int zeroInt = 0;
for (int i = 99; i >= 0; i--) {
if (c[i] == 0) {
zeroInt++;
}
else {
break;
}
}
// 去除小数部分后导零
int zeroFloat = 0;
for (int i = 104; i >= 100; i--) {
if (c[i] == 0) {
zeroFloat++;
}
else {
break;
}
}
std::string result;
if (isNegetive) {
result += "-";
}
// 添加整数部分
for (int i = 99 - zeroInt; i >= 0; i--) {
result += to_string(c[i]);
}
if (result.empty()) {
result = "0";
}
// 添加小数部分
result += ".";
for (int i = 100; i < 105 - zeroFloat; i++) {
result += to_string(c[i]);
}
return result;
}
};
五,结语
高精度算法难不在于思路,而在于代码,很多时候能大概想到怎么做,但就是敲不出来,这可能就是所谓的coding能力的差异吧。
相关代码上传至github仓库:github.com/kair998/Alg...