算法学习笔记四 高精度

本文介绍了高精度计算的基本原理和实现方法,包括加法、减法、乘法和除法。通过字符串操作模拟大数运算,解决long long等类型无法处理的大型数值问题。此外,还讨论了在实际编程中遇到的性能优化挑战,如高精度加法可能导致的时间超限问题,并提出了压位高精加的概念作为优化策略。
摘要由CSDN通过智能技术生成

高精度计算

基本原理:对于很大很大的数,用longlong都处理不了的时候,就采用高精度计算方法,主要思想就是把数据存进——字符串/数组里,然后用加法减法的基本原理一位一位地加减,再把结果存进字符串/数组里。在这个过程中所有的大数都不再是一个数,而是一串字符。

一、高精度加法

(字符串做法)

#include<stdio.h>
#include<string>
#include<string.h>
#include<iostream>
using namespace std;

//高精度加法
//只能是两个正数相加
string add(string str1, string str2)//高精度加法
{
    string str;
    int len1 = str1.length();
    int len2 = str2.length();
   
    //前面补0,弄成长度相同
    if (len1 < len2)
    {
        for (int i = 1; i <= len2 - len1; i++)
            str1 = "0" + str1;    //在前面添0哦
    }
    else
    {
        for (int i = 1; i <= len1 - len2; i++)
            str2 = "0" + str2;
    }


    len1 = str1.length();   //现在是一样的

    int cf = 0;  //进位标志,最开始是0
    int temp;  //存每一次计算后的个位

    for (int i = len1 - 1; i >= 0; i--)   //从最后一位开始做加法
    {
        temp = str1[i] - '0' + str2[i] - '0' + cf;   //这里是强制转换,得到的是两数同一位的和+cf
        cf = temp / 10;   //前一位的进位标志,如果cf>10,说明要进位呀
        temp %= 10;   //进位之后的两数和的个位
        str = char(temp + '0') + str;   //再一次强制转换,把刚刚算出来的个位存进str里
    }
    if (cf != 0)  str = char(cf + '0') + str;  //全部循环完之后,查看最前面一位的进位标志是否为1
    return str;   //返回结果,是string格式的
}

int main()
{
    string a, b;
    cin >> a >> b;
    string out;
    out = add(a, b);    //调用,非常方便
    cout << out;
}

二、高精度减法

  • 输入两个正数,不规定顺序,在函数里面自己判断了顺序)
  • 和加法大同小异,但是这里因为会涉及到输出的数可能是00123,000,…这些情况,所以采取下面的函数:
    • find_first_not_of ( str ,num ) 函数 【还可以是last】
      从字符串第 num + 1 个字符开始查找第一个与str中的字符不匹配的字符,返回它的位置。缺省时从头开始。如果没找到就返回string::nops
    • str.erase ( a , b ) 函数
      删除 [ a , b )包含的字符
    • 组合使用:str.erase(0, str.find_first_not_of(‘0’)); 即可删掉00123前面的00.
    • 但是如果是000就会被全部删掉了,所以要在最后加个判断,如果str.empty(),就令输出为0.
#include<stdio.h>
#include<stdio.h>
#include<string>
#include<string.h>
#include<iostream>
using namespace std;

int len;

