文章目录

  • 前言
  • 一、大数加法
    • 1. 基本思想
    • 2. 代码实现
  • 二、大数减法
    • 1. 基本思想
    • 2. 代码实现
  • 三、大数乘法
    • 1. 基本思想
    • 2. 代码实现
  • 四、大数除法
    • 1. 基本思想
    • 2. 代码实现

前言

由于编程语言提供的基本数值数据类型表示的数值范围有限,不能满足较大规模的高精度数值计算,因此需要利用其他方法实现高精度数值的计算,于是产生了大数运算。

比如bc命令是任意精度计算器语言,通常在Linux下当计算器使用,它就可以进行大数的运算。

对于这种大数的计算,编程语言提供的基本数值数据类型已经无法进行存储,此时可以将这些大数存储到字符串当中,然后实现基于字符串的加减乘除运算即可。

一、大数加法

1. 基本思想

将两个字符串中的数相加其实非常简单,就像我们小学时列竖式计算一样。我们会从两个数字的最低位开始进行计算,如果低位相加时有进位,则会将该进位记录下来,下一次将两个数高一位对应的数字相加时就会将该进位一同加上。

2. 代码实现

//大数加法
string AddString(string num1, string num2)
{int end1 = num1.size() - 1, end2 = num2.size() - 1;string ret; //存储两个字符串相加后的结果int carry = 0; //进位(初始时进位设置为0)while (end1 >= 0 || end2 >= 0){//1、取出num1中本次待相加的数字int a = 0;if (end1 >= 0){a = num1[end1] - '0';end1--;}//2、取出num2中本次待相加的数字int b = 0;if (end2 >= 0){b = num2[end2] - '0';end2--;}//3、将这两个数字相加(注意加上进位)int sum = a + b + carry;//4、判断是否需要进位if (sum > 9){sum -= 10;carry = 1; //需要进位,将carry设置为1}else{carry = 0; //不需要进位,将carry设置为0}ret += (sum + '0');}if (carry != 0) //判断是否还需进位(可能两个数的最高位相加后会进位)ret += '1';reverse(ret.begin(), ret.end()); //将ret字符串进行反转return ret; //返回两个字符串相加后的结果
}

代码说明:

  • 在代码中,需要进位时直接将carry设置成了1,因为两个数相加后如果有进位,其进位就是1。
  • 如果将两个数的对应位相加后没有产生进位,需要及时将carry的值重新设置为0,防止对下一次相加造成影响。
  • 当对两个数中所有的位都进行相加操作后,最后还需要判断carry的值是否为1,如果carry的值为1,则还需要在结果中尾插一个1(这个1在进行字符串反转后也就是最高位的1)。
  • 由于我们是从最低位开始进行对应位的相加的,因此我们每得到一个位的结果就需要将其头插到ret字符串中,而头插时需要将字符串中所有的字符向后移动一位,时间复杂度是O(N)O(N)O(N)。因此我们这里选择先尾插,当需要返回最终结果时再进行一次字符串反转即可。

二、大数减法

1. 基本思想

将两个字符串中的数相减也是非常简单的,和字符串相加类似。也是从两个数字的最低位开始进行计算,如果低位相减时有借位,则会将该借位记录下来,下一次将两个数高一位对应的数字相减时就会将该借位一同减去。

2. 代码实现

