5.2 高精度运算

版权声明:沉迷代码,难以自拔 https://blog.csdn.net/qq_33846054/article/details/51045773

问:什么是大数和高精度?(big number)
答:超过浮点数取值范围的数据,比如一个1000位的整数,无法用常规方法来处理。这些精度很高的数据通常称为高精度数,或称为大数。 高精度数的运算只能用本章介绍的高精度数计算方法来处理。

5.2.1 小学生算术

http://acm.nyist.net/JudgeOnline/problem.php?pid=74
时间限制:3000 ms | 内存限制:65535 KB
难度:1
描述
很多小学生在学习加法时,发现“进位”特别容易出错。你的任务是计算两个三位数在相加时需要多少次进位。你编制的程序应当可以连续处理多组数据,直到读到两个0(这是输入结束标记)。
输入
输入两个正整数m,n.(m,n,都是三位数)
输出
输出m,n,相加时需要进位多少次。
样例输入
123 456
555 555
123 594
0 0
样例输出
0
3
1

我的代码:比较清晰但也比较啰嗦

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

int main()
{
    int m,n,cf,sum;//输入整数不超过9个数字,整型刚刚好
    cin>>m>>n;
    while (m!=0&&n!=0)
    {   cf=0;sum=0;
        while (m>0&&n>0)
        {           
            if ((m%10+n%10+cf)>9)
           {
             cf=1;
             sum++;
           }
           else
            cf=0;
            m=m/10;
            n=n/10;
        }
        cout<<sum<<endl;

        cin>>m>>n;
    }


}

课本代码:机智地运用了三目条件判断语句

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

int main()
{
    int m,n;
    while (cin>>m>>n)
    {
        if (!m&&!n)
            return 0;//当当a=b=0时条件为1
        int cf=0,sum=0;
        while (m>0&&n>0)
        {
            cf=(m%10+n%10+cf)>9?1:0;
            sum+=cf;
            m/=10;
            n/=10;
        }
        cout<<sum<<endl;
    }

    return 0;
}

5.2.2 阶乘的精确值

输入不超过1000的正整数n,输出n的阶乘的精确结果

样例输入:30

样例输出:265252859812191058636308480000000

思路:

1)为了保存结果,需要分析1000!有多大。用计算器算一算不难知道,1000!约等于4*10^2567,也就是这个上限数字大约有3000位,因此可以用一个3000个元素的数组f保存,每个数组元素仅保存一个10以内的数字,输出时遍历数组即可把该数字打印出来。为方便起见,让f【0】保存个位,f【1】保存十位等等。
2)外循环N次,每次3000位各位都要乘以I,依次进位
3)输出时从max开始,先找到第一个不为零的数,再打印输出。

解法(1)

#include <iostream>
#include <iomanip>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

using namespace std;

int main()
{
    const int maxn=3000;
    int n,cf,k;
    int f[maxn+1];

    while(cin>>n)
    {
        memset(f,0,sizeof(f));//数组初始化
        f[0]=1;//第一位set1
        for (int i=2; i<=n; i++)//I代表每次阶乘的数
        {
            cf=0;//代表进位
            for(int j=0; j<=maxn; j++)//3000位数组都要乘以I
            {
                int s=f[j]*i+cf;
                f[j]=s%10;
                cf=s/10;
            }
        }
        for(k=maxn; k>=0; k--)
            if (f[k]!=0)
                break;//找到第一个不为零的数
        for(int j=k; j>=0; j--)
            cout<<f[j];
            cout<<endl;
    }


}

解法(2): 还能不能更快?
思路:数组元素为INT,最多能保存9位数字,现在我们每5位进一位可以吗?

1)

int s=f[j]*i+cf;
f[j]=s%100000;
cf=s/100000;

2)但是输出时必须要考虑这个五位数的前几位为0怎么办?必须要凑足五位。

printf(%05d”,f[j]);//5位的数字,不足前面添0

完整代码:

#include <iostream>
#include <iomanip>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

using namespace std;

int main()
{
    const int maxn=600;
    int n,cf,k;
    int f[maxn+1];

    while(cin>>n)
    {
        memset(f,0,sizeof(f));//数组初始化
        f[0]=1;//第一位set1
        for (int i=2; i<=n; i++)//I代表每次阶乘的数
        {
            cf=0;//代表进位
            for(int j=0; j<=maxn; j++)//3000位数组都要乘以I
            {
                int s=f[j]*i+cf;
                f[j]=s%100000;
                cf=s/100000;
            }
        }
        for(k=maxn; k>=0; k--)
            if (f[k]!=0)
                break;//找到第一个不为零的数
                cout<<f[k];//第一个数不需要补全0
        for(int j=k-1; j>=0; j--)
            printf ("%05d",f[j]);
            cout<<endl;
    }


}

