高精度运算

  • 高精度的存储和输入输出
    • 单精度与高精度
    • 存储
    • 输入
    • 输出
  • 高精度运算
    • 高精度加法
    • 高精度加法 --代码实现
    • 高精度减法
      • 使用小端序的好处
      • 高精度减法 - 代码实现
    • 高精度乘法
    • 高精度乘法——代码实现

C++自己本身有一些内置的整数类型,比如short、int、long long等。但它们的最大值都有一个限制,即便是unsigned long long能存储的最大数也只有2^{64} - 1264−1。

  • 在使用整数的过程中,并不是只需要把它每一位存下来就好了,还需要:
  • 读入用户的输入,
  • 还要进行整数之间加减乘除的运算
  • 还要将计算结果输出给用户看。

只有当这些基本操作都实现了,才算真真正正实现了一个完整的大整数操作,所有相关大整数的操作本质就是模拟。

高精度的存储和输入输出

单精度与高精度

  • 单精度:能用一个内置类型存储的整数。
  • 高精度:不能用内置类型存储的大整数,通常用数组存储每一个数位。

存储

当把大整数存储在数组里时,有两种存储方式可供选择:

  • 大端序:数字的高位在地址的低位(也就是和打印顺序一致)。以2021为例:

  • 小端序:数字的低位在地址的低位。

通常进行高精度计算时,采取小端序方式,主要的目的是为了方便模拟竖式计算。

输入

数字很长以至于无法用C++内置类型读入的话,我们唯一的选择:

  • 由字符数组表示的字符串

怎样将字符串转化成高精度整数类型?

考虑到我们的数字存储方向和打印顺序的不同,我们需要将字符串里的元素“倒着”存进数组。

