C++算法:高精度计算
前言
内容纯属原创,如有雷同,纯属巧合!
如转载请注明出处!
参考题目
洛谷
https://www.luogu.com.cn/problem/P1601
OpenJudge
http://noi.openjudge.cn/ch0106/10/
一、高精度的普及内容
在C++中,数据类型最高也就只可定义19位数字(好像吧),但Python不同,Python自带高精度,这也是比赛不支持Python的一部分原因。
但是自从用了高精度之后,麻麻再也不用担心上限不够了!
高精度可以随意定义位数
二、高精度所涉及的基础内容
1.高精度所涉及的基础内容有:数组、字符串、以及基础计算等
2.涉及很多基础,如for循环、while循环、if、以及一些逻辑(让人头疼的瞎搞),不过相信大家这些都没有问题(除了我)。
三、基本逻辑和思想
我们先以加法做例子(加法:我不要做栗子!我要……“唉吗真香!”来自作者稚嫩可爱的吃饭声)
咳咳,
我们先来手动算一个加法算式(别告诉我你不会算)
好的我们来分析一下(回顾一下小学竖式加法的做法)
右起第1位,3+8=11,写1进1
右起第2位,3+8=11,写2进1(本位的1加上进位的1得出2)
右起第3位,3+1=4,写5(本位的4加上进位的1得出5)
右起第4位,2+4=6
右起第5位,5=5
但是要注意,在C++里可不能像第5位这样,因为这样会越界,所以我们第5位的真正算式是:0+5=5
正常时候,我们计算加法竖式有什么要点呢?
来来来我教一下一年级知识(啊不,二年级)
(1)从右边开始计算
(2)可能会有进位
(3)数位对齐计算
首先我们来看一下,从左边开始这一条非常简单,我们只需要存数组的时候倒着存进来,然后输出倒着输出就行了
第二条,这也非常简单,我们只需要将多出来的一位加到后面那一位就行了
第三条更加容易实现,for循环里的i(给你眼神自己体会qwq)
竖式计算到这里
我们来列一下大纲
1.读入
2.计算
3.输出
这部分讲到这里,具体实现往下看
四、具体逻辑的实现
实现第1步,读入
但是问题来了,我们这样去读,该读到哪里呢?怎么读呢?
可能有聪明的老司机小伙伴想到了
可以用
scanf("%1d",a[i]);
来读进数组中。
可是,这位童鞋,难道你没有想到,你读入多长呢?长度咱也不知道哇
所以,我们就要用伟大的万能的字符串来读入了
首先我们把字符串名字叫做a1和b1吧(因为我比较习惯把a和b当做数组的名字)
然后cin(其实scanf也可以,这里便于写(其实是偷懒)才这样的你肯定没发现我括号套娃了)
那么字符串的长度我们就可以知道了,
int lena=a1.length(); int lenb=b1.length();
这里我把lena当成a数组的长度,lenb当成b数组的长度
用C++自带的string.length来获取长度
当然,你如果不喜欢用string,用char也是可以的,那么你这里就应该写strlen(r);了
然后我们把字符串转化成数组元素
for(int i=0;i<lena;i++) a[i]=a1[lena-i]-48;//玄学转换术 for(int i=0;i<lenb;i++) b[i]=b1[lenb-i]-48;//玄学*2
这里给大家讲解一下
-48这个我就不说了,因为48在ASCII码里代表的是'0',减出来正好可以得出数
上面我们说过了,两个数组是倒着存的,所以我们这里的lena-i和lenb-i都是辅助我们倒着存的
给大家简单写写
那么我们可以清晰的看出,对比原数组和读进的数组来看的话,完全就是相反的。
别忘了我们的for+数组也是从右边开始计算的,这样读入部分就算完结了
来实现第2步
我们回顾一下第一张竖式计算的图片,
最后得出来的结果绝对不可能大于两个数最大的位数+1
比如999+9999
两个数中位数最多的是4位
4+1=5
那么这两个数相加绝对不可能大于5。
那么和的长度有了,就来计算了
我们暂时把和的数组叫做c(其实和存到b也可以,但是我懒的去搞了,就先这样吧)
这里给大家提一个小问题
我们是应该算完一个数进一次位还是全部算完再进位呢?
A、算完一个数进一次位
B、全部算完再进位
答案是:AB
为什么呢?
比如选A
2 3 4
+ 9 9 9
------------
0 0 1 3
0 1 2 3
1 1 3 3
------------
1 2 3 3
但如果你是存到b数组中的
第一步:计算
2 3 4
+ 9 10 3
第二步:进位
2 3 4
+ 10 3 3
第三步:计算
2 3 4
+ 12 3 3
第四步:进位
2 3 4
+ 1 2 3 3
好的我们暂时计算到这里
开始真正的计算(代码写)
首先吧lenc也就是c数组最长长度算出来
int lenc=max(lena,lenb)+1;
然后进行c数组的计算
我们这里用先算完在进位的方法,思路会比较清晰
for(int i=1;i<=lenc;i++){ c[i]=a[i]+b[i]; }
然后是进位部分
这一块我都不讲了,太简单了,主要讲思路
for(int i=1;i<=lenc;i++){ c[i+1]+=c[i]/10; c[i]=c[i]%10; }
还没结束!
这里提供另一种lenc的计算方法!
在开始定义的地方
也就是
int lenc=max(lena,lenb)+1;
我们可以这样写
int lenc=max(lena,lenb);
其实就是少了个+1,但是思路却大大改变
原先的写法,是进位就刚好的想法,如果不进位的话数组最前面也就是c[lenc]的位置就会多一个0
而且如果题目上没说读入会有前导0的话(前导0就比如0000003333333333333333333333这种数,输出的时候要特地把前导0过滤一遍)
这种方法需要特意的去除前导0(用while或者if)
while去前导0的写法我后面会讲,if就直接这样
if(!c[lenc])len--;
但如果用第二种的话,那么思路会大大改变
模拟这道题不进位
然后计算完进位完之后,加一个
if(c[lenc+1])len++;
思路:发现这其实进位了,那么和的长度应该是进位后的长度,也就是原先的lenc+1
不多废话,我刚刚好像承诺了你们要讲while去前导0的
那好吧,我讲讲
来个例子
0 0 0 1 2 0
这个数要去前导0
那么我们从左边开始看,左起第一个数是0
去一个前导0位数就减少了1,那么我们直接减位数就行了,也就是lenc--;
0 0 1 2 0
继续继续
当前左起第一位发现还是0
去0
0 1 2 0
左起第一位还是0
去0
1 2 0
左起第一位不是0了
那么跳出循环
为啥要跳出循环呢?
不然容易去掉结尾的0,比如原本的120变成了12就错了
while(!c[lenc]&&lenc>1)lenc--;
前面的!c[lenc]表示这位是0
lenc>1要保证剩个0,不然就啥都不输出了,比如00000,最后lenc长度就剩下0了。。。。
最后第3步,输出要进行一个变动
我们读入的时候是反着读入的,这意味着我们要反着输出
那么想到这里就很轻松了
for(int i=lenc;i>=1;i--)cout<<c[i];
这里用的是倒着循环
同样,你也可以和读入一样写
for(int i=1;i<=lenc;i++)cout<<c[lenc-i];
方法任你选择,就看个人爱好了
练习题在前面有写了,也可以自己去洛谷、Openjudge里搜,题非常多。
完整代码:(头文件我用的万能头,大家要不要养成万能头的习惯哦!)
#include<bits/stdc++.h> using namespace std; string a1,b1; int a[1000],b[1000],c[1000]; int main(){ cin>>a1>>b1; int lena=a1.length(); int lenb=b1.length(); for(int i=1;i<=lena;i++) a[i]=a1[lena-i]-48; for(int i=1;i<=lenb;i++) b[i]=b1[lenb-i]-48; int lenc=max(lena,lenb); for(int i=1;i<=lenc;i++){ c[i]=a[i]+b[i]; } for(int i=1;i<=lenc;i++){ c[i+1]+=c[i]/10; c[i]=c[i]%10; } if(c[lenc+1])lenc++; while(c[lenc]==0&&lenc>1)lenc--; for(int i=lenc;i>=1;i--)cout<<c[i]; return 0; }
五、高精度拓展
高精度不仅能实现加法,还可以实现减法、乘法、除法以及求模
这里我简单讲一下运算时的思路
1.减法
lenc我们就直接求被减数和减数的最大位数就行了
我们在计算时,for里先写一个if,也就是如果被减数比减数大,那么被减数的前一位-1,当前位+10,然后if外面写减就行了,这个都不用处理进位
但是一般的减法要考虑负数,我们只要在前面读入完a1和b1的时候这样写
if((a1<b1&&a1.length()==b1.length())||a1.length()<b1.length()){
cout<<"-";
string temp;
temp=a;
a=b;
b=temp;
}
这里判断了两种情况,一种是两数位数相等但是大小不一样,另一种是位数不一样
然后如果成立的话就把a和b互换,输出负号
因为被减数小于减数,那么差就相当于负的减数减被减数
比如2-3,他们的差就等于-(3-2)
2.乘法
乘法就很简单啊
乘法分两种
高精度*单精度
高精度*高精度
高精度*单精度就相当于加法
比如100*8就很简单
这里示范高精度*高精度
和加法一样,不过是需要二重循环来写
for(int i=1;i<=lena;i++){ for(int j=1;j<=lenb;j++){ c[i+j-1]+=a[i]*b[j]; } }
不过计算要改
因为要很多数相加
比如11*11
那么就是11+110
要很多数相加所以是+=
然后lenc的话就很简单了
int lenc=lena+lenb+1;
这个lenc大家自己品吧,和加减的差不多个道理
3.除法
除法也分两种,
高精度/单精度
高精度/高精度
高精度/单精度也很简单
就是挨个位除而已,除不尽落下来
高精度/高精度就比较麻烦了
先比较位数,在挨个位比较大小
如果能除就除,就相当于比较完大小之后减,但这个减也是高精度-高精度的,减完再看看能不能减,这样循环,如果不能减了就落下来,下一次循环
但最后他可能会让你保留小数之类的
那么你就要小心了,要把小数的地方留出来
所以这个地方要谨慎
5.取模
这个大概和除法没啥区别吧。。。。不就是把余数留出来吗
反而比除法简单
都不用留出小数的空间了
六、总结
码了好半天,真累啊qwq
求给个顶或者占楼ddd
咳咳,那么正式总结一下
我们这里用了字符串转数字、挨个位计算之类的
总之会了一个,其他几个想想也就能算出来
易错点:
(1)保留小数
(2)考虑负数
(3)进位借位
(4)数据范围(数组大小)
(5)lenc的长度
(6)位数
(7)去不去(或者有没有)前导0
(8)倒着输入、输出
(9)i的起始位置要确定
那么今天到这里,欢迎下次再来看qwq