//高精度减法
string sub(string str1, string str2)
{
    string str;
    int len1 = str1.length();
    int len2 = str2.length();

    //前面补0,弄成长度相同
    if (len1 < len2)
    {
        for (int i = 1; i <= len2 - len1; i++)
            str1 = "0" + str1;    //在前面添0哦
    }
    else
    {
        for (int i = 1; i <= len1 - len2; i++)
            str2 = "0" + str2;
    }

    len = str1.length();   //现在是一样的

    //补完之后判断一下大小,存进a和b里
    string a, b;
    int flag = 0;   // 正负号标志
    a = str1;
    b = str2;
    for (int i = 0; i < len; i++)
    {
        if (str1[i] > str2[i])
        {
            a = str1;
            b = str2;
            break;
        }
        if (str1[i] < str2[i])
        {
            a = str2;
            b = str1;
            flag = 1;  //表示结果是一个负数哦,要记得在后面填上-
            break;
        }
    }

    int cf = 0;  //进位标志,最开始是0
    int temp;  //存每一次计算后的个位

    for (int i = len - 1; i >= 0; i--)   //从最后一位开始做减法
    {
        temp = (a[i] - '0') + 10 - (b[i] - '0') - cf;   //这里是强制转换,得到的是两数同一位的和+cf
        cf = temp >= 10 ? 0 : 1;   //前一位的借位标志,如果cf>10,说明不用借位呀
        temp %= 10;   //两数差的个位
        str = char(temp + '0') + str;   //再一次强制转换,把刚刚算出来的个位存进str里
    }
    str.erase(0, str.find_first_not_of('0'));
    if (flag == 1)str = "-" + str;
    return str;   //返回结果,是string格式的
}

int main()
{
    string a, b;
    cin >> a >> b;
    string out;
    out = sub(a, b);    //调用,非常方便
    if (out.empty()) cout << 0;
    else cout << out;
}

三、高精度乘法

原理就是str1与str2的每一位相乘,得到str2的位数的个数的tempstr(要在后面依次添0、1、2…个0),然后调用高精加,就可以啦!!

#include<stdio.h>
#include<string>
#include<string.h>
#include<iostream>
using namespace std;

int len;

//高精度加法
//只能是两个正数相加
string add(string str1, string str2)//高精度加法
{
    string str;
    int len1 = str1.length();
    int len2 = str2.length();

    //前面补0,弄成长度相同
    if (len1 < len2)
    {
        for (int i = 1; i <= len2 - len1; i++)
            str1 = "0" + str1;    //在前面添0哦
    }
    else
    {
        for (int i = 1; i <= len1 - len2; i++)
            str2 = "0" + str2;
    }


    len1 = str1.length();   //现在是一样的

    int cf = 0;  //进位标志,最开始是0
    int temp;  //存每一次计算后的个位

    for (int i = len1 - 1; i >= 0; i--)   //从最后一位开始做加法
    {
        temp = str1[i] - '0' + str2[i] - '0' + cf;   //这里是强制转换,得到的是两数同一位的和+cf
        cf = temp / 10;   //前一位的进位标志,如果cf>10,说明要进位呀
        temp %= 10;   //进位之后的两数和的个位
        str = char(temp + '0') + str;   //再一次强制转换,把刚刚算出来的个位存进str里
    }
    if (cf != 0)  str = char(cf + '0') + str;  //全部循环完之后,查看最前面一位的进位标志是否为1
    return str;   //返回结果,是string格式的
}

//高精度乘法
//只能是两个正数相乘(会用到高精度加)
string mul(string str1, string str2)
{
    string str;
    int len1 = str1.length();
    int len2 = str2.length();

    string tempstr;
    for (int i = len2 - 1; i >= 0; i--)
    {
        tempstr = "";   //tempstr是表示乘法算出来的中间的值,str2有多少位,就有多少个tempstr,所以每次都要重新定义
        int temp = str2[i] - '0';   //取str2的最后一位的值
        int t = 0;
        int cf = 0;
        if (temp != 0)  //如果这一位是0,当然不需要计算了
        {
            for (int j = 1; j <= len2 - 1 - i; j++)   //注意,除了第一个,要在tempstr后面添1个、2个...0
                tempstr += "0";
            for (int j = len1 - 1; j >= 0; j--)   //把str1拿来进行计算
            {
                t = (temp * (str1[j] - '0') + cf) % 10;    //相乘+进位,再取余,得到个位
                cf = (temp * (str1[j] - '0') + cf) / 10;    //得到进位
                tempstr = char(t + '0') + tempstr;   //在tempstr前面加上这一位的个位
            }
            if (cf != 0) tempstr = char(cf + '0') + tempstr;
        }
        str = add(str, tempstr);  //这是乘法的步骤,就是吧tempstr相加(已经在后面添过0了)
    }

    str.erase(0, str.find_first_not_of('0'));   //保险起见,删除掉str第一个非0数字前面的所有0
    return str;
}

