【君义精讲】高精度计算
一、概念
1. 高精度計算
高精度計算是指參與運算的數的范圍大大超出了標準數據類型能表示的范圍的運算。
如100位數字和100位數字的加減乘除運算。
為處理高精度計算,我們使用數字數組來表示高精度數字。
2. 數字數組
數字數組:第0位置保存數字位數,而后從低位到高位保存各位數字,每個數組元素保存一位數字。
| 含義 | 位數 | 個位 | 十位 | 百位 | 千位 | 萬位 | … |
例:數字12345用數字數組表示為
| 數值 | 5 | 5 | 4 | 3 | 2 | 1 |
數字數組必須初始化為0,因為進行高精度計算時,會默認數字數組各位都為0。
(以下N為常量,表示高精度數字最大位數)
將數組元素初始化為0的幾種寫法
高精度計算使用數字數組模擬各種計算過程,完成各種計算。
3. 代碼風格
具體寫法上,有三種風格可以選擇
- 數組與函數
- 結構體與函數
- 類中重載運算符
本文先給出數組與函數式寫法,最后給出類中重載運算符的寫法。
4. 解題過程
本人推薦,面對高精度問題時,可以先將各個量當成低精度數字,寫出代碼。然后再將代碼轉為高精度計算。以下介紹中,會給出每個高精度運算中的操作在如果是在低精度運算中的等價代碼。
二、相關操作
1. 字符串轉為數字數組
運算數讀入時都是字符串,需要將字符串轉為數字數組。
注意:字符串從左到右是從高位到低位的。如果從低位到高位填充數字數組,則必須從右向左遍歷字符串。
該操作是輸入時的必需操作,等價低精代碼:cin >> a;
- 字符數組轉為數字數組
- string類對象轉為數字數組
2. 輸出數字
注意:要從高位遍歷到低位
等價低精代碼:cout << a;
3. 數字拷貝(賦值)
注意:從下標0開始賦值
等價低精代碼:a = b;
4. 數字比較
先比較位數,如果位數相同,從高位到低位遍歷比較
模仿strcmp函數,如果a比b大,返回1,如果a比b小,返回-1,如果二者相等,返回0
5. 確定高精度數字的位數
該操作不是一個獨立的運算,卻是以下每種高精度運算中的基本操作。
- 寫法1:
作用為:已知某數字一定大于等于該高精度數字的位數,而后確定該高精度數字的位數。
方法為:i不斷左移,直到i指向的數字不為0,i就是該數字的位數。
- 寫法2:
已知從第i位到該數字的最高位都不為0,確定該數字的位數。
本文以下對setLen()函數的調用都采用寫法1
三、高精度運算
高精度運算有兩類:
- 高精與低精的運算
- 高精與高精的運算
盡管將低精數字轉化為高精數字后,再進行高精與高精的運算,當然也可以完成運算。不過通常情況下,高精與低精數字間運算的復雜度要低于高精與高精數字間運算的復雜度。所以學習高精對低精運算是必要的。
注意:以下用于存儲高精度運算結果的數字數組r必須在函數運行前初始化為0
常見操作:
- 設表示進位的變量c
- 更新進位:c = r[i] / 10
- 更新這一位:r[i] %= 10
注意:高精對低精運算時,進位數字未必是0~9范圍內的數字,可能是很大的數字。
1. 加法
1.1 高精加低精
將低精數字加到高精數字的個位,而后不斷進位
- 等價低精代碼:r = a + b;
- 等價低精代碼:a += b;
1.2 高精加高精
模擬加法豎式,注意i要聲明在循環外面。
- 等價低精代碼:r = a + b;
- 等價低精代碼:a += b;
2. 減法
如果不確定被減數和減數哪個更大,就用numcmp()函數先比較一下兩個數,如果被減數更小,則先輸出負號,再進行更大數字減更小數字的減法運算。
這里預先確定數字a大于等于數字b。
2.1 高精減低精
高精度數字的最低位減低精度數字,而后不斷借位,直到每一位都大于等于0。
- 等價低精代碼:r = a - b;
- 等價低精代碼:a -= b
2.2 高精減高精
模擬減法豎式
- 等價低精代碼:r = a - b;
- 等價低精代碼:a -= b;
3. 乘法
2.1 高精乘低精(常用)
高精度數字的每一位乘低精度數字,而后不斷進位。
- 等價低精代碼:r = a * b;
- 等價低精代碼:a *= b;
2.2 高精乘高精
模擬乘法豎式
- 等價低精代碼:r = a * b;
- 等價低精代碼:a *= b;
沒有直接實現該表達式的函數,可以間接的進行r = a * b, a = r;來實現a *= b;
4. 除法
4.1 高精除以低精
注意:從高位到低位除,模擬豎式
- 等價低精代碼:r = a / b;
- 等價低精代碼:r = a % b;
4.2 高精除以高精
//a = a*10+num,即在數字數組的個位添加一位數,0 <= num <= 9,如2添加1后變為21 void addNum(int a[], int num) {if(a[0] == 1 && a[1] == 0)//如果a是0,那么就把個位設為num a[1] = num;else{for(int i = a[0]; i >= 1; --i)//數組移位a[i+1] = a[i];a[1] = num;a[0]++;} } //高精度除法中的減法代替除法操作,實際就是除法的另一種實現方式 //已知商一定是0~9的數字。a是被除數 b是除數 返回值為商,計算后數組a保存的是余數 int divideByMinus(int a[], int b[]) {int q = 0, r[N];//q:商(減的次數) r:臨時結果 while(numcmp(a, b) >= 0){memset(r, 0, sizeof(r));Minus(a, b, r);numcpy(a, r);//將a設為上次減法的結果q++;}return q; } //r = a / b 高精度數字除以高精度數字 void Divide(int a[], int b[], int r[]) {int x[N] = {};//x:中間臨時使用的數字,是上一次不斷減數后余下的數,以及作為下一次的被減數for(int i = a[0]; i >= 1; --i){addNum(x, a[i]);r[i] = divideByMinus(x, b);}setLen(r, a[0]); } //x = a % b 高精度數字對高精度數字取模 void Mod(int a[], int b[], int x[]) {for(int i = a[0]; i >= 1; --i){addNum(x, a[i]);divideByMinus(x, b);} }四、其他
1. 處理高精度k進制數
默認k是低精度數字。將k設為全局變量。
數字數組中每個元素保存k進制數中的一位,也就是一個0~k-1的數字
例:十六進制數ABCDE用數字數組表示為
| 數值 | 5 | 14 | 13 | 12 | 11 | 10 |
在運算時,將原函數中所有出現的10替換為k。
例:高精乘低精
在輸出時,將數值轉為n進制數碼。
例:輸出16進制數字
2. 結構體與函數風格寫法
在一個結構體中保存數字數組與數字位數
這樣寫的優點在于,可以將運算結果作為函數的返回值返回。而不是像上面函數中寫的那樣,需要用數組參數將結果帶出來。
例:高精乘低精:
struct HPN {int num[N], len;//num:數字數組 len:數字位數 }; HPN Multiply(HPN a, int b) {HPN r;memset(r.num, 0, sizeof(r.num));int c = 0, i;for(i = 1; i <= a.len; ++i){r.num[i] = b * a.num[i] + c;c = r.num[i] / 10;r.num[i] %= 10;}while(c > 0){r.num[i] = c % 10;c /= 10;i++;}while(r.num[i] == 0 && i > 1)i--;r.len = i;return r; }盡管寫法稍有不同不同,算法的思想是相同的,其它運算函數本文不再贅述。
有一道用這種寫法出的題,可以參考:
NOIP 2011 普及組初賽 第28題
3. 類中重載運算符風格寫法
學過重載運算符知識后,可以嘗試使用這種寫法。
將高精度數字的相關操作以及高精度運算都寫在類中,重載各種運算符。
最終效果可以使在寫代碼時,只需要考慮具體問題,而不需要考慮高精度相關的問題。按我們熟悉的低精度運算的寫法將代碼寫出,即可實現高精度運算。
附錄
高精度計算 數組與函數風格寫法
#include <bits/stdc++.h> using namespace std; #define N 10000 //數字數組: //第0位置保存數字位數,第1位置保存個位,第2位置保存十位...從低位到高位保存數字 //注意:以下高精度計算的所有函數的表示結果的參數數組r,在調用函數前都必須清零(memset(r, 0, sizeof(r));),清零語句不可以在函數內調用。 //高精運算高精、高精運算低精的函數名相同,但參數類型不同,形成函數重載。兩個函數都可以使用。//將字符數組轉化為數字數組 數字數組從第1位置到第len位置,從低位到高位保存各位數字,第0位置保存數字位數 void toNum(char s[], int a[]) {a[0] = strlen(s);for(int i = 1; i <= a[0]; ++i)a[i] = s[a[0] - i] - '0'; } //輸出數字數組 void showNum(int a[]) {for(int i = a[0]; i >= 1; --i)cout << a[i]; } //已知i一定大于等于數字a的位數,該函數確定數字a的位數,并賦值給a[0] void setLen(int a[], int i) {while(a[i] == 0 && i > 1)i--;a[0] = i; } //將數字數組b復制到數字數組a之中 void numcpy(int a[], int b[]) {for(int i = 0; i <= b[0]; ++i)a[i] = b[i]; } //比較兩個數字數組 如果a比b大,返回1,如果a比b小,返回-1,如果二者相等,返回0 int numcmp(int a[], int b[]) {if(a[0] > b[0])//如果a的位數比較多return 1;else if (a[0] < b[0])//如果b的位數比較多return -1;else{for(int i = a[0]; i >= 1; --i){if(a[i] > b[i])return 1;else if(a[i] < b[i])return -1;}return 0;} } //高精加低精 r = a + b void Add(int a[], int b, int r[]) {int c = b, i = 1;while(c > 0){r[i] += c;c = r[i] / 10;r[i] %= 10;i++;}if(i > a[0])setLen(r, i); } //高精加低精 a += b void Add(int a[], int b) {int c = b, i = 1;while(c > 0){a[i] += c;c = a[i] / 10;a[i] %= 10;i++;}if(i > a[0])setLen(a, i); } //高精加高精 r = a + b void Add(int a[], int b[], int r[]) {int c = 0, i;for(i = 1; i <= a[0] || i <= b[0]; ++i){r[i] = a[i] + b[i] + c;c = r[i] / 10;r[i] %= 10;}r[i] = c;setLen(r, i); } //高精加高精 a += b void Add(int a[], int b[]) {int c = 0, i;for(i = 1; i <= a[0] || i <= b[0]; ++i){a[i] += b[i] + c;c = a[i] / 10;a[i] %= 10;}a[i] = c;setLen(a, i); } //高精減低精 r = a - b 前提:a >= b void Minus(int a[], int b, int r[])//數a大于等于數b {int i = 1, c;//c:借位數 numcpy(r, a);r[1] -= b;while(r[i] < 0){if(-r[i] % 10 == 0)c = -r[i] / 10;elsec = -r[i] / 10 + 1;r[i] += 10*c;r[i+1] -= c;i++;}setLen(r, r[0]); } //高精減低精 a -= b,前提:a >= b void Minus(int a[], int b) {int i = 1, c;//c:借位數 a[1] -= b;while(a[i] < 0){if(-a[i] % 10 == 0)c = -a[i] / 10;elsec = -a[i] / 10 + 1;a[i] += 10*c;a[i+1] -= c;i++;}setLen(a, a[0]); } //高精減高精 r = a - b 前提:a >= b void Minus(int a[], int b[], int r[]) {int i, c = 0;for(i = 1; i <= a[0]; ++i){r[i] = a[i] - c - b[i];c = 0;if(r[i] < 0){c = 1;r[i] += 10;}}setLen(r, i); } //高精減高精 a -= b,前提 a>=b void Minus(int a[], int b[]) {int i;for(i = 1; i <= a[0]; ++i){a[i] -= b[i];if(a[i] < 0){a[i+1] -= 1;a[i] += 10;}}setLen(a, i); } //高精乘低精 r = a * b void Multiply(int a[], int b, int r[]) {int c = 0, i;for(i = 1; i <= a[0]; ++i){r[i] = b * a[i] + c;c = r[i] / 10;r[i] %= 10;}while(c > 0){r[i] = c % 10;c /= 10;i++;}setLen(r, i); } //高精乘低精 a *= b void Multiply(int a[], int b) {int c = 0, i;for(i = 1; i <= a[0]; ++i){a[i] = a[i]*b + c;c = a[i] / 10;a[i] %= 10; }while(c > 0){a[i] = c % 10;c /= 10;i++;}setLen(a, i); } //高精乘高精 r = a * b void Multiply(int a[], int b[], int r[]) {for(int i = 1; i <= a[0]; ++i){int c = 0;for(int j = 1; j <= b[0]; ++j){r[i+j-1] += a[i]*b[j] + c;c = r[i+j-1] / 10;r[i+j-1] %= 10;}r[i+b[0]] += c;}setLen(r, a[0] + b[0]); } //高精乘高精 a *= b void Multiply(int a[], int b[]) {int r[N] = {};Multiply(a, b, r);//調用上面有3個參數的Multiply函數numcpy(a, r); } //高精除以低精 r = a / b void Divide(int a[], int b, int r[]) {int x = 0;//余數for(int i = a[0]; i >= 1; --i){r[i] = (x * 10 + a[i]) / b;x = (x * 10 + a[i]) % b;}setLen(r, a[0]); } //高精模低精 返回值 = a % b int Mod(int a[], int b)//返回a % b的值 {int x = 0;for(int i = a[0]; i >= 1; --i)x = (x * 10 + a[i]) % b;return x; } //高精除以高精//a = a*10+num,即在數字數組的個位添加一位數,0 <= num <= 9,如2添加1后變為21 void addNum(int a[], int num) {if(a[0] == 1 && a[1] == 0)//如果a是0,那么就把個位設為num a[1] = num;else{for(int i = a[0]; i >= 1; --i)//數組移位a[i+1] = a[i];a[1] = num;a[0]++;} } //高精度除法中的減法代替除法操作,實際就是除法的另一種實現方式 //已知商一定是0~9的數字。a是被除數 b是除數 返回值為商,計算后數組a保存的是余數 int divideByMinus(int a[], int b[]) {int q = 0, r[N];//q:商(減的次數) r:臨時結果 while(numcmp(a, b) >= 0){memset(r, 0, sizeof(r));Minus(a, b, r);numcpy(a, r);//將a設為上次減法的結果q++;}return q; } //r = a / b 高精度數字除以高精度數字 void Divide(int a[], int b[], int r[]) {int x[N] = {};//x:中間臨時使用的數字,是上一次不斷減數后余下的數,以及作為下一次的被減數for(int i = a[0]; i >= 1; --i){addNum(x, a[i]);r[i] = divideByMinus(x, b);}setLen(r, a[0]); } //x = a % b 高精度數字對高精度數字取模 void Mod(int a[], int b[], int x[]) {for(int i = a[0]; i >= 1; --i){addNum(x, a[i]);divideByMinus(x, b);} } int main() {//例子:測試高精乘高精 int a[N] = {}, b[N] = {}, r[N] = {};char s1[N], s2[N];cin>>s1>>s2;toNum(s1, a);toNum(s2, b); Multiply(a, b, r);showNum(r);return 0; }高精度計算 類中重載運算符風格
#include <bits/stdc++.h> using namespace std; #define N 1005 struct HPN {//使用默認拷貝構造函數、默認賦值符號 int a[N];//數字數組 a[0]表示數字位數 低位到高位存儲 HPN(){memset(a, 0, sizeof(a));}HPN(char s[])//用字符數組構造高精度數字 {memset(a, 0, sizeof(a));int len = strlen(s);for(int i = 0; i < len; ++i)a[len - i] = s[i] - '0';a[0] = len;}int& operator [] (int i)//重載中括號,可以讓高精度數字對象類似數組一樣取值{return a[i];}friend ostream & operator << (ostream &out, HPN &b)//友元函數,使該高精度數字可以用cout輸出 {for(int i = b[0]; i >= 1; --i)out << b[i];return out;}void show()//一般輸出 {for(int i = a[0]; i >= 1; --i)cout << a[i];cout << endl;}int numcmp(int a[], int b[]){if(a[0] > b[0])//如果a的位數比較多return 1;else if (a[0] < b[0])//如果b的位數比較多return -1;else{for(int i = a[0]; i >= 1; --i){if(a[i] > b[i])return 1;else if(a[i] < b[i])return -1;}return 0;}}bool operator > (HPN b)//判斷自己比b大{return numcmp(a, b.a) > 0;}bool operator < (HPN b)//判斷自己比b小 {return numcmp(a, b.a) < 0;}bool operator == (HPN b)//判斷自己與b相等 {return numcmp(a, b.a) == 0;}bool operator >= (HPN b)//判斷自己大于等于b {return numcmp(a, b.a) >= 0;}bool operator <= (HPN b)//判斷自己小于等于b {return numcmp(a, b.a) <= 0;}bool operator != (HPN b)//判斷自己不等于b {return numcmp(a, b.a) != 0;}void setLen(int i)//從第i位置開始,向低位尋找,直到找到一個不為0的數位,更新數字長度{while(a[i] == 0 && i > 1)i--;a[0] = i;}HPN operator + (int b)//高精加低精{HPN r(*this);//this是指向自己這個對象的指針,*this就是自己這個對象。這里用了默認拷貝構造函數。 int c = b, i = 1;while(c > 0){r[i] += c;c = r[i] / 10;r[i] %= 10;i++;}if(i > r[0])r.setLen(i);return r;}void operator += (int b)//高精加低精{int c = b, i = 1;while(c > 0){a[i] += c;c = a[i] / 10;a[i] %= 10;i++;}if(i > a[0])setLen(i);}HPN operator + (HPN b)//高精加高精{HPN r;int i, c = 0;for(i = 1; i <= a[0] || i <= b[0]; ++i){r[i] = a[i] + b[i] + c;c = r[i] / 10;r[i] %= 10;}r[i] = c;r.setLen(i);return r;}void operator += (HPN b)//高精加高精{int c = 0, i;for(i = 1; i <= a[0] || i <= b[0]; ++i){a[i] += b[i] + c;c = a[i] / 10;a[i] %= 10;}a[i] = c;setLen(i);}HPN operator - (int b)//高精減低精 本身比b大 {HPN r(*this);//使用拷貝構造函數 int i = 1, c;//借位數 r[1] -= b;while(r[i] < 0){if(-r[i] % 10 == 0)c = -r[i] / 10;elsec = -r[i] / 10 + 1;r[i] += 10*c;r[i+1] -= c;i++;}r.setLen(r[0]);return r;}void operator -= (int b)//高精減低精{int i = 1, c;//c:借位數 a[1] -= b;while(a[i] < 0){if(-a[i] % 10 == 0)c = -a[i] / 10;elsec = -a[i] / 10 + 1;a[i] += 10*c;a[i+1] -= c;i++;}setLen(a[0]);}HPN operator - (HPN b)//高精減高精 前提本數字比b大{HPN r(*this);int i, c = 0;for(i = 1; i <= r[0] || i <= b[0]; ++i){if(r[i] < b[i]){r[i + 1]--;r[i] += 10;}r[i] = r[i] - b[i];}r.setLen(i);return r;}void operator -= (HPN b)//高精減高精{int i;for(i = 1; i <= a[0]; ++i){a[i] -= b[i];if(a[i] < 0){a[i+1] -= 1;a[i] += 10;}}setLen(i);}HPN operator * (int b)//高精乘低精{HPN r;int c = 0, i;for(i = 1; i <= a[0]; ++i){r[i] = b * a[i] + c;c = r[i] / 10;r[i] %= 10;}while(c > 0){r[i] = c;c = r[i] / 10;r[i] %= 10;i++;}r.setLen(i);return r;}void operator *= (int b)//高精乘低精{int c = 0, i;for(i = 1; i <= a[0]; ++i){a[i] = a[i]*b + c;c = a[i] / 10;a[i] %= 10; }while(c > 0){a[i] = c % 10;c /= 10;i++;}setLen(i);}HPN operator * (HPN b)//高精乘高精{HPN r;for(int i = 1; i <= a[0]; ++i){int c = 0;for(int j = 1; j <= b[0]; ++j){r[i + j - 1] = a[i] * b[j] + r[i + j - 1] + c;c = r[i + j - 1] / 10;r[i + j - 1] %= 10;}r[i + b[0]] += c;}r.setLen(a[0] + b[0]);return r;}void operator *= (HPN b)//高精乘高精{(*this) = (*this) * b;}HPN operator / (int b) //高精除低精{int x = 0;HPN r;for(int i = a[0]; i >= 1; --i){x = x * 10 + a[i];r[i] = x / b;x %= b;}r.setLen(a[0]);return r;}void operator /= (int b) //高精除低精{int x = 0;for(int i = a[0]; i >= 1; --i){x = x * 10 + a[i];a[i] = x / b;x %= b;}setLen(a[0]);}int operator % (int b) //高精模低精{int x = 0;for(int i = a[0]; i >= 1; --i)x = (x * 10 + a[i]) % b;return x;}void addNum(HPN &x, int num)//x = x*10 + num {if(x[0] == 1 && x[1] == 0)//如果x是0,那么就把個位設為num x[1] = num;else {//以下幾句完成x = x * 10 + b for(int j = x[0]; j >= 1; --j)x[j + 1] = x[j];x[1] = num;x[0]++;}}HPN operator / (HPN b) //高精除高精{HPN r, x;for(int i = a[0]; i >= 1; --i){addNum(x, a[i]); //求r[i] = x / b; 以減法代替除法int q = 0;//記錄減了幾次,該值就是通過減法代替除法得到的x/b的商while(x >= b)//高精度數字比較{x = x - b;//高精度數字減法q++;}r[i] = q;}r.setLen(a[0]);return r;}HPN operator % (HPN b) //高精模高精{HPN x;for(int i = a[0]; i >= 1; --i){addNum(x, a[i]);//求x = x % b; 以減法代替除法while(x >= b)//高精度數字比較 x = x - b;}return x;} };int main() {char c[40], d[40];cin>>c>>d;HPN a(c), b(d);a /= 3;cout << a;return 0; }總結
以上是生活随笔為你收集整理的【君义精讲】高精度计算的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mipi和isp处理_图像信号处理 (I
- 下一篇: mysql安装失败net_mysql安装