#include <bits/stdc++.h>
using namespace std;
char str[111];
int digits[110];
int len;
int main() {cin >> str;// 获取高精度整数长度int len = strlen(str);    for (int i = 0; i < len; ++i)// 将字符转换成数字,倒着存进数位数组digits[i] = str[len - i - 1] - '0';        }

输出

上文提到,我们输入没有选择和“打印顺序”一致的数据存储方法,而是相反。

所以,如果我们想按照“打印顺序”输出的话,就需要将数组倒过来,也就是从后往前输出

因此,这也是为什么我们需要存储数字长度。

#include <bits/stdc++.h>
using namespace std;
int digits[10] = {1, 2, 0, 2}; // 2021
int len = 4;
int main() {for (int i = len - 1; i >= 0; --i) cout << digits[i];cout << endl;
}

高精度运算

高精度加法

为了实现高精度的四则运算,我们需要模拟竖式操作。

在模拟的过程中,不但要维护数位数组上面的信息,还要更新数位数组的长度。

所以,以高精度加减法为例,我们分别从数位操作维护长度两个方面来介绍。

  • 数位操作

加法竖式计算的法则:

从低位开始,逐位相加;

若该位相加结果超过10,需向下一位进位

上面的例子中,可以发现:

在计算数位数组的第i位时,需要将三个部分加在一起:

第一个加数的第i位,第二个加数的第i位,和第i-1位的进位。

  • 维护长度

从上例中可以发现,如果最高位出现进位,还需要将答案数位长度+1

高精度加法 --代码实现

代码实现主要分为三部分:数位操作维护长度输出

#include <bits/stdc++.h>
#define N 110
using namespace std;
// 这里我们采用直接赋值的形式初始化,按照小端序的方式存储
// 所以这里加数a = 368, 加数b = 997,
int a_digits[N] = {8, 6, 3}, a_len = 3;
int b_digits[N] = {7, 9, 9}, b_len = 3;
int ans_digits[N], ans_len;
int main() {// 1. 数位操作: // 先将答案的长度赋值成和更大的加数一样的长度。// 然后再从低位向高位开始,逐位相加,回顾上文,每一位相加的部分包括两个加数的对应位和低位进位。// 相加时,大于等于10的部分要从这一位去掉然后进位到高位,分别对应%10和/10的操作ans_len = max(a_len, b_len);     // 初始长度int k = 0;                      // 记录进位的变量for (int i = 0; i < ans_len; ++i) {// 假设a_len > b_len,这里需要保证b[b_len]到b[a_len - 1]的位置都是0,否则可能会出错。ans_digits[i] = a_digits[i] + b_digits[i] + k; // 相加计算k = ans_digits[i] / 10;     // 更新进位ans_digits[i] %= 10;}// 2. 维护长度: // 因为两个数相加,最高位仍然可能产生进位,// 在这种情况下,需要我们将答案的长度+1if (k) ans_digits[ans_len++] = k; // 最高位进位// 3. 输出// 按照打印顺序输出,从高位到低位。for (int i = ans_len - 1; i >= 0; --i) cout << ans_digits[i];cout << endl;return 0;
}

高精度减法

  • 数位操作

根据减法竖式计算的规则:

从低位开始,逐位相减;

若该位不够减,需向下一位借位,并且借一当十;

上面的例子中,可以是:

i位的结果等于被减数第i位减去减数第i位和低位的借位。

  • 维护长度

在两个数相减时,若令初始长度为被减数的长度。最终计算的差的长度,和被减数相比,很可能变小。我们看以下几个特殊情况:

可以看到,

  1. 因为最终差的长度可能减小很多,所以需要用循环来更新长度。

具体判断应该是,如果差的最高位是0,那么差的长度应该减小。

  1. 由被减数和减数相同这个例子可以看出,差的位数必须大于等于1

如果最终差的位数为1,即便是最高位等于0,那也不应该缩减长度。

使用小端序的好处

小端序的存储方式:数字的低位在地址的低位

由此,我们可以看到使用小端序的理由:

  1. 因为加法、减法以及后面介绍的乘法等,都是从低位算到高位。这样存储符合我们平时习惯的枚举顺序
  2. 因为数位计算结束后,需要更新数位数组的长度。把高位放在数组后面比较方便数组伸缩。

高精度减法 - 代码实现

总假设被减数大于等于减数

代码依旧分成数位操作维护长度输出三个部分。

#include <bits/stdc++.h>
#define N 110
using namespace std;
// 同样,采用小端序存储
int a_digits[N] = {8, 6, 3, 2}, a_len = 4;
int b_digits[N] = {7, 9, 9},   b_len = 3;int ans_digits[N], ans_len;
int main() {// 1. 数位操作// 依旧是从低位到高位开始逐位相减// 因为总假设a>=b,所以初始长度先设为a的长度// 考虑每一位,需要计算的部分是被减数的当前位,减去减数的当前位,再减去低位的借位// 如果上一步的计算得出当前位<0,那我们需要向高位借位,然后给当前位+10ans_len = a_len;  // 初始长度int k = 0;          // 维护借位for (int i = 0; i < ans_len; ++i) {ans_digits[i] = a_digits[i] - b_digits[i] - k;if (ans_digits[i] < 0) {k = 1;ans_digits[i] += 10;} else k = 0;   // 这里赋值成0很关键,而且容易遗漏}// 2. 维护长度// 想象一下,如果实际数字是1,但是长度记录是4的话,那么输出该数字结果将是0001,// 也就是出现了“前导0”,所以维护长度的目的是为了去掉前导0// 所以,我们用while循环实现这样的逻辑:只要最高位是0,我们就把位数缩小1位。// 但是需要注意,只有位数>1的时候才可以缩小,否则当保存的数字是0时,长度也会减为0.while (ans_len > 1 && !ans_digits[ans_len - 1]) // 只有长度大于1才可以去掉前导零--ans_len;// 3. 输出for (int i = ans_len - 1; i >= 0; --i) cout << ans_digits[i];cout << endl;return 0;
}

高精度乘法

  • 数位操作

高精度乘高精度会比之前复杂很多。其原因在于:

  1. 高精度加减法只需要第i位和第i位相加,答案仍然贡献给第i位;且第i可能产生对i + 1位的进位;
  2. 在高精度乘法中,第一个乘数的第i位,会和第二个乘数的每一位相作用。

所以,请思考下面的问题

第一个乘数的第i位和第二个乘数的第j位相乘的结果,会贡献给答案的第几位呢?(考虑从右向左0-based下标,比如,23中3是第0位)

通过观察上述竖式会发现,答案会贡献给i + j位。

当答案中的所有数位计算完毕以后:

  • 遍历每一个位置k,然后把>=10的部分进位到k + 1位。
  • 维护长度

    • 令初始长度为两个乘数长度之和

Tips:一个n位的整数和一个m位的整数相乘,结果最多为n + m位的整数,我们可以从99 x 999 = 98901得到验证

考虑到一个n位的整数和一个m位的整数相乘,结果最少为1位,比如1000 x 0 = 0

所以和减法一样,我们维护数组长度需要用while循环去掉前导零,并且只在数组长度大于1时进行。

高精度乘法 - 代码实现

高精度乘法——代码实现

代码分为数位操作统一进位维护长度输出四个部分。

#include <bits/stdc++.h>
#define N 110
using namespace std;
int a_digits[N] = {3, 2}, a_len = 2;
int b_digits[N] = {8, 6}, b_len = 2;
// int a_digits[N] = {0}, a_len = 1;
// int b_digits[N] = {9, 9}, b_len = 2;
int ans_digits[N * 2], ans_len;
int main() {// 1. 数位操作// 考虑到(a位数×b位数)最多得到(a + b)位数,所以我们设置答案初始长度为a + b。// 另外考虑到第i位×第j位会贡献到(i + j)位,所以,我们用累加的方式计算答案的每一位。// 值得注意的是,这里累加的结果可能>=10,所以按理说应该进位,但为了效率考虑,我们// 在后面统一维护进位,而不是一边加一边进。ans_len = a_len + b_len;      // 初始化长度for (int i = 0; i < ans_len; ++i) ans_digits[i] = 0; // 因为是不断累加的形式,所以要将范围内的元素初始化为0。for (int i = 0; i < a_len; ++i) for (int j = 0; j < b_len; ++j) ans_digits[i + j] += a_digits[i] * b_digits[j]; // ans的每一位更新都要使用累加的形式,这是因为对于ans的第k位,满足i + j == k的(i, j)很多,所以可能答案的第k位可能先后被更新很多次。// 2. 统一进位// 上一步提到,因为累加后得到的答案各个数位有可能>=10,所以要将其变成一个合法的高精度形式// 也就是说,要把>=10的部分进位到下一位。所以我们用类似于高精度加法的方法维护。// 每一位只需要将自己的值和低位的进位相加,然后把>=10的部分作为新的进位进到下一位。int k = 0;for (int i = 0; i < ans_len; ++i) {ans_digits[i] += k;k = ans_digits[i] / 10;ans_digits[i] %= 10;}// 3. 维护长度// 上面提到,(a位数×b位数)最多得到(a + b)位数// 但考虑一个非零整数和0相乘的情况,答案的长度很可能降为1。所以我们需要向减法一样更新长度。// 只有当长度仍然>1的时候,才需要去掉前导0while (ans_len > 1 && ans_digits[ans_len - 1] == 0) --ans_len;// 4. 输出for (int i = ans_len - 1; i >= 0; --i) cout << ans_digits[i];cout << endl;return 0;
}

C++算法(高精度算法)相关推荐

  1. 高精度算法(加减乘除取模(均可以处理负数))

    高精度算法 前言 大数加法 不可以处理负数的模板 可以处理负数 大数减法 两个数都是整数,且相减结果大于0 两个数都是正整数,相减结果可以是负数 两个数均可以是负数 高精度乘法 两个数均可以是负数 大 ...

  2. 高精度算法——高精度减法

    介绍: 高精度减法也同加法一样,也是用于位数太大的运算,给你一个十几位的数你可能会做直接开个long long 的数据类型就解决了,但是给你一个100位的呢,1000位的呢,开long long 也不 ...

  3. 高精度阶乘和 高精度算法(c语言)

    以前刷oj的时候 遇见一个题目 也就是输入一个数字 判断阶乘和 用高精度计算出S=1!+2!+3!+-+N!(N≤50),其中"!"表示阶乘, 例如:5!=5*4*3*2*1.输入 ...

  4. 【基础】(C语言)高精度算法

    文章目录 前言:为什么我们需要高精度算法? 一.高精度算法基础 二.高精度乘法 1.计算流程 2.完整代码 三.总结 前言:为什么我们需要高精度算法? 多数时候我们需要进行非常庞大的数字运算,其中涉及 ...

  5. c语言高精度算法(加法)

    1.为什么使用高精度算法啊? 在c语言中,int类型取值范围为(-2^31 ~ 2^31-1),而long long类型取值范围也才仅有(-2^63~ 2^63-1),在处理一些较大的数据时,显然仅靠 ...

  6. 基础算法-高精度乘法

    高精度算法 为什么要使用高精度算法 C++ 每一个变量都有自己的类型,每个类型都有自己的存储长度范围. 名称 关键字 字节 长度 短整型 short int 2 (-2的15次方)~(2的15次方-1 ...

  7. A.pro读算法の2:高精度算法

    1.1 描述 高精度算法,属于处理大数字的数学计算方法.在一般的科学计算中,会经常算到小数点后几百位或者更多,当然也可能是几千亿几百亿的大数字.一般这类数字我们统称为高精度数,高精度算法是用计算机对于 ...

  8. 高精度算法详解(蒟蒻,轻喷,欢迎指正)

    高精度算法详解 1.前言 2.内容 1.高精度加法 0.实现过程中需注意的细节 1.用普通数组实现 2.用结构体实现 3.vector数组(推荐-) vector (推荐S+) 3.重载+运算符 同时 ...

  9. 高精度算法(大整数的加减乘除运算)

    1.什么是高精度数 ​ 在一般的科学计算中,会经常算到小数点后几百位或者更多,当然也可能是几千亿几百亿的大数字.一般这类数字我们统称为高精度数,高精度算法是用计算机对于超大数据的一种模拟加.减.乘.除 ...

最新文章

  1. 傅里叶频域,复数域,冲激函数,香农采样(不介绍公式-只介绍是啥)另一种思维
  2. 博士后工作站来了!智源研究院获得正式授牌
  3. android的helloworld工程目录学习
  4. TextView显示颜色高亮的问题
  5. 什么是 Web 应用性能评测领域的 RAIL 模型
  6. eclipse插件开发_开发Eclipse插件
  7. mongodb java驱动_Java的MongoDB驱动及读写策略
  8. 在LoadRunner中查找和替换字符串
  9. Linux执行命令./command与直接输入命令的区别
  10. 注册(五)之请求处理
  11. 怎么修改图片大小尺寸?
  12. VUE2.X全教程--基础详解(二)
  13. 梦幻手游登录显示服务器爆满,梦幻西游手游服务器爆满怎么办
  14. BIOS知识枝桠—— Protocol
  15. 如何获取股票交易接口
  16. eigrp 扩散算法_EIGRP之DUAL(扩散更新算法)
  17. mogrt格式动态字幕模板安装和使用
  18. 【Python】窗口界面 按钮 文本框
  19. 移动端最常用的四个框架
  20. 关于RT thread系统节拍时钟的配置

热门文章

  1. 压电陶瓷薄膜材料介电性能检测
  2. 自旋锁是什么,是如何实现的?
  3. 用python实现西绪福斯黑洞(即123数字黑洞)的演示
  4. MySQL表的完整性约束
  5. Qt操作excel时文件路径问题
  6. [Test][心理测试]测试:《红楼梦》你是哪个角色?
  7. 棋牌软件被恶意攻击应该怎么办
  8. UI进阶之多线程(GCD)
  9. 工业级树莓派RevPi应用案例2:智能铆接工具
  10. C语言中 .h文件和.c文件的区别