懒了很久终于又来刷题,还从最简单的开始,暴露出不少问题。
本来是个最简单的问题,大数高精度计算,因为种种原因居然写了两个小时才AC。菜到家了
题目的描述:
Description
This problem requires that you write a program to compute the exact value of R n where R is a real number ( 0.0 < R < 99.999 ) and n is an integer such that 0 < n <= 25.
Input
Output
Sample Input
95.123 12 0.4321 20 5.1234 15 6.7592 9 98.999 10 1.0100 12
Sample Output
548815620517731830194541.899025343415715973535967221869852721 .00000005148554641076956121994511276767154838481760200726351203835429763013462401 43992025569.928573701266488041146654993318703707511666295476720493953024 29448126.764121021618164430206909037173276672 90429072743629540498.107596019456651774561044010001 1.126825030131969720661201
第一眼看上去挺简单的,不就是用数组算嘛,连题都没看完,很快就写好代码了。读入一个float,一个int,算出float的小数位数,然后把float存入数组,计算结果,输出结果。提交,WA。
很不服气地拿sample一比较,发现5.1234 15居然错了。其实之前就意识到这个问题了,只是抱着侥幸心理忽略了:C++的浮点数不精确。5.1234被当成5.123999还是5.1234001来着,位数一大肯定错了。
改成string,因为不熟悉C++,又在strlen和string.length和string.length()之间折腾了一会儿,最后一个才是对的,strlen的参数必须是const char*。提交,依然WA。
仔细一对比,发现纯小数前面的0要省略。改过来,提交,WA。
再仔细一对比,发现小数最后的无意义的0要省略。改过来,提交,WA。
这次没有参照了。自己想想吧,试着输入整数,发现后面跟了小数点。改过来,我已经开始心虚了,提交,WA。
还是没有放弃,想了想还有什么特例。输入1.00,发现这个情况没处理,输出为1.00。改过来,提交,忐忑万分,果然没失望还是WA。
继续想想,试着输入0.000,发现这个情况没考虑,输出了0.000 。改过来,我简直不能相信过不了。最后依然是WA。
我已经被折磨得不行了,决定从网上找测试数据。
然后果然又发现两个问题,一个大问题是前后的省略0没处理好导致10.000 1这样的数据会被处理成1输出。一个小问题是 0 0这样的输入会输出成0 。
把这两个问题解决掉,才终于AC了。实在没想到简单的一道题能做成这样。代码看起来丑陋不堪,效率也非常低下,非常伤心,整理之后如下:
// Problems.cpp : 定义控制台应用程序的入口点。
//
#include <iostream>
#include <string>
#include <math.h>
using namespace std;
const int LENGTH = 600;
void calBigInt(int nums[LENGTH], int n){
int tmp[LENGTH];
int tmpResult[LENGTH];
int tlength, length = 0;
while(nums[length]>=0)length++; //length of nums
memcpy(tmpResult,nums,sizeof(int) * LENGTH);
for(int i = 0; i < n-1; i ++)
{
memset(tmp,-1,sizeof(int) * LENGTH);
tlength = 0;
while(tmpResult[tlength]>=0)tlength++; //length of tmpResult
for(int j = 0; j < length; j++)//the jth number multiple with the tmp result
{
for(int k = 0; k < tlength; k++)
{
if(tmp[j+k] == -1)tmp[j+k] = 0;
tmp[j+k] += nums[j] * tmpResult[k];
if(tmp[j+k] >= 10)
{
if(tmp[j+k+1] == -1)tmp[j+k+1]=0;
tmp[j+k+1] += tmp[j+k]/10;
tmp[j+k] = tmp[j+k]%10;
}
}
}
tlength = 0;
while(tmp[tlength]>=0)tmpResult[tlength] = tmp[tlength++];
}
memcpy(nums,tmpResult,sizeof(int) * LENGTH);
}
int main()
{
string s;
int n,t,length,start;
int nums[LENGTH];
while(cin >> s >> n)
{
if(n == 0){
cout<<1<<endl;continue;
}
///init of params
memset(nums,-1,sizeof(int) * LENGTH);//init
t = 0; //float count
length = 0; //be used as index
int i = s.length() -1;
while(i>=0) //transfer string to array
{
if(s[i]=='.'){// position of dot
t = s.length() - 1 - i;
i--;
continue;
}
nums[length++] = s[i--] - '0';
}
t *= n; //final float count
calBigInt(nums,n); //calculation
///handle prefix 0 and suffix 0
length = 0;
while(nums[length]>=0)length++;
while(length >= 1 && nums[length-1]==0)length--;//length of effective number
if(length == 0){
cout << 0 << endl;continue;
}
start = 0;
if(t > 0)//suffix 0
{
for(i = 0; i < t; i++)
{
if(nums[i]==0)continue;
break;
}
start = i;
}
if(t >= length)//prefix 0 of pure decimal
{
cout<<".";
for(i = 0; i < t-length; i++)
{
cout<<"0";
}
}
for(i = length-1; i >= start; i --)
{
cout<<nums[i];
if(i == t && t > start)cout<<".";
}
cout<<endl;
}
return 0;
}
但是我记得有一个更高效的计算幂的方法,矩阵幂运算。
比如要计算3^5 ,result = 1,先算tmp=3^(2^0)=3,5%2=1,所以result*=tmp=3, 然后3^(2^1) = 9, 5>>1=2%2=0, 什么也不干;然后3^(2^2)=81, 2>>1=1%2=1, 所以result*=tmp=243。这个方法可以极大地提高效率。试一试,把算法换成:
/**
* Calculate the two numbers and save result to the first
*/
void CalTwoBigInt(int number1[LENGTH], int number2[LENGTH]){
int length1 = 0, length2 = 0, length = 0;
int result[LENGTH];
while(number1[length1]>=0)length1++; //length of num1
while(number2[length2]>=0)length2++; //length of num2
memset(result, -1, LENGTH * sizeof(int));
for(int j = 0; j < length2; j++) //every number of num2 multiple with number1
{
for(int i = 0; i < length1; i++)
{
if(result[i+j] == -1)result[i+j] = 0;
result[i+j] += number1[i] * number2[j];
if(result[i+j]>=10)
{
if(result[i+j+1] == -1) result[i+j+1] = 0;
result[i+j+1] += result[i+j]/10;
result[i+j] %= 10;
}
}
}
memcpy(number1,result,LENGTH * sizeof(int));
}
void QuickCalBigInt(int nums[LENGTH], int n){
// if(n < 0) throw exception("n must be a positive number.");
if(n <= 0){
memset(nums,0,LENGTH * sizeof(int));return;
}
if(n == 1)return;
int num2[LENGTH], result[LENGTH];
memset(result, -1, LENGTH * sizeof(int));
memcpy(num2,nums,LENGTH * sizeof(int));
result[0] = 1;
while(true)
{
if(n%2 == 1)
{
CalTwoBigInt(result,num2);
}
n >>= 1;
if(n == 0)break;
CalTwoBigInt(num2, num2);
}
memcpy(nums,result,LENGTH * sizeof(int));
}
结果反而慢了。这个结果不能接受。自己测了几遍,发现只有当数据比较大的时候,后者才有略微的优势,大概也就提高了一倍的效率。
真是备受打击。
附:测试数据
输入:
99.001 3
99.010 1
99.010 3
99.100 1
99.100 3
95.123 12
0.4321 20
5.1234 15
6.7592 9
98.999 10
1.0100 12
.00001 1
.12345 1
0001.1 1
1.1000 1
10.000 1
000.10 1
000000 1
000.00 1
.00000 0
000010 1
000.10 1
0000.1 1
00.111 1
0.0001 1
0.0001 3
0.0010 1
0.0010 3
0.0100 1
0.0100 3
0.1000 1
0.1000 3
1.0000 1
1.0000 3
1.0001 1
1.0001 3
1.0010 1
1.0010 3
1.0100 1
1.0100 3
1.1000 1
1.1000 3
10.000 1
10.000 3
10.001 1
10.001 3
10.010 1
10.010 3
10.100 1
10.100 3
99.000 1
99.000 3
99.001 1
99.001 3
99.010 1
99.010 3
99.100 1
99.100 3
99.998 1
99.998 3
95.123 12
0.4321 20
5.1234 15
6.7592 9
98.999 10
1.0100 12
.00001 1
.12345 1
0001.1 1
1.1000 1
10.000 1
000.10 1
000010 1
000.10 1
0000.1 1
00.111 1
0.0001 1
0.0001 3
0.0010 1
0.0010 3
0.0100 1
0.0100 3
0.1000 1
0.1000 3
1.0000 1
1.0000 3
1.0001 1
1.0001 3
1.0010 1
1.0010 3
1.0100 1
1.0100 3
1.1000 1
1.1000 3
10.000 1
10.000 3
10.001 1
10.001 3
10.010 1
10.010 3
10.100 1
10.100 3
99.000 1
99.000 3
99.001 1
99.001 3
99.010 1
99.010 3
99.100 1
99.100 3
99.998 1
99.998 3
10.100 1
10.100 3
99.000 1
99.000 3
99.001 1
99.998 1
输出:
970328.403297001
99.01
970593.059701
99.1
973242.271
548815620517731830194541.899025343415715973535967221869852721
.00000005148554641076956121994511276767154838481760200726351203835429763013462401
43992025569.928573701266488041146654993318703707511666295476720493953024
29448126.764121021618164430206909037173276672
90429072743629540498.107596019456651774561044010001
1.126825030131969720661201
.00001
.12345
1.1
1.1
10
.1
0
0
1
10
.1
.1
.111
.0001
.000000000001
.001
.000000001
.01
.000001
.1
.001
1
1
1.0001
1.000300030001
1.001
1.003003001
1.01
1.030301
1.1
1.331
10
1000
10.001
1000.300030001
10.01
1003.003001
10.1
1030.301
99
970299
99.001
970328.403297001
99.01
970593.059701
99.1
973242.271
99.998
999940.001199992
548815620517731830194541.899025343415715973535967221869852721
.00000005148554641076956121994511276767154838481760200726351203835429763013462401
43992025569.928573701266488041146654993318703707511666295476720493953024
29448126.764121021618164430206909037173276672
90429072743629540498.107596019456651774561044010001
1.126825030131969720661201
.00001
.12345
1.1
1.1
10
.1
10
.1
.1
.111
.0001
.000000000001
.001
.000000001
.01
.000001
.1
.001
1
1
1.0001
1.000300030001
1.001
1.003003001
1.01
1.030301
1.1
1.331
10
1000
10.001
1000.300030001
10.01
1003.003001
10.1
1030.301
99
970299
99.001
970328.403297001
99.01
970593.059701
99.1
973242.271
99.998
999940.001199992
10.1
1030.301
99
970299
99.001
99.998