//大数比较
int Cmp(string& num1, string& num2)
{if ((num1.size() > num2.size()) || (num1.size() == num2.size() && num1 > num2))return 1; //num1大于num2,返回1else if ((num1.size() < num2.size()) || (num1.size() == num2.size() && num1 < num2)) return -1; //num1小于num2,返回-1elsereturn 0; //num1等于num2,返回0
}
//大数减法
string SubString(string num1, string num2)
{//保证num1大于等于num2if (Cmp(num1, num2) == -1){return "-" + SubString(num2, num1); //num1小于num2,则返回num2-num1所得到的结果的负值}int end1 = num1.size() - 1, end2 = num2.size() - 1;string ret; //存储两个字符串相减后的结果int borrow = 0; //借位(初始时借位设置为0)while (end1 >= 0){//1、取出num1中本次待相减的数字int a = num1[end1] - '0';end1--;//2、取出num2中本次待相减的数字int b = 0;if (end2 >= 0){b = num2[end2] - '0';end2--;}//3、将这两个数字相减(注意减去借位)int differ = a - b - borrow;//4、判断是否需要进位if (differ < 0){differ = 10 + differ;borrow = 1; //需要借位,将borrow设置为1}else{borrow = 0; //不需要借位,将borrow设置为0}ret += (differ + '0');}reverse(ret.begin(), ret.end()); //将ret字符串进行反转//过滤掉ret字符串前面的'0'size_t pos = ret.find_first_not_of('0');if (pos == string::npos) //ret中全部为'0',则两个数相减后的结果为0{return "0";}return ret.substr(pos); //返回两个字符串相减后的结果
}

代码说明:

  • 与字符串相加时类似,当需要借位时直接将borrow设置成了1,因为两个数相减时如果需要借位,也只需要借一次就够了。
  • 如果将两个数的对应位相减时没有进行借位,对应也需要及时将borrow的值重新设置为0,防止对下一次相减造成影响。
  • num1的值小于num2时,其相减后的结果是一个负值,该负值的绝对值与num2-num1的值是相同的,因此我们可以选择重新调用SubString函数,得到num2-num1的值后,在该结果前面加上一个负号即可。
  • 由于我们保证了实际在进行相减操作时,num1的值是大于等于num2的值的,因此对两个数中所有的位都进行相减操作后,无需再判断borrow的值是否为1,此时borrow的值必然为0。
  • 与字符串相加时一样,为了避免每次插入字符的时间复杂度都是O(N)O(N)O(N),每次插入字符时也是进行的尾插,当需要返回最终结果时再进行一次字符串反转即可。

三、大数乘法

1. 基本思想

字符串相乘相对来说会难一点,但其原理还是和我们平时列竖式时是一样的。我们平时会将乘数的每一位与被乘数的每一位相乘,将相乘后的结果写到对应的位置,然后将这些乘积对应加起来就得到了两个数相乘后的结果。

实际我们在计算的过程中也可以先不进行进位,先将每位相乘后的乘积放到对应的位置,然后将对应位置的乘积加起来。

最后再对这个序列从低位到高位依次进行进位操作,最终也能够得到这两个数相乘后的结果。只不过我们平时列竖式计算时是时刻都在进行进位操作的,而我们现在统一将进位的过程放到了最后。

问题一:如果我们将这些乘积累加到vector当中,这个vector应该开辟多大的空间?

首先我们需要明确的是:如果被乘数num1的长度为m,乘数num2的长度为n,则它们乘积的长度为m+n-1m+n

证明如下:

  • 若num1和num2都取最小值,则num1=10m-1, num2=10n-1,那么它们的乘积就为10m+n-2,此时乘积的长度为m+n-1。
  • 若num1和num2都取最大值,则num1=10m-1, num2=10n-1,那么它们的乘积就为10m+n-10m-10n+1,该结果是小于10m+n而大于10m+n-1的,此时乘积的长度为m+n。

综上所述:长度分别为m和n的数相乘后,乘积的长度为m+n-1或m+n。

因此,如果我们要将这些乘积累加到vector当中,为了确保能够容纳得下这些乘积,这个vector的必须要能够存储m+n个元素。

问题二:乘数的每一位与被乘数的每一位相乘后的结果,到底应该累加到vector中的哪一个下标位置?

以图中的例子为例,这里的被乘数和乘数的长度都是3,因此vector的大小应该开辟为6,稍作观察可以看到,乘数的第i位与被乘数的第j位相乘后的乘积,应该累加到vector中下标为i+j+1的位置。

将vector从低位到高位进行进位操作后,最终vector当中存储的序列就是这两个数相乘后的结果,此时我们需要从vector的高位开始,将其一个个尾插到一个字符串中,最后返回的这个字符串即为字符串相乘后的字符串。

