大整数(高精度整数)概念、创建、输入、表示和四则运算(高精度的加减乘除)——附完整代码

文章目录

  • 1 大整数的存储
    • 1.1 概念
    • 1.2 存储结构
    • 1.3 读入
    • 1.4 比较大小
  • 2 大整数的四则运算
    • 2.1 高精度的加法
      • 2.1.1 思想
      • 2.1.1 模版代码
      • 2.1.2 加法示例
    • 2.2 高精度的减法
      • 2.2.1 思想
      • 2.2.2 模版代码
    • 2.3 高精度与低精度的乘法
      • 2.3.1思想
      • 2.3.2 模版代码
    • 2.4 高精度与低精度的除法
      • 2.4.1 思想
      • 2.3.2 模版代码
      • 2.3.3 高精度的除法模版
      • 2.3.4 高精度与高精度的乘法
      • 2.4.4 高精度与高精度的除法示例(被除数小于除数)
      • 2.4.5 保留合适位数的低精度除法
  • 3 完整示例代码

1 大整数的存储

1.1 概念

大整数:高精度整数,其含义是基本数据类型无法存储其精度的整数。

整数的高位存储在数组的高位,整数的低位存储在数组的低位。不反过来的原因是,在运算的时候都是从整数的低位到高位进行枚举,顺位存储和这种思维相合。

例如:235813 存储在数组中,即有d[0] = 3,d[1]= 1,d[2] = 8, d[3] = 5, d[4] =3, d[5] = 2;

注意: 把整数按字符串%s读入的时候,实际上是逆位存储的,即str[0] =‘2’, str[3] = ‘3’,str[5]= ‘3’,因此在读入之后,需要另存为至d[]数组的时候反转一下

为了方便获取大整数的长度,一般会定义一个int类变量len来记录其长度,并和d数组合成结构体:

1.2 存储结构

struct bign{
    int d[1000];
    int len;
    bign(){
        memset(d,0, sizeof(d));
        len = 0;
    }
};

初始化结构体,是为了减少在输入代码时,总是忘记初始化的问题。

struct bign
{
    int d[1000];
    int len;
    bign(){//构造函数(函数名与构造体相同,无返回值)
        memset(d, 0, sizeof(d));
        len = 0;
    }
};

1.3 读入

在输入大整数的时,一般都是先用字符串读入,然后再把字符串另存至bign结构体

bign change(char str[]){//将整数转换为bign
    bign a;
    a.len = strlen(str);//bign的长度就是字符串的长度
    for (int i = 0; i < a.len; ++i)
    {
       a.d[i] = str[a.len - i - 1] - '0';//逆序赋值
    }
    return a;
}

1.4 比较大小

比较两个bign变量的大小:
先判断两者的len大小,如果不想等,则以长的为大,如果相等,则从高位到低位进行比较,直到出现某一位不相等,就可以判断两个数的大小。

int campare(bign a, bign b){
    if(a.len > b.len){//a大
        return 1;
    }else if(a.len < b.len){//a小
        return -1;
    }else{//从高位往地位比较
        for (int i = a.len - 1; i >= 0; --i)
        {
            if(a.d[i] > b.d[i]){//只要有一位a大,则a大
                return 1;
            }else if(a.d[i] < b.d[i]){//只要有一位a小,则a小
                return -1;
            }
        }
        return 0;
    }
}

2 大整数的四则运算

2.1 高精度的加法

2.1.1 思想

根据整数的加法,可以得知:
对其中一位进行加法的步骤:将该位上的数组和进位相加,得到的结果取个位作为该位结果,取十位数作为新的进位。

注意:这样的写法条件:是两个对象都是非负数,如果有一方时负数,可以在转换到数组这一步时去掉符号,然后采用高精度减法;
如果两者都是负的,就都去掉负号后用高精度加法,最后再把负数加回去即可。

2.1.1 模版代码