解法(3)字符数组

本题可以采用字符数组来存储读入的两个加数。因为一个big number可以作为一个很长很长的字符串“123654789”保存到char类型的数组里,然后对两个字符数据进行加法、进位。便不用考虑整型还是长整型能不能放的下这个big number的问题了!

对两个加数进行加法运算时,要注意以下两点:
(1)在进行加法时,要得到数字字符对应的数值,方法是将数字字符减去数字字符“0”
(2)从两个加数的最低位开始按位求和,如果和大于9,则会向前一位进位。要注意某一个加数的每一位都运算完毕,但另一个加数还有若干位没有运算完毕的情形。999586 + 798,这两个加数分为有6位和3位数。当第2个加数的最低3位数都运算完毕时,还会向前面进位,这时第1个加数还有3位没有运算完毕,由于进位的存在,这3位在运算时都还会产生进位。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//计算两数相加的进位次数
int main( )
{
    char add1[11], add2[11];	//读入的两个加数
    while( scanf("%s%s", add1, add2) )
    {
        if( !strcmp(add1,"0") && !strcmp(add2,"0") )  break;
        
        int carry = 0;	//进位次数
        int i1 = strlen(add1) - 1;//指向这个big number的个位
        int i2 = strlen(add2) - 1;
        int C = 0;		//进位
        while( i1>=0 && i2>=0 )	
        {//从两个数的最后一位开始相加,在撸完一个串儿或两个串儿同时撸完的时候循环结束
            if( add1[i1]-'0'+add2[i2]-'0'+C>9 )
            {
                carry++;
                C = 1;
            }
            else  C = 0;
            i1--;
            i2--;
        }
        while( i1>=0 )	//如果第1个串儿没撸完
        {
            if( add1[i1]-'0'+C>9 )
            {
                carry++;
                C = 1;
            }
            else  C = 0;
            i1--;
        }
        while( i2>=0 )	//如果第2个串儿没撸完
        {
            if( add2[i2]-'0'+C>9 )
            {
                carry++;
                C = 1;
            }
            else  C = 0;
            i2--;
        }
        if( carry>1 )  
            printf( "%d carry operations.\n", carry );
        else if( carry==1 ) 
         printf( "%d carry operation.\n", carry );
        else  printf( "No carry operation.\n" );
    }
    return 0;
}

另一种计算N!的方式——log展开,求数的位数

http://acm.nefu.edu.cn/JudgeOnline/problemShow.php?problem_id=65

Problem:65

Time Limit:1000ms

Memory Limit:65536K

Description

N! (N的阶乘) 是非常大的数,计算公式为:N! = N * (N - 1) * (N - 2) * … * 2 * 1)。现在需要知道N!有多少(十进制)位。

Input

每行输入1个正整数N。0 < N < 1000000

Output

对于每个N,输出N!的(十进制)位数。

Sample Input

1
3
32000
1000000

Sample Output

1
1
130271
5565709

思路:
1) 所谓N!的(十进制)位数,就是Lg(N!)+1
2) 根据数学公式,有 N!=12……*N Lg(N!)=lg(2)+……+lg(N),即转化成循环相加的运算。
3) 注意log10(x)函数返回值类型为double,sum必须定义为1.0,如果用int误差超级大。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
using namespace std;
	
int main()
{
    int n,i;//N<100000用INT没问题
    double sum;//注意这里的sum一定要定义成double类型的
    while(scanf("%ld",&n)!=EOF)
    {
        sum=1.0;
        for(i=2; i<=n; i++) sum+=log10(i);
        printf("%ld\n",(int)sum);
    }
}

解法(2)记忆化搜索

