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

posted @ 2020-08-04 23:16  无咕  阅读(3379)  评论(2编辑  收藏  举报
/* 点击爆炸效果*/