int main()
{
    string a, b;
    cin >> a >> b;
    if (a == "0" || b == "0")   //防止有一个为0的时候计算出错...(本来不想写的,谁会用0去乘还要用高精乘啊?结果提交居然WA了)
        cout << 0;
    else
    {
        string out;
        out = mul(a, b);    //调用,非常方便
        cout << out;
    }
}

四、高精度除法

摆一个函数在这里,会用到高精加和高精乘

//高精度除法
//两个正数相除,商为quotient,余数为residue
//需要高精度减法和乘法
void div(string str1,string str2,string &quotient,string &residue)
{
    quotient=residue="";//清空
    if(str2=="0")//判断除数是否为0
    {
        quotient=residue="ERROR";
        return;
    }
    if(str1=="0")//判断被除数是否为0
    {
        quotient=residue="0";
        return;
    }
    int res=compare(str1,str2);
    if(res<0)
    {
        quotient="0";
        residue=str1;
        return;
    }
    else if(res==0)
    {
        quotient="1";
        residue="0";
        return;
    }
    else
    {
        int len1=str1.length();
        int len2=str2.length();
        string tempstr;
        tempstr.append(str1,0,len2-1);
        for(int i=len2-1;i<len1;i++)
        {
            tempstr=tempstr+str1[i];
            tempstr.erase(0,tempstr.find_first_not_of('0'));
            if(tempstr.empty())
              tempstr="0";
            for(char ch='9';ch>='0';ch--)//试商
            {
                string str,tmp;
                str=str+ch;
                tmp=mul(str2,str);
                if(compare(tmp,tempstr)<=0)//试商成功
                {
                    quotient=quotient+ch;
                    tempstr=sub(tempstr,tmp);
                    break;
                }
            }
        }
        residue=tempstr;
    }
    quotient.erase(0,quotient.find_first_not_of('0'));
    if(quotient.empty()) quotient="0";
}

五、一道例题

在这里插入图片描述

  • 分析:
  1. 对于n可能达到50的情况,稍微估算就知道需要用到高精度计算。题目也很好理解,就是不断的乘和加。
  2. 这里沿用了上面高精加和高精乘的函数,仅仅是进行了调用,这样虽然理解比较简单但是时间会多一些,更好的方法是在字符串计算的时候就进行阶乘求和这一步。换句话说,就是实现多个数字的高精加和高精乘,这个之后如果有缘还会进行完善。
  3. int转字符串的做法:
    std::string in = std::to_string(1005);
    s
#include<stdio.h>
#include<string>
#include<string.h>
#include<iostream>
using namespace std;

int len;

//高精度加法
//只能是两个正数相加
string add(string str1, string str2)//高精度加法
{
    string str;
    int len1 = str1.length();
    int len2 = str2.length();

    //前面补0,弄成长度相同
    if (len1 < len2)
    {
        for (int i = 1; i <= len2 - len1; i++)
            str1 = "0" + str1;    //在前面添0哦
    }
    else
    {
        for (int i = 1; i <= len1 - len2; i++)
            str2 = "0" + str2;
    }


    len1 = str1.length();   //现在是一样的

    int cf = 0;  //进位标志,最开始是0
    int temp;  //存每一次计算后的个位

    for (int i = len1 - 1; i >= 0; i--)   //从最后一位开始做加法
    {
        temp = str1[i] - '0' + str2[i] - '0' + cf;   //这里是强制转换,得到的是两数同一位的和+cf
        cf = temp / 10;   //前一位的进位标志,如果cf>10,说明要进位呀
        temp %= 10;   //进位之后的两数和的个位
        str = char(temp + '0') + str;   //再一次强制转换,把刚刚算出来的个位存进str里
    }
    if (cf != 0)  str = char(cf + '0') + str;  //全部循环完之后,查看最前面一位的进位标志是否为1
    return str;   //返回结果,是string格式的
}