bign add(bign a, bign b){
    bign c;
    int carry = 0;//进位
    for (int i = 0; i < a.len || i < b.len; ++i)//以较长的为界
    {
        int temp = a.d[i] + b.d[i] + carry;//两个对应位与进位相加
        carry = temp / 10;//十位为新的进位
        c.d[c.len++] = temp % 10;//各位为该位结果
    }
    if(carry != 0){//如果最后进位不为0,则直接赋值给结果的最高位
        c.d[c.len++] = carry;
    }
    return c;
}

2.1.2 加法示例

#include 
#include 

struct bign
{
    int d[1000];
    int len;
    bign(){//构造函数
        memset(d, 0, sizeof(d));
        len = 0;
    }
};

bign change(char str[]){//将整数转换为bign
    bign a;
    a.len = strlen(str);//bign的长度就是字符串的长度
    for (int i = 0; i < a.len; ++i)
    {
       a.d[i] = str[a.len - i - 1] - '0';//逆序赋值
    }
    return a;
}

int campare(bign a, bign b){
    if(a.len > b.len){//a大
        return 1;
    }else if(a.len < b.len){//a小
        return -1;
    }else{//从高位往地位比较
        for (int i = a.len - 1; i >= 0; --i)
        {
            if(a.d[i] > b.d[i]){//只要有一位a大,则a大
                return 1;
            }else if(a.d[i] < b.d[i]){//只要有一位a小,则a小
                return -1;
            }
        }
        return 0;
    }
}

bign add(bign a, bign b){
    bign c;
    int carry = 0;//进位
    for (int i = 0; i < a.len || i < b.len; ++i)//以较长的为界
    {
        int temp = a.d[i] + b.d[i] + carry;//两个对应位与进位相加
        carry = temp / 10;//十位为新的进位
        c.d[c.len++] = temp % 10;//各位为该位结果
    }
    if(carry != 0){//如果最后进位不为0,则直接赋值给结果的最高位
        c.d[c.len++] = carry;
    }
    return c;
}

void print(bign a){
    for (int i = a.len - 1; i >= 0; --i)//输出bign
    {
        printf("%d", a.d[i]);
    }
}


int main(int argc, char const *argv[])
{
    char str[1000],str2[1000];
    scanf("%s%s",str, str2);
    bign a = change(str);
    bign b = change(str2);
    print(add(a,b));
    return 0;
}

2.2 高精度的减法

2.2.1 思想

对于某一步,比较被减位和减位,如果不够减,则令被减位的高位减1、被减位加10再进行减法;如果够减,则直接减。
最后一步要注意减法后高位可能有多余的0,要忽略它们,但也要保证结果至少有一位

2.2.2 模版代码

bign sub(bign a, bign b){
    bign c;
    for (int i = 0; i < a.len || i < b.len; ++i)//以较长为边界
    {
        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];//减法结果为当前位结果
    }
    while(c.len - 1 >= 1 && c.d[c.len - 1] == 0){
        c.len--;//去除高位的0,至少还保留一位最低位
    }
    return c;

}

2.3 高精度与低精度的乘法

2.3.1思想

低精度就是可以用基本数据类型存储的数据,例如int型。

147 × 35 147\times35 147×35为例,这里147视为高精度bign类型,而35视为int类型。并在下面的过程中,始终将35看成一个整体。

147 × 35 2   4   5 140 35      5   1   4   5 \quad147\\ \frac{\times \quad35}{2\,4\,5}\\ 140\quad\\ \frac{35\quad\;\;}{5\,1\,4\,5} 147245×35140514535

步骤:

  • 1 7 × 35 = 245 7\times35=245 7×35=245,取个位数5作为该位结果,高位部分24作为进位;
  • 2 4 × 35 = 140 4\times35=140 4×35=140,加上进位24,去各位4作为该位结果,高位进位16作为进位;
  • 3 1 × 35 = 35 1\times35=35 1×35=35,加上进位16,得51,取个位数1作为该位结果,高位部分5作为进位;
  • 4 没得乘了,此时进位还不为0,把进位5直接作为结果的高位。
    最迂某一步来说这每一步结果:取bign的某位与int型整体相乘,在与进位相加,所得结果的个位数作为该位结果,高位部分作为新的进位。