此时需要注意,两个数相乘后乘积的长度可能是m+n-1,因此vector中下标为0的位置可能未使用,所以在将vector当中的序列插入到字符串中时,需要先判断vector中下标为0的位置是否被使用,如果未被使用则从vector中下标为1的位置开始往后才算作有效序列。

2. 代码实现

//大数乘法
string MulString(string num1, string num2)
{if (num1 == "0" || num2 == "0") //两个操作数中有一个为0,则结果为0return "0";int m = num1.size(), n = num2.size();vector<int> arr(m + n, 0); //开辟数组arr的大小为m+n,并且全部初始化为0//1、取乘数的每一位与被乘数的每一位相乘,将结果累加到数组arr的对应下标位置for (int i = n - 1; i >= 0; i--) //取乘数的每一位{int a = num2[i] - '0';for (int j = m - 1; j >= 0; j--) //取被乘数的每一位{int b = num1[j] - '0';arr[i + j + 1] += a*b; //乘数第i位与被乘数第j位相乘后的结果累加到数组arr中下标为i+j+1的位置}}//2、从后往前对数组arr进行进位操作int end = m + n - 1;while (end > 0){arr[end - 1] += arr[end] / 10; //进位的值加到前一个位置arr[end] %= 10; //进位后剩下的值存放到当前位end--; //处理下一位}//3、依次将数据尾插到字符串ret当中string ret; //存放两个字符串相乘后的结果int flag = 1; //默认有效值从数组arr当中下标为1的位置开始if (arr[0] != 0)flag = 0; //若数组arr当中下标为0的位置的值不为0,则有效值从第0位开始for (int i = flag; i < m + n; i++){ret += (arr[i] + '0');}return ret; //返回两个字符串相乘后的结果
}

代码说明:

  • 若传入的两个操作数当中其中有一个为0,则相乘后的结果就是0,因此直接返回"0"即可。
  • 代码中定义vector时,将开辟的m+n个位置都先初始化为了0,因此最后在将vector当中的序列尾插到字符串时,可以通过判断vector中下标为0的位置是否为0,来得知该位置是否算作有效值。

四、大数除法

1. 基本思想

两个数相除的商值代表的是,被除数当中最多有多少个除数,而两个数相除的余数代表的是,被除数被分成一个个除数后剩下的不够再分出一个除数的值。比如456÷123456\div123456÷123,其中被除数456最多可以被分成3个123,此时分完后还剩下的87就不够再分出一个123了,因此456÷123=3...87456\div123=3...87456÷123=3...87。

当然,如果要求最终结果精确到小数点后的若干位,在余数不够一个除数的大小时,应该在余数后面补0,然后继续进行计算。

以结果当中的小数点为界限,可以将除法分为两个过程:

  • 计算小数点前面的数。
  • 计算小数点后面的数。

计算小数点前面的数

首先,如果被除数的位数小于除数的位数,那么结果中小数点前的值就是0了,计算后的余数就是被除数本身,该余数用于后续计算小数点后的值。但如果被除数的位数大于或等于除数的位数,那么此时就需要计算了。

回想我们平时列竖式计算时,如果除数的位数len小于被除数的位数,那么我们刚开始时是先只看被除数的前len位的,将被除数的前len位与除数进行除法运算得到一个商值,当余数不够时再将被除数后面的位补到余数后面,然后继续进行除法运算。

因此当两个数相除时,如果除数的位数len小于被除数的位数,那么我们应该先用被除数的前len位进行判断,此时被除数的前len位最多可以被分成几个除数,则说明应该商几,当余数不够时再将被除数后面的位依次添加到余数后面继续进行计算,直到被除数的所有位都被用完,此时得到的商序列就是小数点前的值,而最终剩下的余数就用于后续计算小数点后的值。

当我们要判断一个数a最多可以被分成多少个b时,实际上非常简单,我们只需要判断当前a的值是否大于等于b,如果是,则可以在a的基础上减去b,然后继续该判断。最终a最多可以被分成多少个b,也就却决于a执行多少次减b操作后是小于b的。因此字符串相除可以被转换成字符串相减。

