高精度アルゴリズム
1.はじめに
コンピューターを使用して数値計算を実行すると、次のような問題に遭遇することがあります。の正確な結果は何ですか?
もちろん、n が 30 未満の場合は、コンピューターに付属の電卓を使用して計算できます。しかし、100! に遭遇した場合、正確な結果を直接計算する方法はありません。別の例として、20,000 桁の 2 つの数値の合計を求めます。
では、精度が欠けているという問題を解決するにはどうすればよいでしょうか?
高精度アルゴリズムは、大量の数値を処理するための数学的計算方法です。
- 一般的な科学計算では小数点以下数百桁以上の計算が行われることが多く、もちろん数千億から数百億の大きな数値になる場合もあります。一般に、このような種類の数値を高精度数値と呼びます。高精度アルゴリズムでは、コンピューターを使用して、非常に大きなデータに対する加算、減算、乗算、除算、べき乗、階乗、平方根などの演算をシミュレートします。非常に大きな数値は、通常はコンピュータに保存できません。
- そこで、この数値を 1 桁または 4 桁に分割して配列に格納し、配列で数値を表現するため、この数値を高精度数値と呼びます。
- 高精度アルゴリズムは、高精度の数値に対するさまざまな演算を処理できるアルゴリズムですが、その特殊性により、通常の数値アルゴリズムから分離され、独自のものになります。
- 高精度処理とは、実際には手計算を模擬したシミュレーション手法であり、原理は垂直計算と同じですが、処理の過程では高精度データの読み込み、変換、保存に注意する必要があります。 、データ計算、および結果ビット数の計算と出力などのいくつかの問題。
2. 高精度+低精度
999999999999999999 などの大きな数もあれば、6666 などの小さな数もあります。これら 2 つの数字をどのように加算しますか?
高精度加算のアイデア
-
大きな数値を文字列に格納します。
-
文字列の各文字番号は、ASCII 変換を通じて配列に格納されます。
下位ビットが配列の先頭に存在する必要があることに注意してください: a[i] = s[len-i-1]-'0'; -
加算と桁上げの計算:
① a[i+1] += a[i]/10;
② a[i] %= 10; -
数値オーバーフロー、長さ +1;
-
出力結果を反転します。
C++ 言語プログラミングを例として、その方法を見てみましょう。
#include<iostream>
#include<string>
using namespace std;
string s1;
int a[1000],b;
int main(){
cin>>s1>>b; // 1.输入数值
コードでは、s1 配列には大きな数値が格納され、b 整数には小数が格納されます。
① 1234 + 66
② 123456 + 99
数学的な加算演算によると、単位の桁と単位の桁が最初に加算されます。つまり、
① s1[3] + 6
② s1[5] + 9
上記より、次のようになります。単位 digit + 単位 数字のインデックス添え字に一貫性がないことがわかり、プログラミングの難易度が高くなります。次に、配列を逆の順序で保存することを検討できます。配列の先頭には 1 桁の s1[0] が配置され、値がどんなに大きくても、配列の添え字は s1[0] から始まり加算されます。
// s1存到数组a里面,记得转为整数
int len1 = s1.size(); //获取长度
for(int i=0;i<len1;i++){
a[i] = s1[len1-i-1]-'0';
}
s1 は文字列であり、ASCII コード テーブルを通じて整数に変換する必要があるため、-'0' を減算する必要があります。
さて、上記で大きな数値を配列に格納することが完了しました。次に、加算演算を実行する必要があります。
- 最初に小数点を a[0] の位置に追加します。
a[0] +=b;
たとえば、1234 + 89 =》a[0] = 1323; - 次に、新しい 1 桁を a[0] に残し、他の桁に対して桁上げ演算を実行します。
a[0+1] += a[0] /10; // a[1] = 3+132 = 135
a[0] = a[0] % 10; // a[0] = 3
- 同様に、10の位が更新され、他の桁が実行されます。
//3.进行加法运算。
a[0]+=b; // 5+9999 10004
//4.进位操作
for(int i=0;i<len1;i++){
a[i+1] += a[i] / 10;
a[i] = a[i] % 10;
}
加算演算の後は、数値のオーバーフロー状況を考慮する必要があります。たとえば、999 +11 == 1010 には余分な 1,000 桁が含まれます。この問題の解決策は簡単です。最上位ビットが 0 でないかどうかを判断し、条件が満たされた場合に再度キャリー操作を実行します。
//5.考虑到数字溢出
while(a[len1]){
a[len1+1] += a[len1]/10;
a[len1] %= 10;
len1++;
}
結果を出力するときは、必ず逆にしてください。以前は a[0] が最下位ビットであり、出力は左から右に上位ビットから下位ビットまで行われるためです。
//6.反向输出
for(int i=len1-1;i>=0;i--){
cout<<a[i];
}
高精度 + 低精度の完全なコードは次のとおりです。
#include<iostream>
#include<string>
using namespace std;
string s1;
int a[1000],b;
int main(){
cin>>s1>>b;
// s1存到数组a里面,记得转为整数
int len1 = s1.size();
for(int i=0;i<len1;i++){
a[i] = s1[len1-i-1]-'0';
}
//3.进行加法运算。
a[0]+=b; // 5+9999 10004
//4.进位操作
for(int i=0;i<len1;i++){
a[i+1] += a[i] / 10;
a[i] = a[i] % 10;
}
//5.考虑到数字溢出
if(a[len]){
len++;
}
//6.反向输出
for(int i=len1-1;i>=0;i--){
cout<<a[i];
}
}
3.高精度+高精度
上記と同様の手順。
高精度加算のアイデア:
- 大きな数値を文字列に格納します。
- 文字列の各文字番号は、ASCII 変換を通じて配列に格納されます。
下位ビットが配列の先頭に存在する必要があることに注意してください: a[i] = s[len-i-1]-'0'; - 数値の最大長を取得します: max(len1,len2);
- 加算と桁上げの計算:
① a[i+1] += a[i]/10;
② a[i] %= 10; - 数値オーバーフロー、長さ +1;
- 出力結果を反転します。
#include<iostream>
#include<string>
using namespace std;
string s1,s2;
int a[10000],b[10000],c[100001];
int main(){
// 1.输入值,长度
cin>>s1>>s2;
int len1 = s1.size();
int len2 = s2.size();
// 2.把字符转为整数存到数组
// 注意要个位存到数组开头
for(int i=0;i<len1;i++){
a[i] = s1[len1-i-1]-'0';
}
for(int i=0;i<len2;i++){
b[i] = s2[len2-i-1]-'0';
}
どちらの大きな数値も文字列に格納してから整数に変換する必要があります。次に、配列の添字が桁数、つまり a[i]+b[i] に応じて順番に追加されます。加算をいつ停止するかは、最大の長さの数値によって決定されるため、加算する前に最大の数値の長さが必要です。
// 3.获取最大的数。
int len = max(len1,len2);
// 对各个位数进行相加并把最新的值存到输出C里面。
for(int i=0;i<len;i++){
c[i]=a[i]+b[i];
}
c[i] = a[i]+b[i]; により、c[0] = 11 (10 より大きい) などの状況が発生する可能性があり、キャリーが必要です。
//4.进位
for(int i=0;i<len;i++){
c[i+1] += c[i]/10;
c[i] %= 10;
}
これは今も同じで、キャリー後はオーバーフロー問題を考慮して出力を反転しています。
//6.考虑到数字溢出
if(a[len]){
len++;
}
//7.反向输出
for(int i=len-1;i>=0;i--){
cout<<a[i];
}
高精度 + 高精度の完全なコード:
/*
高精度的加法思想
1.把大数存到字符串;
2.字符串的每个字符数字都通过ASCII转换存到数组,
注意的是要低位存在数组开头:a[i] = s[len-i-1]-'0';
3.获取最大的数长度:max(len1,len2) ;
4.把a,b值加入到c数组: c[i] = a[i]+b[i];
5.c数组加法进位的算式:
① c[i+1] += c[i]/10;
② c[i] %= 10;
6.数字溢出,长度+1;
7.反向输出结果;
*/
#include<iostream>
#include<string>
using namespace std;
string s1,s2;
int a[10000],b[10000],c[100001];
int main(){
// 1.输入值,长度
cin>>s1>>s2;
int len1 = s1.size();
int len2 = s2.size();
// 2.把字符转为整数存到数组
// 注意要个位存到数组开头
for(int i=0;i<len1;i++){
a[i] = s1[len1-i-1]-'0';
}
for(int i=0;i<len2;i++){
b[i] = s2[len2-i-1]-'0';
}
// 3.获取最大的数。
int len = max(len1,len2);
// 对各个位数进行相加
for(int i=0;i<len;i++){
c[i]=a[i]+b[i];
}
//4.进位
for(int i=0;i<len;i++){
c[i+1] += c[i]/10;
c[i] %= 10;
}
//5.溢出
while(c[len]==0 && len>0){
len--;
}
if(c[len]>0){
len++;
}
//6.反向输出
for(int i=len-1;i>=0;i--){
cout<<c[i];
}
return 0;
}
4. 高精度減算
このように減算が必要です。2 つの数値の減算が 0 未満の場合、負の'-'符号が出力されるはずです。
高精度減算の考え方:
-
2 つの大きな数字を入力します。
-
サイズを決定するには、s1 が常に s2 より大きくなるように修正します。
-
長さを取得します。
-
文字を整数に変換します: a[i] = s1[len1-i-1]-'0';
-
減算演算:
① if(a[i]<b[i]){ a[i+1]–; //上位ビット – a[i]+=10; //下位ビット+10 } ② c[i] = a [i]-b[i]; -
先頭のゼロを削除します。
-
出力を反転します。
値を入力してください。最初に入力する値は減数を表し、2 番目の値は被減数を表します。減算では負の数が存在することがわかっているため、減数 < 被減数という状況を考慮する必要があります。つまり、減数の長さ < 被減数の長さ、または長さが等しい場合は、減数の値 < 被減数の値になります。次に、「-」記号を出力し、2 つの値を交換します。減数は常に被減数より大きいことを永続的に認識してください。!!
#include<iostream>
#include<string>
using namespace std;
string s1,s2;
int a[10000],b[10000],c[10000];
int main(){
// 1.输入值
cin>>s1>>s2;
// 2.判断大小,固定s1恒大于s2
if(s1.size()<s2.size() || s1.size()==s2.size() && s1<s2){
swap(s1,s2); //交换值
cout<<"-";
}
// 3.获取长度
int len1 = s1.size();
int len2 = s2.size();
// 4.字符变整数
for(int i=0;i<len1;i++){
a[i] = s1[len1-i-1]-'0';
}
for(int i=0;i<len2;i++){
b[i] = s2[len2-i-1]-'0';
}
整数に変換して配列に格納した後、引き算を行います 引き算のルールにより、引き算に足りない場合は+10を借用し、借用している場合は引き算を行う必要があります1. たとえば、1234-66。a[0] - b[0] < 0 の場合、+10、つまり a[0] + 10 を借用してから b[0] を減算する必要があり、それは a[0+1]– として借用されます。
//5.减法运算
for(int i=0;i<len1;i++){
if(a[i]<b[i]){
a[i+1]--; //被借位--
a[i]+=10; // 本位+10
}
c[i] = a[i]-b[i]; //相减结果存到数组c
}
123 -120 = 003、先頭のゼロは削除する必要があることに注意してください。そして逆に出力します。
//6.去除前导零
while(c[len1-1]==0 && len1>1){
len1--;
}
//7.反向输出
for(int i=len1-1;i>=0;i--){
cout<<c[i];
}
高精度減算の完全なコード:
/*
高精度减法的思想
1.输入大数;
2.判断大小,固定s1恒大于s2:
if(s1.size()<s2.size() || s1.size()==s2.size() && s1<s2){
swap(s1,s2); //交换值
cout<<"-";
}
3.获取长度;
4.字符变整数:a[i] = s1[len1-i-1]-'0';
5.减法运算:
if(a[i]<b[i]){
a[i+1]--; //上位--
a[i]+=10; // 本位+10
}
c[i] = a[i]-b[i];
6.去除前导零;
while(c[len1-1]==0 && len1>1){
len1--;
}
7.反向输出;
*/
#include<iostream>
#include<string>
using namespace std;
string s1,s2;
int a[10000],b[10000],c[10000];
int main(){
// 1.输入值
cin>>s1>>s2;
// 2.判断大小,固定s1恒大于s2
if(s1.size()<s2.size() || s1.size()==s2.size() && s1<s2){
swap(s1,s2); //交换值
cout<<"-";
}
// 3.获取长度
int len1 = s1.size();
int len2 = s2.size();
// 4.字符变整数
for(int i=0;i<len1;i++){
a[i] = s1[len1-i-1]-'0';
}
for(int i=0;i<len2;i++){
b[i] = s2[len2-i-1]-'0';
}
//5.减法运算
for(int i=0;i<len1;i++){
if(a[i]<b[i]){
a[i+1]--; //上位--
a[i]+=10; // 本位+10
}
c[i] = a[i]-b[i];
}
//6去除前导零
while(c[len1-1]==0 && len1>1){
len1--;
}
//7.反向输出
for(int i=len1-1;i>=0;i--){
cout<<c[i];
}
return 0;
}