2.3.2 模版代码

bign multi(bign a, int b){
    bign c;
    int carry = 0;//进位
    for (int i = 0; i < a.len; ++i)
    {
        int temp = 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;
}

2.4 高精度与低精度的除法

2.4.1 思想

除法的计算方法与小学的是相同的。
以1234/7为例:
   0176 2 1234 7    53    49    44    42         2 \quad\;0176\\\\ ^{2}\sqrt{1234}\\ \frac{7}{ \quad \;53 \quad}\\ \frac{\;49}{ \;\quad \quad44 \quad}\\ \frac{\;\quad42}{ \;\;\quad \quad\;2 \quad}\\ 017621234 5374449242
步骤:

  • 1 1与7比较,不够出,因此该位商0,余数为1;
  • 2 余数1与新位2组合成12,12和7比较,够除,商为1,余数为5;
  • 3 余数5与新位3组合成53,53与7比较,够除,商为7,余数为4;
  • 4 余数4与新位4组合成44,44与7比较,够除,商为6,余数为2。

归纳其中某一步的步骤:上一步的余数乘以10加上该步的位,得到该步临时的被除数,将其与除数比较:如果不够除,则该位的商为0;如果够除,则商即为对应的商,余数即为对应的余数。最后一步要注意减法后高位可能有多余的0,要忽略它,但也要保证结果至少有一位。

2.3.2 模版代码

bign divide(bign a, int b, int &r){//r为余数
    bign c;
    c.len = a.len;//被除数的每一位与商的每一位是一一对应的,因此先令长度相等
    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 = r %b;//获得新余数
        }
    }   
    while(c.len - 1 >= 1 && c.d[c.len - 1] == 0){
        c.len--;//去除高位0,同时至少保留一位最低位
    }
    return c;
}

2.3.3 高精度的除法模版

高精度的除法(a小于b,带小数):


int cmp(string a, string b){
    unsigned long i1 = a.find_first_not_of('0');
    unsigned long i2 = b.find_first_not_of('0');
    if(i1 == string::npos) a = '0';
    else a.substr(i1);
    if(i2 == string::npos) b = '0';
    else b.substr(i2);

    if(a.length() > b.length()) return 1;
    else if(a.length() < b.length()) return -1;
    else{                       //长度相等
        if(a < b) return -1;
        if(a > b) return 1;
        else return 0;
    }
}

//此处a一定大于等于b
string subtract(string a, string b){
    //完整减法里,a可以小于b,这结果为负数,交换ab进行下面的代码
    //反转
    reverse(a.begin(), a.end());
    reverse(b.begin(), b.end());
    //按位做减法
    for (int i = 0; i < b.length(); ++i)
    {
        if(a[i] >= b[i]){
            a[i] = a[i] - b[i] + '0';
        }else{//小了就要借位
            int k = 1;
            while(a[i + k] == '0') {//这里可以保证i+k这一位不是0
                a[i + k] = '9';
                k++;
            }

            a[i + k] = a[i + k] - '1' + '0';

            a[i] = (a[i] - '0' + 10) - (b[i] - '0')  + '0';
        }
    }
    reverse(a.begin(), a.end());
    if(a.find_first_not_of('0') == string::npos) return "0";
    return a.substr(a.find_first_not_of('0'));
}


//除法作为减法
string divide(string a, string b){
//只考虑 a < b 的情况
    string ans = "0.";
    //转化为减法
    for (int i = 0; i < 101; ++i)
    {
        a.append("0");
        int t = 0;
        while(cmp(a,b) >= 0){   //a > b
            a = subtract(a, b);  //不停地做减法
            t++;//记录减法做了多少次
        }
        string t_str;
        //i2s(t ,t_str);  //is2()是windows特有的库函数
        stringstream stream;
        stream << t;
        stream >> t_str;
        ans.append(t_str);
    }
    return ans;
}

2.3.4 高精度与高精度的乘法

#include 
#include 
#include 

using std::string;