//高精度乘法
//只能是两个正数相乘(会用到高精度加)
string mul(string str1, string str2)
{
    string str;
    int len1 = str1.length();
    int len2 = str2.length();

    string tempstr;
    for (int i = len2 - 1; i >= 0; i--)
    {
        tempstr = "";   //tempstr是表示乘法算出来的中间的值,str2有多少位,就有多少个tempstr,所以每次都要重新定义
        int temp = str2[i] - '0';   //取str2的最后一位的值
        int t = 0;
        int cf = 0;
        if (temp != 0)  //如果这一位是0,当然不需要计算了
        {
            for (int j = 1; j <= len2 - 1 - i; j++)   //注意,除了第一个,要在tempstr后面添1个、2个...0
                tempstr += "0";
            for (int j = len1 - 1; j >= 0; j--)   //把str1拿来进行计算
            {
                t = (temp * (str1[j] - '0') + cf) % 10;    //相乘+进位,再取余,得到个位
                cf = (temp * (str1[j] - '0') + cf) / 10;    //得到进位
                tempstr = char(t + '0') + tempstr;   //在tempstr前面加上这一位的个位
            }
            if (cf != 0) tempstr = char(cf + '0') + tempstr;
        }
        str = add(str, tempstr);  //这是乘法的步骤,就是吧tempstr相加(已经在后面添过0了)
    }

    str.erase(0, str.find_first_not_of('0'));   //保险起见,删除掉str第一个非0数字前面的所有0
    return str;
}

//阶乘
string mull(int n)
{
    if (n == 1)
        return "1";
    if (n == 2)
        return "2";
    string tmp = "2";  //结果,最先是2的阶乘
    if (n >= 3)
    {
        for (int i = 3; i <= n; i++)
        {
            //把自身存进in里
            std::string in = std::to_string(i);
            tmp = mul(tmp, in);  
        }
    }
    return tmp;
}

//阶乘求和
string mull_add(int n)
{
    if (n == 1)
        return "1";
    string out = "1";
    if (n > 1)
    {
        for (int i = 2; i <= n; i++)
        {
            string a = mull(i);   //求一下自身的阶乘
            out = add(a, out);   //求和
        }
    }
    return out;
}

int main()
{
    int n;
    cin >> n;
    string out;
    out = mull_add(n);    //调用,非常方便
    cout << out;
}

六、当高精度TLE了…

一道简单的例题
在这里插入图片描述
分析很简单,就是类似斐波那契数列的递归,但是因为数据很大Nmax=5000,所以理所当然想到需要用到高精。
首先来一种粗暴的高精加的做法(下面已经是改进过的了,但是仍然TLE了,…当N比较小的时候是可以的,但是一旦N超过30就已经跑不动了…我觉得这种方法其实是有可取之处的,并不是暴力地每次都调用高精加,于是我把代码先放在这里吧,如果能在这个基础上修改应该会比较快):

#include<stdio.h>
#include<string>
#include<string.h>
#include<iostream>
using namespace std;

int len;

//高精度加法
//只能是两个正数相加
string add(string str1, string str2)//高精度加法
{
    string str;
    int len1 = str1.length();
    int len2 = str2.length();

    //前面补0,弄成长度相同
    if (len1 < len2)
    {
        for (int i = 1; i <= len2 - len1; i++)
            str1 = "0" + str1;    //在前面添0哦
    }
    else
    {
        for (int i = 1; i <= len1 - len2; i++)
            str2 = "0" + str2;
    }


    len1 = str1.length();   //现在是一样的

    int cf = 0;  //进位标志,最开始是0
    int temp;  //存每一次计算后的个位

    for (int i = len1 - 1; i >= 0; i--)   //从最后一位开始做加法
    {
        temp = str1[i] - '0' + str2[i] - '0' + cf;   //这里是强制转换,得到的是两数同一位的和+cf
        cf = temp / 10;   //前一位的进位标志,如果cf>10,说明要进位呀
        temp %= 10;   //进位之后的两数和的个位
        str = char(temp + '0') + str;   //再一次强制转换,把刚刚算出来的个位存进str里
    }
    if (cf != 0)  str = char(cf + '0') + str;  //全部循环完之后,查看最前面一位的进位标志是否为1
    return str;   //返回结果,是string格式的
}