按照上述方法得到小数点前的结果后,需要判断所得字符串的最高位是否为0,如果为0并且0的后面不是小数点,则需要将这个0过滤掉,比如200÷30=06.66200\div30=06.66200÷30=06.66,此时我们需要将前面的0过滤掉。但如果字符串的最高位为0,但是0后面是小数点,那么这个0不能被过滤,比如1÷3=0.331\div3=0.331÷3=0.33,此时的0不能被过滤。

计算小数点后面的数

计算小数点后面的数的方法与计算小数点前面的数的方法类似,只不过此时我们在余数后面补的就不是被除数后面的位了,而直接是0,补0后再继续进行计算。

但实际有些数相除永远除不尽,因此这里我们可以给函数设置一个参数n,表示要求计算结果保留到小数点后的第n位,此时我们就将补n次0后最终得到的值进行返回即可。

2. 代码实现

//大数除法
string DivString(string num1, string num2, int n)
{if (num2 == "0") //除数不能为0return "error";string ret; //存储两个字符串相除后的结果string tmp; //余数//1、先计算小数点前面的数if (num1.size() < num2.size()) //num1的位数小于num2{ret += "0."; //商为0tmp = num1; //余数为num1}else //num1的位数大于等于num2{size_t len = num2.size(); //除数的长度tmp = num1.substr(0, len); //先取出被除数的高len位while (1){//a、计算tmp当中最多有多少个num2(tmp除以num2的商)int count = 0;while (Cmp(tmp, num2) != -1) //tmp大于等于num2,则说明商可以更大{tmp = SubString(tmp, num2);count++; }//b、将商值尾插到ret当中ret += (count + '0');//c、如果num1的所有位都被取完了,则小数点之前的结果计算完毕if (len >= num1.size())break;//d、如果num1当中还有未取的位,则继续从num1中一位尾插到tmp当中tmp += num1[len];len++; //下一次待取位下标}ret += "."; //小数点之前的结果计算完毕,加上小数点//如果ret最高位为0,且该位后面不是小数点,则需要将这个0过滤掉if (ret.size() != 2 && ret[0] == '0')ret = ret.substr(1);}//2、再计算小数点后面的数(保留n位小数)for (int i = 0; i < n; i++){if (tmp == "0") //tmp为0(余数为0){ret += "0"; //则直接在ret后面补0即可}else //tmp不为0(余数不为0){tmp += "0"; //在余数后面补0,继续进行计算//a、计算tmp当中最多有多少个num2(tmp除以num2的商)int count = 0;while (Cmp(tmp, num2) != -1){tmp = SubString(tmp, num2);count++;}//b、将商值尾插到ret当中ret += (count + '0');}}return ret; //返回两个字符串相除后的结果
}

代码说明:

  • 两个数相除时要求除数不能为0,如果除数为0则可以做出相应的处理,代码中当除数为0时返回"error"字符串以示错误。

