:::info
课程项目一:乘法程序
实现一个可以完成读取两个数并输出两数相乘结果的程序。
需求
1. 请使用C++完成代码,程序需要可以被一个C++编译器(如g++)编译。程序应该尽可能
符合普通人使用计算器完成乘法的直觉。
2. 通过以下方式执行代码,程序会打印表达式和结果。两个数需要通过命令行参数进行读
取。(以下为linux环境,如果windows环境,虽有小差别(如mul.exe,带有拓展名exe),
但也需要实现类似功能。)
$./mul 2 3
2 * 3 = 6
3. 如果输入存在非整数,程序需要尝试把它们转译成浮点数
$./mul 3.1416 2
3.1416 * 2 = 6.2832
$./mul 3.1415 2.0e-2
3.1415 * 0.02 = 0.06283
4. 如果当输入不是一个数字时,程序能够说明情况
$./mul a * 2
输入不能被解析为一个数字!
5. 如果你输入的是一些大整数,会发生什么情况?请尝试着描述可能的解决方法,并试着实
现它。
$./mul 1234567890 987654321 # 结果应该是 1219326311126352690
6. 如果输入的是大浮点数,会发生什么情况?请尝试着描述可能的解决方法,并试着实现
它。
$./mul 1.0e200 1.0e200
7. 尝试多种方式对代码进行优化,包括执行速度、功能等(提示:搜索高精度乘法等关键
词,以及快速傅里叶变换等关键词,尝试支持大数,并提高乘法精度和速度;可以尝试完
善错误提示,让错误提示更加明确;尝试允许用户自选输出方式,如是否采用科学计数法
等)。
8. 如果可以,尽量少使用相关代码库,直接调用高精度乘法相关的库无法获得高分。
9. 项目报告需要对需求进行分析,描述你的实现方案与项目特色,贴出核心代码并加以解
释,若代码编译运行比较复杂,需要附上如何编译运行代码。给出运行结果的展示(如截
图),并尽量给出性能展示与分析。最后给出总结。同时,鼓励写上实现代码时遇到的困
难以及相应的解决思路/方案。
请注意
1. 请在截止日期前提交项目报告与源代码。提交截止时间是10月15日23:59。超过截止时间
的提交将无效。
2. 请分开提交项目报告与源代码(即不要放在同个压缩包下):报告与源代码命名方式为:
姓名-学号-项目一,报告为pdf,源代码如果单份可以直接发送,如果多份请传zip压缩包。
3. 分数还将取决于源代码和报告的质量。报告应该容易理解,并很好地描述你的工作,特别
是你工作的亮点。
4. 请更加注意代码风格。本项目有足够的时间来编写具有正确结果和良好代码风格的代码。
如果代码风格很糟糕,可能会被扣分。可以阅读Google C++风格指南
(http://google.github.io/styleguide/cppguide.html)或其他一些代码风格指南。
:::
需求1:请使用C++完成代码,程序需要可以被一个C++编译器(如g++)编译。程序应该尽可能
符合普通人使用计算器完成乘法的直觉。
直接 g++ .\project1.cpp -o .\project1.exe编译即可,如何使用?
我们直接./project1.exe 123 123使用,即可得到123*123的结果
需求2:打印表达式和结果,通过命令行参数读取,此处用windows执行。
执行结果

解析
此处是打印表达式用到的东西,此时还用命令行参数,所以存储格式是char,下面会进行转换int

此处是打印“=”与结果,结果存储在一个int类型的数组中,在我们的函数里,为了计算方便,将字符串倒转过来,所以最后打印结果的时候需要逆序打印。
这里还用一个函数判断是否为浮点数,以判断在什么位置应该打印小数点“.”,其中46为小数点的ASCII码

flag1与flag2判断前面是否需要带负号

现在我们来讲讲整数乘法部分是如何实现的,我们先看从函数调用看过去,在此之前,有一个char数组转int数组的操作(并且将数组逆转)(其实转成char数组也可以,但是乘法的算法和int不一样,需要重新设计)

然后将逆转后的数组(int)传参进函数运算

注意到len3=len1+len2,,之后进行乘法运算,最后减去多余的0,如何理解?
如25 * 25=625,此时len3是4,第一个for循环式运算出来的结果是 ,5260,减去多余的0是526,然后逆序输出成625

如果用局部变量来获取长度可能会有问题,如下。我们用全局来解决
len_ptr1=strlen(ptr1);如果用strlen,如果第一个字符是’\x00’,就会截断,长度为0
需求3:如果输入存在非整数,程序需要尝试把它们转译成浮点数
执行结果,和描述有点差异,比如科学技术法打印出来并没有转换成小数,以及小数点后面的0并没有省去
以及这里的科学技术法只能用大写E

科学计数法转换
应该是用到了C++这两个库

分为两步,一个是科学计数法法转浮点数,另一个是浮点数转成字符数组
这里在转换的时候遇到困难,搜资料发现有如此简单的方法,便采用,用到了类的东西
使用find方法找到E的位置,exp是指数部分,base是我们前面的部分
然后做一个运算

小数分析
上面提到过,有一个函数判断是否为浮点数,我们的函数如下,就是根据里面有没有”.”进行判断,是的话,返回真,执行我们的浮点数运算函数。这个函数就是看有没有’.’,此时Dotflag传进来的还是char类型的数组


那浮点数乘法部分我们如何实现的呢?
首先我们先看,此处定义了dotflag,来分别存储各个数组小数点的位置,在小数运算中有个特性,就是结果的小数点后几位取决于乘法两个数小数点几位相加的结果。比如1.44=1.2*1.2(2=1+1)
所以我们dotflag就是1 2相加。
此时我们看前面两个for循环,这两个for循环是记住dot对的位置,然后去除dot。
注意这里判断的时候,由于我们前面逆转操作时有 int(ptr[i]-‘0’)的操作,所以我们不能直接判断它是否为’.’,而是如图
去掉小数点之后,我们数组就剩数字了,是不是意味着我们可以把它当做整数处理?
所以后面直接带进整数乘法。

最后出来,我们要把小数点给插上去,插进去的时候注意len3长度要加1

最后出来到main函数这里打印,注意这里有三个if同级,后面两个if是因为程序有点bug,临时做的一点修改,我目前找不到这个bug来源于哪里,这后面的两个if大概是这样的:
因为逆序输出,第一位就是最后一位(len-1),在科学计数法运算的时候,Float_Mul更改过的mul_result数组最后一位是以0结尾,也看不见小数点,这个问题可能和长度有关。但是呢,我们如果输入小数它又是正常的。
bug如:0625 修补后0.625
第三个if单独修补以’.’开头的东西
大概效果如:.625 修补后0.625
最后for循环里面,由于我们在Float_Mul插进去的是’.’,int类型值是46,所以我们也要判断如果是46就要换成点。注意这里数组每个值都不会大于10,如果大于10会被前面Big_Mul算法除掉,所以这是安全的。

需求4 检查
如图,我们声明了两个int变量来记录dot和E的个数,如果2个及以上就是非法字符,当然这里没法判断输入者可以用.和E结尾,不过程序会运行出一些奇怪的数据。
还有输入其他的也是非法字符,只让输入0-9 . E e这些
但是这里在处理科学计数法的时候会有一个问题,后面讲大浮点数的时候会涉及到。

需求5 大整数
由于这里的算法是针对大整数的,char类型数组不怕溢出,溢出了可以改改宏定义,所以多大都不怕。

需求6 大浮点数
大浮点数运算执行不通过,报错是自己写的,说太多’.’了(最后一行),我们可以看到整个存储它的数组
原因找到了,是因为check检查的时候,我们输入1.0E200在这个位置记录了一个dot,转换之后,len长度发生变化,但是check函数并没有重新执行一遍,所以在最后检测到了dot,此时char_dot为2,退出程序。

解决方法:在check前面进行科学计数法转换


可以看到都打印成功

需求7 需求8
完善错误提示
1 2 3 4 5
| cout<<"ERROR[0]:Please enter a valid number"<<endl;
cout<<"ERROR[1]:Too many dot!"<<endl;
cout<<"ERROR[2]:To many 'E'!"<<endl;
|
支持大数
看了看傅里叶变换,感觉很厉害,这里记录下来以后会看
https://www.cnblogs.com/stelayuri/p/13347896.html
性能展示:
普通整数相乘

科学计数法,最后一个没有运行时间,说明崩掉了,不知道为什么最后一个会崩掉?难道是输出太多数据了?

感觉对于大数字还是足够快的。
不过了比较多的一个for循环,以及一个双重循环,这些就对时间有影响
需求9
高精度乘法的实现方案,我们用数组来存储一个一个位,这样就不会有大数的问题,按照对乘法的一般认识来写的这个程序,这样我们刚好用一个二层循环来实现乘法,先让A的每一位去乘B0,过完一次循环就到B1,以此类推。

核心代码就在大数乘法的部分,解释如注释,也在前面大乘法的时候解释过:

项目特色,个人觉得是段代码了,转换科学计数法的时候,很简洁,借鉴了别人的方法

总结
一个月的时间还是足够完成程序的,但是比较难处理的是修补程序各种想不到的bug,比如Float_Mul去掉点并记录位置的时候,此时int类型的 . 并不是其ACSII码46,而是其之前逆转字符串到int数组做的处理’.’-‘0’=-2这个当时找了很久都没找到是什么问题,很折磨。从写浮点数开始就已经出现许多错误了,然后到check函数,check函数还是遇到许多问题的,但是还好不是什么短路求值问题,而是在科学计数法转换的时候出现的一些问题,详细见需求6。
对刚学C++的我来说,感觉在这次作业比较偏向于C,因为还没学多少C++面向对象的一些特性,什么重载还有函数模板,感觉暂时也用不上(Ptr_Print)可以,不过那个函数是我为了检查有什么问题才写的,与主题程序无关。到科学计数法这一块,才认识到面向对象对的一些特性使用起来是多么方便。
虽然,我个人认为能够完成整个任务并没有什么问题不是一件容易的事,但是,我深刻感觉到我的代码有点繁琐,许多功能并不能协同实现,一些代码带入了一些不必要的循环,有很多是我现在也没有意识到的,对于以后选取采用何种方式,必须要进行深究。
源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
| #include <iostream> #include <string.h> #include <string> #include <cmath> #include <stdlib.h> #include <time.h> using namespace std;
#define MAX 1024
bool flag1=true,flag2=true; int *mul_result = new int [MAX]{0}; int len1=0; int len2=0; int len3=0; int dotflag=0;
void Float_Mul(char *ptr1,char* ptr2,int* result); void Big_Mul(char *ptr1,char* ptr2,int* result); bool Dot_Flag(char *ptr1,char* ptr2); void Scientific_Notation(char* ptr,int*plen); void check(char *ptr1,char* ptr2); void Ptr_Print(char*ptr); void Is_Scientific(char *ptr,int *plen);
int main(int argc,char* argv[]) { time_t begin,end; begin=clock(); if(argc == 3) { char ptr1[MAX]={0}; char ptr2[MAX]={0};
if(argv[1][0]=='-') { strcpy(&argv[1][0],&argv[1][1]); flag1=false; } if(argv[2][0]=='-') { strcpy(&argv[2][0],&argv[2][1]); flag2=false; }
Ptr_Print(argv[1]); cout<<" *"<<" "; Ptr_Print(argv[2]); len1=strlen(&argv[1][0]); len2=strlen(&argv[2][0]);
Is_Scientific(argv[1],&len1); Is_Scientific(argv[2],&len2); check(argv[1],argv[2]); for(int i=0;i<len1;i++) { ptr1[i]=int(argv[1][len1-1-i]-'0'); } for(int i=0;i<len2;i++) { ptr2[i]=int(argv[2][len2-1-i]-'0'); }
cout<<" ="<<" "; if (Dot_Flag(argv[1],argv[2])) { Float_Mul(ptr1,ptr2,mul_result); if(flag1==true&&flag2==false||flag1==false&&flag2==true) cout<<"-"; if(mul_result[len3-1]==0) mul_result[len3-1]='.'; if(mul_result[len3-1]==46) cout<<"0"; for(int i=len3-1 ; i>=0 ; i--) { if(mul_result[i]!=46) { cout<<mul_result[i]; } else putchar('.'); } } else { Big_Mul(ptr1,ptr2,mul_result); if(flag1==true&&flag2==false||flag1==false&&flag2==true) cout<<"-"; for(int i=len3-1 ; i>=0 ; i--) { cout<<mul_result[i]; } } } else { cout<<"two argument expected."<<endl; } delete []mul_result; end=clock(); cout<<endl; cout<<endl<<"runtime: "<<double(end-begin)/CLOCKS_PER_SEC<<endl;
return 0; }
void Float_Mul(char *ptr1,char* ptr2,int* result) { int temp; int dotflag1=0; int dotflag2=0; len3=len1+len2;
for(int i=0;i<len1;i++) { if(int(ptr1[i])==int('.'-'0')) { dotflag1=i; strcpy(&ptr1[i],&ptr1[i+1]); } } for(int i=0;i<len2;i++) { if(int(ptr2[i])==int('.'-'0')) { dotflag2=i; strcpy(&ptr2[i],&ptr2[i+1]); } } dotflag=dotflag1+dotflag2; Big_Mul(ptr1,ptr2,mul_result); if(dotflag) { len3=len3+1; for(int i=len3-1;i>dotflag;i--) { mul_result[i]=mul_result[i-1]; } mul_result[dotflag]='.'; }
}
bool Dot_Flag(char *ptr1,char* ptr2) { int max=len1; if(len1<len2) max=len2; for(int i=0;i<max;i++) { if(i<len1) { if(ptr1[i]=='.') { return true; } } if(i<len2) { if(ptr2[i]=='.') { return true; } } } return false; }
void Big_Mul(char *ptr1,char* ptr2,int* mul_result) { int temp; len3=len1+len2; for(int i=0 ; i < len1 ; ++i) { temp =0; for(int j=0 ; j < len2 ; ++j) { mul_result[i+j]=ptr1[i]*ptr2[j]+temp+mul_result[i+j]; temp=mul_result[i+j]/10; mul_result[i+j]%=10; } mul_result[i+len2]=temp; } for(int i=len3-1 ; i>=0 ; --i) { if(mul_result[i]==0 && len3 > 1) { len3--; } else { break; } } }
void check(char *ptr1,char* ptr2) { int char_e=0; int char_dot=0; for(int i=0;i<len1;i++) { int qwq=((ptr1[i]=='.'||ptr1[i]=='-')||(ptr1[i]=='+'||ptr1[i]=='E')||(ptr1[i]=='e')); if(!(('0'<=ptr1[i]&&ptr1[i]<='9')||qwq)) { cout<<"ERROR[0]1:Please enter a valid number"<<endl; exit(0); } if(ptr1[i]=='.') { char_dot++; if(char_dot>=2) { cout<<"ERROR[1]:Too many dot!"<<endl; exit(0); } } if(ptr1[i]=='E'||ptr1[i]=='e') { char_e++; if(char_e>=2) { cout<<"ERROR[2]:To many 'E'!"<<endl; exit(0); } } } char_e=0; char_dot=0; for(int i=0;i<len2;i++) { int qwq=((ptr2[i]=='.'||ptr2[i]=='-')||(ptr2[i]=='+'||ptr2[i]=='E')||(ptr2[i]=='e')); if(!(('0'<=ptr2[i]&&ptr2[i]<='9')||qwq)) { cout<<"ERROR[0]2:Please enter a valid number"<<endl; exit(0); } if(ptr2[i]=='.') { char_dot++; if(char_dot>=2) { cout<<"ERROR[1]:Too many dot!"<<endl; exit(0); } } if(ptr2[i]=='E'||ptr2[i]=='e') { char_e++; if(char_e>=2) { cout<<"ERROR[2]:To many 'E'!"<<endl; exit(0); } } }
} void Scientific_Notation(char* ptr,int *plen) { char c[50]={'\x00'}; string s=ptr; double double_num; int eIndex = s.find("E"); double base = stod(s.substr(0, eIndex)); int exp = stoi(s.substr(eIndex + 1)); double_num= base * pow(10, exp); sprintf(ptr,"%f",double_num); *plen=strlen(ptr);
}
void Ptr_Print(char*ptr) { int len=strlen(ptr); for(int i=0;i<len;i++) { cout<<ptr[i]; } }
void Is_Scientific(char *ptr,int *plen) { for ( int i = 0; i < *plen; i++) { if(ptr[i]=='E'||ptr[i]=='e') { Scientific_Notation(ptr,plen); } } }
|