網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
前言
高精度的運(yùn)算在算法題尤為常見,在處理一些大型數(shù)據(jù)的項(xiàng)目中也需要用到。雖然Boost庫(kù)中有處理高精度問題的模板,但是標(biāo)準(zhǔn)庫(kù)中并沒有。為了方便學(xué)習(xí),本文提供了高精度運(yùn)算的模板,供大家參考。
什么是大整數(shù)?
眾所周知,最大的整型long long的范圍是【-9223372036854775808~9223372036854775807】,即使是無(wú)符號(hào)位的unsigned long long最大值也只是擴(kuò)大一倍,那當(dāng)題目需要用到或需要輸出比long long類型的范圍還要大的數(shù)字時(shí),我們是不是就不能用常規(guī)辦法去接收這些數(shù)字了。這個(gè)時(shí)候使用大整數(shù)就可以解決上述問題——也就是解決接收超出整型范圍數(shù)字的問題。
大整數(shù)的表示
其實(shí)大整數(shù)的表示方法也不難,我們使用數(shù)組就可以了(當(dāng)然C++的vector容器也是可以的,原理都一樣,但為了照顧C(jī)語(yǔ)言的小伙伴,本文用數(shù)組表示)。
假設(shè)現(xiàn)在有一個(gè)int類型數(shù)組d[1000],那么我們可以用數(shù)組中的每一位元素表示大整數(shù)中的每一位數(shù)字,比如有整數(shù)123456789,那么我們可以用d[0]表示億位上的【1】,用d[1]表示千萬(wàn)位上的【2】...以此類推,我們就可以用一個(gè)長(zhǎng)度為9的數(shù)組表示這個(gè)大整數(shù)了。
當(dāng)然為了契合我們后面四則運(yùn)算的思維,我們將數(shù)組元素的順序翻轉(zhuǎn)一次,也就是在數(shù)組靠前的元素表示低位,而靠后的元素表示高位(原因后面會(huì)講到),也就是如下圖所示:
而為了方便我們獲取當(dāng)前大整數(shù)的長(zhǎng)度,我們可以使用一個(gè)結(jié)構(gòu)體(或者一個(gè)類,與C語(yǔ)言的結(jié)構(gòu)體不同,在C++中的結(jié)構(gòu)體也可以定義成員函數(shù))來(lái)表示。
//struct of bign(big number)
struct bign {
int d[1000];
int len;
bign()//構(gòu)造函數(shù)
{
this->len = 0;
memset(d, 0, sizeof(d));
}
};
當(dāng)然,一般輸入大整數(shù)時(shí),都是先用字符串讀入,然后再把字符串另存為至bign結(jié)構(gòu)體。
bign change(const string str)//將整數(shù)轉(zhuǎn)換為begin c語(yǔ)言用const char*
{
bign a;
a.len = str.size();//bign的長(zhǎng)度就是字符串的長(zhǎng)度 c語(yǔ)言用strlen()函數(shù)
for (int i = 0; i < a.len; i++)
{
a.d[i] = str[a.len - i - 1] - '0';
}
return a;
}
大整數(shù)的運(yùn)算
對(duì)于大整數(shù)的四則運(yùn)算,我們需要模擬在小學(xué)期間學(xué)習(xí)四則運(yùn)算的思路和過程,也就是把我們?cè)诓莞寮埳线\(yùn)算的過程,抽象成代碼的邏輯。
1、高精度加法
加法實(shí)現(xiàn)方式與我們以前學(xué)到的加法一樣。對(duì)于某一位的運(yùn)算:我們將該位上的兩個(gè)數(shù)字與進(jìn)位相加,得到的結(jié)果取個(gè)位數(shù)作為該結(jié)果,十位數(shù)作為新的進(jìn)位。
bign add(bign a, bign b)
{
bign c;
int carry = 0; //carry是進(jìn)位標(biāo)志
for (int i = 0; i < a.len || i < b.len; i++) //以較長(zhǎng)的為界限
{
int temp = a.d[i] + b.d[i] + carry; //兩個(gè)對(duì)應(yīng)位與進(jìn)位相加
c.d[c.len++] = temp % 10; //取個(gè)位數(shù)為該位的結(jié)果
carry = temp / 10; //取十位數(shù)為新的進(jìn)位
}
if (carry) //如果最后的進(jìn)位不為0,則直接賦給結(jié)果的最高位
{
c.d[c.len++] = carry;
}
return c;
}
這里要注意,這樣寫法的條件是兩個(gè)對(duì)象都是非負(fù)整數(shù)。如果有雙方異號(hào),可以在轉(zhuǎn)換到數(shù)組這一步時(shí)去掉符號(hào),再使用高精度減法;如果雙方都為負(fù)數(shù),那么去掉負(fù)號(hào)后采用高精度加法,最后負(fù)號(hào)加回去即可。?
2、高精度減法
通過對(duì)減法步驟的拆分可以得到一個(gè)簡(jiǎn)練的步驟:對(duì)某一位,比較被減位和減位,如果不夠減,則令被減位的高位減1,被減位加10再進(jìn)行減法(借一位);如果夠減,那就直接減。最后需要注意減法后高位可能有多余的0,要去除它們,但要保證結(jié)果至少有一位數(shù)。
bign sub(bign a, bign b) //a - b
{
bign c;
for (int i = 0; i < a.len || i < b.len; i++) //以較長(zhǎng)的為界限
{
if (a.d[i] < b.d[i]) //不夠減
{
a.d[i + 1]--; //向高位借位
a.d[i] += 10; //向前位借10
}
c.d[c.len++] = a.d[i] - b.d[i]; //減法結(jié)果為當(dāng)前位
}
while (c.len >= 2 && c.d[c.len - 1] == 0) //剩余的位數(shù)不小于十位,并且最高位是0
{
c.len--; //去除高位的0,同時(shí)至少保留一位最低位
}
return c;
}
3、高精度乘以低精度
所謂高精度乘以低精度,就是bign*int的運(yùn)算。
對(duì)某一位來(lái)說(shuō)是這樣的步驟:取bign的某位與int型整體相乘,再與進(jìn)位相加,所得結(jié)果的個(gè)位數(shù)作為該結(jié)果,高位部分作為新的進(jìn)位。對(duì)于a、b異號(hào)的情況只需要一個(gè)標(biāo)志位變量記錄,在輸出的時(shí)候加上負(fù)號(hào)就行了。
bign multi(bign a, int b)
{
bign c;
int carry = 0; //進(jìn)位
for (int i = 0; i < a.len; i++)
{
int temp = a.d[i] * b + carry;
c.d[c.len++] = temp % 10; //個(gè)位作為該結(jié)果
carry = temp / 10; //高位部分作為新的進(jìn)位
}
while (carry != 0) //和加法不一樣,乘法的進(jìn)位可能不止一位,因此用while
{
c.d[c.len++] = carry % 10;
carry /= 10;
}
return c;
}
4、高精度除以低精度
高精度除以低精度,就是bign/int的運(yùn)算。考慮到有時(shí)還需要知道計(jì)算之后的余數(shù),于是就把余數(shù)寫成引用的形式傳入,當(dāng)然也可以把余數(shù)設(shè)成全局變量。?
對(duì)于某一位來(lái)說(shuō):上一步的余數(shù)乘以10加上該步的位,得到該步當(dāng)前的被除數(shù),將其與除數(shù)比較;如果不夠除,則該位的商為0;如果夠除,則商即為對(duì)應(yīng)的商,余數(shù)即為對(duì)應(yīng)的余數(shù)。和其他運(yùn)算一樣,要注意結(jié)果可能有多余的0,要去掉它們,但也要保證結(jié)果至少有一位數(shù)。
bign divide(bign a, int b, int& r) //r為余數(shù)
{
bign c;
c.len = a.len;//被除數(shù)的每一位和商的每一位是一一對(duì)應(yīng)的,因此先令長(zhǎng)度相等
for (int i = a.len - 1; i >= 0; i--) //從高位開始
{
r = r * 10 + a.d[i]; //和上一位遺留的余數(shù)組合
if (r < b) c.d[i] = 0; //不夠除,該位為0
else //夠除
{
c.d[i] = r / b; //商
r = r % b; //獲得新的余數(shù)
}
}
while (c.len >= 2 && c.d[c.len - 1] == 0)
{
c.len--; //去除高位的0,同時(shí)至少保留一位最低位
}
return c;
}
如果大家對(duì)于上述的邏輯還不清楚的話,可以自己在稿紙上舉例幾個(gè)簡(jiǎn)單的數(shù)算算,實(shí)際思路和我們運(yùn)算時(shí)的思路是一樣的哈。
大整數(shù)的表示
最后,當(dāng)我們要打印出大整數(shù)時(shí)則要注意了,在上文中,我們?yōu)榱朔奖氵\(yùn)算,將數(shù)組中的位序翻轉(zhuǎn)了一次,所以打印時(shí)就是需要從后往前輸出。
而如果我們輸入的數(shù)據(jù)是【04】,那么輸出的結(jié)果就不是單純的【4】了,一般來(lái)說(shuō)這不是我們想要的結(jié)果是吧。那么為了解決這個(gè)問題,我們可以在打印的時(shí)候加上一個(gè)標(biāo)志位來(lái)判斷。
void print(bign ans)
{
int flag = 0;
for (int i = ans.len - 1; i >= 0; i--)
{
if (ans.d[i] != 0) //標(biāo)志位如果首位是0 則不輸出
{
flag = 1;
}
if (flag)
{
cout << ans.d[i];
}
}
if (!flag)
cout << 0; //包含輸出0的情況
}
補(bǔ)充:使用示例
例題1(貪心+大整數(shù))
題目:洛谷P1080 國(guó)王游戲
分析
此題需要先貪心找到排序規(guī)律為按照每個(gè)人的左右手乘積非降序排列,在計(jì)算最小值時(shí)累乘的過程中可能數(shù)字溢出,所以要使用大整數(shù),但是根據(jù)題目描述大整數(shù)開100001位是不夠的需要開的更大,但是實(shí)際測(cè)試數(shù)據(jù)并沒有這么大。
AC代碼
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 10001;
pair<int,int> nums[maxn]; //first 右手,second左手
struct bign{ //存123時(shí): d={3,2,1}llen =3;
int d[maxn];
int len;
bign(){
memset(d,0,sizeof(d));
len = 0;}
}res,sum,ans; //res最終結(jié)果,sum累乘結(jié)果,ans比較變量
bign multi(bign a,int b){ //高精度大整數(shù)與低精度的乘法
bign c;
int carry = 0; //進(jìn)位
for(int i=0;i<a.len;i++){
int temp = a.d[i]*b + carry;
//printf("%d*%d+%d\n",a.d[i],b,carry);
c.d[c.len++] = temp%10;
carry = temp / 10;
}
while(carry!=0){
c.d[c.len++] = carry%10;
carry/=10;
}
return c;
}
bign divide(bign a,int b,int& r){ //高精度除以低精度,r為余數(shù)
bign c;
c.len = a.len;
r=0;
for(int i=a.len-1;i>=0;i--){
r = r*10 + a.d[i];
if(r<b) c.d[i] = 0;
else{
c.d[i] = r/b;
r %= b;
}
}
while(c.len-1>=1&&c.d[c.len-1]==0){
c.len--;
}
return c;
}
int compare(bign a,bign b){ //比較a和b的大小,a>b返回1,相等返回0,a<b返回-1
if(a.len>b.len) return 1;
if(a.len<b.len) return -1;
for(int i=a.len-1;i>=0;i--){
if(a.d[i]>b.d[i]) return 1;
if(a.d[i]<b.d[i]) return -1;
}
return 0;
}
void print(bign a){ //輸出大整數(shù)
for(int i=a.len-1;i>=0;i--)
printf("%d",a.d[i]);
return;
}
int cmp(pair<int,int> a,pair<int,int> b){
return a.first*a.second<b.first*b.second;
}
int main(){
int n;
scanf("%d",&n);
scanf("%d %d",&nums[0].first,&nums[0].second);
for(int i=1;i<=n;i++)
scanf("%d %d",&nums[i].first,&nums[i].second);
sort(nums+1,nums+1+n,cmp);
sum.d[0] = 1; sum.len = 1;
for(int i=1;i<=n;i++){
int r = 0; //存余數(shù)
sum = multi(sum,nums[i-1].first);
ans = divide(sum,nums[i].second,r);
if(compare(ans,res)==1)
res = ans;
}
print(res);
return 0;
}
總結(jié)
原文鏈接:https://blog.csdn.net/ZER00000001/article/details/127722669
相關(guān)推薦
- 2022-08-22 Nginx配置使用詳解_nginx
- 2022-08-01 C++無(wú)符號(hào)整數(shù)溢出問題解析_C 語(yǔ)言
- 2022-07-27 Python中的collections集合與typing數(shù)據(jù)類型模塊_python
- 2022-05-10 torch.cuda.is_available()返回false最終解決方案
- 2022-11-24 C++?OpenCV實(shí)現(xiàn)boxfilter方框?yàn)V波的方法詳解_C 語(yǔ)言
- 2022-04-09 python中IO流和對(duì)象序列化詳解_python
- 2022-07-21 centos docker容器優(yōu)化清理磁盤空間以及內(nèi)存占用
- 2022-07-01 Python錯(cuò)誤+異常+模塊總結(jié)_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支