大数运算(加、减、乘、除)相关推荐

  1. 大数高精度加减、乘除、开根(C++版全套最详细、最易懂)

    大数高精度加减.乘除.开根 一.前面铺垫 二.加法 三.减法 四.乘法 五.除法 六.开根(待完善)   大数高精度加减乘除主要用在超过long型的数字计算(比如1000位数), 最基本的思路就是换成 ...

  2. c语言大数的加减运算,求用C编个大数加减法运算程序

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 只写过加法的,杭电的A + B Problem (II)(AC): #include #include #include #define N 3000 i ...

  3. C++大数乘加减除比较操作集(含测试原码)

    本博文源于C语言基础,旨在解决大数的乘法.加法.减法.除法.比较运算的操作.并给出测试效果. 测试效果 大数的存储方式 struct bign{int d[1000];int len;bign(){m ...

  4. 【C语言】无符号大数的加减问题

    无符号大数加.减运算..题目要求输入两个无符号大数,保证一个大数不小于第二个大数,输出它们的和.差. 输入样例: 1234567890987654321333888999666 14765576565 ...

  5. 贺利坚老师汇编课程32笔记:处理字符串——大小写转换通过与和或运算加减20H

    指路老师博客 20H = 10 0000B b 62H 0110 0010B B 42H 0100 0010B 第一个字符串:小写字母转换为大写字母 小写转大写,通过与运算,0对应位上无论是多少相与是 ...

  6. Java的学习与java大数运算

    之前就学过一点java,但太久没用知识点早就还给书本,之前在实验室搞到一本java的书,今天来重新温习一下 java的语法大部分和c++语言是一样的,入门非常快,所以在这里基础语句的用法就省略了 输出 ...

  7. MFC实现简单连续加减计算器

    C++初学者完成课程作业,时间有限没实现括号运算,也可以使用vector容器写,我这里没有用,思路是一样的. 添加类成员: void MyCalculate(); //计算函数CString m_st ...

  8. 定点运算之原码的加减交替除法(不恢复余数法)

    加减交替法处理思想是先减后判,如果减余数后发现不够减,则下一步中改为加除数操作 一.运算规则 1.符号位异或运算 2.被除数X ,除数Y均取绝对值的补码,且取双符号位 3.被除数X初始值为 [ |X| ...

  9. C语言实现大数运算(长整数的加、减、乘、除)

    由于整型数的位数有限,因此整型数不能满足大整数(超长整数)的运算要求 .大整数计算是利用字符串来表示大整数,即用字符串的一位字符表示大整数的一位数值,然后根据四则运算规则实现大整数的四则运算. 简单表 ...

  10. 大数运算(加、减、乘、除)--C++

    **将大数存储在string中,因为long long类型无法包括超级的大数 **将string中的字符串放在int数组中,倒置处理(本人认为更方便) **按照每一位进行+ -运算,直到得出结果 数据 ...

最新文章

  1. STM32电源框图解析(VDD、VSS、VDDA、VSSA、VREF+、VREF-、VBAT等的区别)
  2. Python自动化测试框架之Pytest教程【让你小鸡变老鹰】
  3. 6 频率_六级连续6年出现频率最高的200个词组【pdf版本】
  4. 《关系营销2.0——社交网络时代的营销之道》一检查拼写和语法
  5. A Data Access Layer to persist business objects using attributes and reflection - Part III [无常译]...
  6. oracle可以在liux上装_linux安装Oracle11G
  7. 【转】用instruments来检验你的app
  8. Spring MVC + freemarker实现半自动静态化
  9. 汽车故障检测仪计算机教程,道通MS诊断仪在线编程刷隐藏908SPRO汽车故障检测电脑...
  10. win7休眠、待机api
  11. 牛郎织女都见面,而你却在吃狗粮---男士星座脱单指南
  12. [置顶]使用scrapy_redis,自动实时增量更新东方头条网全站新闻
  13. 吉林大学计算机专业研究生导师,吉林大学计算机科学与技术学院导师教师简介-张晋东...
  14. 表白套路计算机公式,数学情话大全浪漫情话套路句子 数学情话表白公式短句说说合集...
  15. 如何注册腾讯云账号(图文教程)?
  16. 全国计算机一类学校专科,中国专科学校排行榜前十名(含金量最高的10所专科学校)...
  17. 阿哲学了就来聊——Java反射
  18. android 京东收货地址,手机京东商城怎么添加收货地址?
  19. 三星p601刷android9,三星P601 刷机大师一键刷机教程
  20. 鹏业安装算量喷淋管件修改问题解答

热门文章

  1. PMP之项目风险管理
  2. 分别使用docx4j,jacob将文字与图片插入word中书签位置
  3. 十张图看懂华为IPD和LTC
  4. linux虚拟机读取本地磁盘文件,kvm虚拟化学习笔记(十三)之kvm虚拟机磁盘文件读取小结...
  5. web开发规范 - html书写规范
  6. 解决Google浏览器中Flash插件禁用问题
  7. UBNT ER-4 UPnP相关配置
  8. 如何打开别人的Android项目
  9. 分析了633个中国城市之后,我们发现五分之二都在流失人口...(附统计图)
  10. 在拼多多上班,是一种什么样的体验?我tm心态崩了呀!