Hdu 1018 Big Number Pku 1423 Big Number 用1+lg(1)+lg(2)+…+lg(n)的方法,在PKU上超时了。
原因:
1 <= m <= 10^7 规模很大。这道题目给出的限时是1000ms。假如对于给出的每一个数据,我们都慢慢地将它从log10(1) 慢慢加到log10(N),绝对会超时。因为里面有大量重复的运算,例如log10(1),如果有100组数据,那么它的值就会被计算100次。
于是,我们想到,能否把所有计算过的log10值保存起来,然后若遇到重复的,就从这个结果数组里面提取就可以了,不用再计算。这个方法很好,但是题目给出的数据规模比较大,开一个10^7大的double数组,绝对会Memory Excceed Limit。

思路:
1)
假设问题给出的C组数据,是从小往大排列的,例如,给出三个数据,10,20,30,那么我们可以想到,计算log10(20!)的时候,我们是可以利用long10(10!)的结果。因为: log10(10!) = log10(1) + log10(2) + log10(3) + … + log10(9) +log10(10) log10(20!) = log10(1) + log10(2) + log10(3) + … + log10(9) +log10(10) + log10(11) + log10(12) +… +log10(19) + log10(20) 容易看出: log10(20!) = log10(10!) + log10(11) + log10(12) +… +log10(19) + log10(20) ; 同理: log10(30!) = log10(10!) + log10(21) + log10(22) +… +log10(29) + log10(30) ;
2)
题目没有说给出的数据是有序的,但是我们可以通过排序使之有序。至于排序,那么当然是系统的sort()函数了。
3)为什么要定义一个结构体?为了保留每个输入数据的结果和排序之前的序号。

typedef struct 
{     
int num;     
int id;     
double result;
 } data; 

我试着写了一个代码,但是怎么巨长无比?而且输入10,20出现的结果是7,18,用stirling公式算的等于7,19。
黑人问号脸?????

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <algorithm>
using namespace std;

typedef struct//定义结构体,简化版名称为ata
{
    int num;//数据
    int id;//序号
    double result;
} data;

int dig(int n)
{
    int i;
    double sum=1.0;
        for (i=2;i<=n;i++)
        {
            sum+=log10(i);
        }
        return sum;
}
int dig2(int m,int n)
{
    int i;
    double sum=0.0;
        for (i=m;i<=n;i++)
        {
            sum+=log10(i);
        }
        return sum;
}
void asort(data a[10],int n)
{
    int i,j;
    data temp;
    for (i=0;i<n-1;i++)
        for (j=i;j<n-1-i;j++)
    {
        if (a[j].num>a[j+1].num)
        {
            temp=a[j];
            a[j]=a[j+1];
            a[j+1]=temp;
        }
    }
}
void re(data a[],int n)
{
    int i,j;
    data temp;
    for (i=0;i<n-1;i++)
        for (j=i;j<n-1-i;j++)
    {
        if (a[j].id>a[j+1].id)
        {
            temp=a[j];
            a[j]=a[j+1];
            a[j+1]=temp;
        }
    }
}
int main()
{
   int n,i;
   data a[10];
   cin>>n;

       for (i=0;i<n;i++)
       {
            cin>>a[i].num;
            a[i].id=i;
       }

    asort(a,n);
     a[0].result=(int)dig(a[0].num);
     for (i=1;i<n;i++)
     {
         a[i].result=(int)(a[i-1].result+dig2(a[i].num-a[i-1].num+1,a[i].num));

     }
     re(a,n);
     for (i=0;i<n;i++)
        cout<<a[i].result<<endl;



}

解法(3)Stirling公式
这里写图片描述

思路:
1) 易知整数n的位数为[lg10(n)]+1。
用Stirling公式计算n!结果的位数时,可以两边取对数,得:
log10(n!) = log10(2PIn)/2+nlog10(n/E);
故n!的位数为 res=log10(2
PIn)/2+nlog10(n/E)+1

#include <iostream> 
#include <math.h> 
using namespace std; 
//注意 e和pi的值要精确 

const double e=2.7182818284590452354, pi = 3.141592653589793239;  
double str_ling(int n) 
{ 
return 0.5*log10(2*pi*n)+n*log10(n/e);     
} 
int main() 
{  
int t,m;  
cin>>t;  
while(t--)  
{   
cin>> m;   
cout<<(int)str_ling(m)+1<<endl;             }       return 0;    
} 

高精度计算的基本思路是:
数组存储参与运算的数的每一位,在运算时以数组元素所表示的位为单位进行运算。可以采用字符数组,也可以采用整数数组,到底采用字符数组还是整数数组更方便,应试具体题目而定。

