0%

C++第一次小项目——乘法程序

:::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. 请在截止日期前提交项目报告源代码。提交截止时间是10152359超过截止时间

的提交将无效

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');//注意这里ptr里面存的已经是数字了,不是它的ASCII码
}
for(int i=0;i<len2;i++)
{
ptr2[i]=int(argv[2][len2-1-i]-'0');
}

cout<<" ="<<" ";
//根据判断有没有.的结果来选择进入函数
//输出计算结果(逆序输出逆序算出的结果就是正序)
if (Dot_Flag(argv[1],argv[2]))
{//Entering float_Mul func()
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
{//Entering Big_Mul func()
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;//这里len还算有.的长度

//判断是否为'.',并记录位置,我们处理是将dot展示移出,等计算完再放回来
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);

//把小数点插进去,计算结果是 123.45的话,在存储中会是54321,把小数点插入54.321,长度len+1
//strcpy(&mul_result[dotflag+1],&mul_result[dotflag]);
if(dotflag)
{
len3=len3+1;
for(int i=len3-1;i>dotflag;i--)
{
mul_result[i]=mul_result[i-1];
}
mul_result[dotflag]='.';//这里本来放点。可是左值是一个INT类型
//所以会转为ACSII码,这个地方会显示46,我们遇到46去替换就行了
}
/*
cout<<endl;
cout<<"dot"<<dotflag<<endl;
for(int i=0;i<len3+1;i++)
{
cout<<mul_result[i]<<",";
}
*/
}

//判断是否采用浮点数
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; //留下temp用于退出循环进位,如果没有退出循环,就在循环里进位
mul_result[i+j]%=10;
//cout<<"mul_result_"<<i+j<<":"<<mul_result[i+j]<<endl;
}
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);
}
}
}