int main(){
    char s[40],ss[40];
    int a[40],b[40],c[80];
    
    memset(a,0,sizeof(a)); //清零数组
    memset(b,0,sizeof(b));
    memset(c,0,sizeof(c)); //清零

    scanf("%s%s",s, ss);
    int len = strlen(s);
    int lenn = strlen(ss);

    for (int i = 0 ; i < len ; i++)    a[len - i - 1] = s[i] - '0';//将字符串转化为数组
    for (int i = 0 ; i < lenn ; i++)    b[lenn - i - 1] = ss[i] - '0';


    for (int i = 0 ; i < len ; i++)
        for (int j = 0 ; j < lenn ; j++)
            c[i + j] += a[i] * b[j]; //运算(这个就有一点复杂了)

    int l = len + lenn - 1; //l是结果的最高位数

    for (int i = 0 ; i < l ;i++)
    {
        c[i + 1] += c[i] / 10; //保证每一位的数都只有一位,并进位
        c[i] %= 10;
    }

    if (c[l] > 0) l++; //保证最高位数是对的

    while (c[l - 1] >= 10)
    {
        c[l] = c[l - 1] / 10;
        c[l - 1] %= 10;
        l++;
    }

    while (c[l - 1] == 0 && l > 1) l--; //while去零法


    for (int i = l - 1; i >= 0 ; i--) //输出结果
        printf("%d",c[i]);
        
    return  0;
}

2.4.4 高精度与高精度的除法示例(被除数小于除数)

#include 
#include 
#include 
#include 

using namespace std;

void i2s(int i, string& s){
    stringstream stream;
    stream << i;
    stream >> s;
}

string add(string a, string b){
    a = a.substr(a.find_first_not_of('0'));//去掉开头的零
    b = b.substr(b.find_first_not_of('0'));
    long long lenA = a.length();
    long long lenB = b.length();
    long long len = max(lenA,lenB) + 1;//有可能A、B长度相同,相加后进一位

    reverse(a.begin(), a.end());    //反转便于低位逐步求和
    reverse(b.begin(), b.end());

    string ans(len,'0'); //初始化答案为len长,全部为字符0
    for (int i = 0; i < lenA; ++i)//把a拷贝到ans中
    {
        ans[i] = a[i];
    }
    int temp = 0;       //temp是上一位相加后的进位
    for (int i = 0; i < len; ++i)
    {
        if(i < b.length()){
            temp += (ans[i] - '0') + (b[i] - '0');
        }else{
            temp += (ans[i] - '0');
        }
        ans[i] = temp % 10 + '0';
        temp /= 10;
    }
    reverse(ans.begin(), ans.end());
    return ans.substr(ans.find_first_not_of('0'));
}

int cmp(string a, string b){
    unsigned long i1 = a.find_first_not_of('0');
    unsigned long i2 = b.find_first_not_of('0');
    if(i1 == string::npos) a = '0';
    else a.substr(i1);
    if(i2 == string::npos) b = '0';
    else b.substr(i2);

    if(a.length() > b.length()) return 1;
    else if(a.length() < b.length()) return -1;
    else{                       //长度相等
        if(a < b) return -1;
        if(a > b) return 1;
        else return 0;
    }
}

//此处a一定大于等于b
string subtract(string a, string b){
    //完整减法里,a可以小于b,这结果为负数,交换ab进行下面的代码
    //反转
    reverse(a.begin(), a.end());
    reverse(b.begin(), b.end());
    //按位做减法
    for (int i = 0; i < b.length(); ++i)
    {
        if(a[i] >= b[i]){
            a[i] = a[i] - b[i] + '0';
        }else{//小了就要借位
            int k = 1;
            while(a[i + k] == '0') {//这里可以保证i+k这一位不是0
                a[i + k] = '9';
                k++;
            }

            a[i + k] = a[i + k] - '1' + '0';

            a[i] = (a[i] - '0' + 10) - (b[i] - '0')  + '0';
        }
    }
    reverse(a.begin(), a.end());
    if(a.find_first_not_of('0') == string::npos) return "0";
    return a.substr(a.find_first_not_of('0'));
}