//高精度乘法
//只能是两个正数相乘(会用到高精度加)
string mul(string str1, string str2)
{
    string str;
    int len1 = str1.length();
    int len2 = str2.length();

    string tempstr;
    for (int i = len2 - 1; i >= 0; i--)
    {
        tempstr = "";   //tempstr是表示乘法算出来的中间的值,str2有多少位,就有多少个tempstr,所以每次都要重新定义
        int temp = str2[i] - '0';   //取str2的最后一位的值
        int t = 0;
        int cf = 0;
        if (temp != 0)  //如果这一位是0,当然不需要计算了
        {
            for (int j = 1; j <= len2 - 1 - i; j++)   //注意,除了第一个,要在tempstr后面添1个、2个...0
                tempstr += "0";
            for (int j = len1 - 1; j >= 0; j--)   //把str1拿来进行计算
            {
                t = (temp * (str1[j] - '0') + cf) % 10;    //相乘+进位,再取余,得到个位
                cf = (temp * (str1[j] - '0') + cf) / 10;    //得到进位
                tempstr = char(t + '0') + tempstr;   //在tempstr前面加上这一位的个位
            }
            if (cf != 0) tempstr = char(cf + '0') + tempstr;
        }
        str = add(str, tempstr);  //这是乘法的步骤,就是吧tempstr相加(已经在后面添过0了)
    }

    str.erase(0, str.find_first_not_of('0'));   //保险起见,删除掉str第一个非0数字前面的所有0
    return str;
}


//数阶梯,n=3的时候的方法是n=1和n=2的和;n=4的时候的方法是n=3和n=2的和....
//即n的方法是n-1和n-2的和

string add1(string in)   //只加1的高精加法
{
    string out = "";
    int cf = 0;
    int len = in.length();
    for (int i = len - 1; i >= 0; i--)
    {
        int temp;
        if (i == len - 1) temp = int(in[i] - '0') + 1;
        else temp = int(in[i] - '0') + cf;

        if (temp < 10)
        {
            cf = 0;
            out = char(temp + '0') + out;
        }
        else
        {
            cf = 1;
            out = char(temp % 10 + '0') + out;
        }
    }
    if (cf == 1) out = "1" + out;
    return out;
}

string out1 = "0";
string out2 = "0";

void feib(int n)   //统计1和2各自出现多少次
{
    if (n == 1)
    {
        out1 = add1(out1);
    }
    else if (n == 2)
    {
        out2 = add1(out2);
    }
    else 
    {
        feib(n - 1);
        feib(n - 2);
    }
}



int main()
{
    int n;
    cin >> n;
    feib(n);
    string temp = mul("2", out2);   //2出现out2次,每次有2种
    string ans = add(out1, temp);   //1和2相加
    cout << ans;
}

压位高精加

概念
压位高精加,顾名思义,就是将高精度数组的每个位存多点。

优点
因为高精度其实是模拟竖式计算的方法,一个变量存一位,也就是0…9,这样有点浪费时间,而当n,m太大时则会超时,这个时候,就引申除了压位高精加。

实现
对高精度略作修改,将原来的%10、/10,改成了%100000000,/100000000,但是输出的时候要记得处理前面的0,例如 9999999999999999+2=10000000000000001

在数组里面它是这样的:101
所以,要判断当它不是第一个数字的时候,要添加0,至于添加几个,则看它有多大。判断可以直接暴力,也可以数学方法(log)去算,从而添加0,例如1=7个0+1 10=6个0+1,100=5个0+1……以此类推。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值