skew二进制(Skew Binary)
题目来源:Mid-Central USA 1997
题号:ZOJ1712,POJ1565
题目描述:
在十进制里,第k位数字的权值是10k。(每位数字的顺序是从右到左的,最低位,也就是最右边的位,称为第0位)。例如:
81307(10) = 8 * 104 + 1 * 103 + 3 * 102 + 0 * 101 + 7 * 100
= 80000 + 1000 + 300 + 0 + 7 = 81307.
而在二进制里,第k位的权值为2k。例如:
10011(2) = 1 * 24 + 0 * 23 + 0 * 22 + 1 * 21 + 1 * 20
= 16 + 0 + 0 + 2 + 1 = 19.
在skew二进制里,第k位的权值为2(k+1) - 1,skew二进制的数码为0和1,最低的非0位可以取2。例如:
10120(skew2)
= 1 * (25 - 1) + 0 * (24 - 1) + 1 * (23 - 1) + 2 * (22 - 1) + 0 * (21 - 1)
= 31 + 0 + 7 + 6 + 0 = 44.
skew二进制的前10个数为0,1,2,10,11,12,20,100,101和102。

输入描述:
输入文件包含若干行,每行为一个整数n。n = 0代表输入结束。除此之外,n是skew二进制下的一个非负整数。

输出描述:
对输入文件中的每个skew二进制数,输出对应的十进制数。输入文件中n最大值对应到十进制为231 – 1 = 2147483647。

样例输入:
10120
200000000000000000000000000000
10
1000000000000000000000000000000
0

样例输出:
44
2147483646
3
2147483647

思路:
1)对输入文件中的skew二进制数,不能采用整数形式(int)读入,必须采用字符数组。那么需要定义多长的字符数组?
2)正如样例输入数据所示,十进制数2147483647对应的skew二进制数为:
1000000000000000000000000000000。
因此存储输入文件中的skew二进制数可以采用长度为40的字符数组。
3)在把skew二进制数转换成十进制时,只需把每位按权值展开求和即可。采用字符数组存储高精度数,要求高精度数的总位数及取出每位上的数码都是很方便的。

完整代码:

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

int main( )
{
   char n[40];
   int k,i,j;
   int  sum;
   cin>>n;
   while ((n-'0')!=0)
   {
       sum=0;
       k=strlen(n);
       for (i=k-1,j=0;i>=0;i--,j++)
       {
          sum+=(pow(2,j+1)-1)*(n[i]-'0');
       }
       cout<<sum<<endl;
         cin>>n;
   }

    return 0;
}

高精度数的基本运算——加法、乘法和除法

高精度数的加法

例整数探究(Integer Inquiry)
题目来源:Central Europe 2000
题号:pku1503 hdu1047

http://acm.hdu.edu.cn/showproblem.php?pid=1047

Integer Inquiry
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 19343 Accepted Submission(s): 5063

Problem Description
One of the first users of BIT’s new supercomputer was Chip Diller. He extended his exploration of powers of 3 to go from 0 to 333 and he explored taking various sums of those numbers.
This supercomputer is great,'' remarked Chip.I only wish Timothy were here to see these results.’’ (Chip moved to a new apartment, once one became available on the third floor of the Lemon Sky apartments on Third Street.)

Input
The input will consist of at most 100 lines of text, each of which contains a single VeryLongInteger. Each VeryLongInteger will be 100 or fewer characters in length, and will only contain digits (no VeryLongInteger will be negative).

The final input line will contain a single zero on a line by itself.

Output
Your program should output the sum of the VeryLongIntegers given in the input.

This problem contains multiple test cases!

The first line of a multiple input is an integer N, then a blank line followed by N input blocks. Each input block is in the format indicated in the problem description. There is a blank line between input blocks.

The output format consists of N output blocks. There is a blank line between output blocks.

Sample Input
1

123456789012345678901234567890
123456789012345678901234567890
123456789012345678901234567890
0

Sample Output
370370367037037036703703703670

中文版:
输入描述:
输入文件的第1行为一个整数N,表示输入文件中接下来有N组数据。每组数据最多包含100行。每一行由一个非常长的十进制整数组成,这个整数的长度不会超过100个字符而且只包含数字,每组数据的最后一行为0,表示这组数据结束。
每组数据之间有一个空行。

输出描述:
对输入文件中的每组数据,输出它们的和。每两组数据的输出之间有一个空行。

猜你喜欢

转载自blog.csdn.net/qq_33846054/article/details/51045773
5.2