//除法作为减法
string divide(string a, string b){
//只考虑 a < b 的情况
    string ans = "0.";
    //转化为减法
    for (int i = 0; i < 101; ++i)
    {
        a.append("0");
        int t = 0;
        while(cmp(a,b) >= 0){   //a > b
            a = subtract(a, b);  //不停地做减法
            t++;//记录减法做了多少次
        }
        string t_str;
        //i2s(t ,t_str);  //is2()是windows特有的库函数
        stringstream stream;
        stream << t;
        stream >> t_str;
        ans.append(t_str);
    }
    return ans;
}


int main(int argc, char const *argv[]){
    string s1, s2, ans;
    cin >> s1 >> s2;
    cout << divide(s1, s2) <<endl;

    return 0;
}

2.4.5 保留合适位数的低精度除法

#include
int main (){
    int n,a,b;
    while(~scanf("%d%d%d", &a, &b, &n)){
        printf("%d.", a/b);//计算整数部分
            while(n--){//计算小数部分
                a=(a-a/b*b)*10;//重新计算被除数
                printf("%d",a/b);
            }
            printf("\n");
    }
    //上面的极简形式
    //    for(;~scanf("%d%d%d",&a,&b,&n);printf("\n"))
//        for(printf("%d.",a/b);n>0;a=(a-a/b*b)*10,printf("%d",a/b),--n);
    return  0;
}

3 完整示例代码

#include 
#include 

struct bign
{
    int d[1000];
    int len;
    bign(){//构造函数
        memset(d, 0, sizeof(d));
        len = 0;
    }
};

bign change(char str[]){//将整数转换为bign
    bign a;
    a.len = strlen(str);//bign的长度就是字符串的长度
    for (int i = 0; i < a.len; ++i)
    {
       a.d[i] = str[a.len - i - 1] - '0';//逆序赋值
    }
    return a;
}

int campare(bign a, bign b){
    if(a.len > b.len){//a大
        return 1;
    }else if(a.len < b.len){//a小
        return -1;
    }else{//从高位往地位比较
        for (int i = a.len - 1; i >= 0; --i)
        {
            if(a.d[i] > b.d[i]){//只要有一位a大,则a大
                return 1;
            }else if(a.d[i] < b.d[i]){//只要有一位a小,则a小
                return -1;
            }
        }
        return 0;
    }
}

bign add(bign a, bign b){
    bign c;
    int carry = 0;//进位
    for (int i = 0; i < a.len || i < b.len; ++i)//以较长的为界
    {
        int temp = a.d[i] + b.d[i] + carry;//两个对应位与进位相加
        carry = temp / 10;//十位为新的进位
        c.d[c.len++] = temp % 10;//各位为该位结果
    }
    if(carry != 0){//如果最后进位不为0,则直接赋值给结果的最高位
        c.d[c.len++] = carry;
    }
    return c;
}

bign sub(bign a, bign b){
    bign c;
    for (int i = 0; i < a.len || i < b.len; ++i)//以较长为边界
    {
        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];//减法结果为当前位结果
    }
    while(c.len - 1 >= 1 && c.d[c.len - 1] == 0){
        c.len--;//去除高位的0,至少还保留一位最低位
    }
    return c;

}

bign multi(bign a, int b){
    bign c;
    int carry = 0;//进位
    for (int i = 0; i < a.len; ++i)
    {
        int temp = 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为余数
    bign c;
    c.len = a.len;//被除数的每一位与商的每一位是一一对应的,因此先令长度相等
    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 = r %b;//获得新余数
        }
    }   
    while(c.len - 1 >= 1 && c.d[c.len - 1] == 0){
        c.len--;//去除高位0,同时至少保留一位最低位
    }
    return c;
}

void print(bign a){
    for (int i = a.len - 1; i >= 0; --i)//输出bign
    {
        printf("%d", a.d[i]);
    }
}


int main(int argc, char const *argv[])
{
    char str[1000],str2[1000];
    scanf("%s%s",str, str2);
    bign a = change(str);
    bign b = change(str2);
    print(sub(a,b));
    return 0;
}

你可能感兴趣的:(算法)