C语言实现大数计算器
一、实验介绍
1.1 实验内容
本实验通过使用C语言实现一个简易计算器,该计算器能够进行任意长度的有符号整数的加
、减
、乘
、除
运算。
1.2 实验知识点
- c语言大数加法实现
- c语言大数减法实现
- c语言大数乘法实现
- c语言大数除法实现
1.3 实验环境
- vim
- gcc 编译器
- Xfce终端
1.4 适合人群
本课程适合已了解C语言基础语法规则,并期望通过实际编程项目进行演练,以提高C语言编程技巧的用户进行学习。
1.5 代码获取
你可以通过下面命令将代码下载到实验楼环境中,作为参照对比进行学习。
$ wget http://labfile.oss.aliyuncs.com/courses/750/calculator_sample.tar.gz
$ tar xvf calculator_sample.tar.gz
二、实验步骤
2.1 如何表示大数
由于C语言的基本数据类型在表示数字大小时都有一定的限制,所以要在C语言中表示一个大数,有两种方式:字符串和自行构造数据结构。
-
字符串表示,适合人解读,但不适用于机器运算
-
数据结构表示,适用于机器运算,但人对其进行解读比较困难
我们的设计,综合考虑上述两种方式的优缺点,我们自行构造数据用于表示大数以提供给机器进行运算,同时提供字符串与我们构造的数据结构进行互转的辅助性函数:
// 大数数据结构
typedef struct bignumber_s{
char sign; // 代表大数的符号,1为负数,0为正数
int len; // 代表大数的位数
char data[]; // 大数的数据内容,data[0]代表个位,data[1]代表十位,data[2]代表百位....
} bignumber_s;
// 构造大数模板,len指向大数的数据位数,sign表示该大数的符号
bignumber_s *make_bignumber_temp(int len, int sign);
// 从字符串构造一个大数
bignumber_s *make_bignumber_fromstr(const char *str);
// 以字符串的形式打印输出一个大数
void print_bignumber(bignumber_s* b);
在bignumber_s
数据结构的定义中,我们使用了C语言里一种称为柔性数组的技巧,感兴趣的同学可以自行研究。
下图演示了我们的bignumber_s
如何表示"-123"
这个数:
实现make_bignumber_temp
函数:
// 构造大数模板,len指向大数的数据位数,sign表示该大数的符号
bignumber_s *make_bignumber_temp(int len, int sign)
{
// 分配bignumber_s及其所代表数据 所需的内存
bignumber_s *temp = malloc(sizeof(bignumber_s)+len);
if (NULL == temp) {
perror("Malloc");
exit(-1);
}
temp->sign = sign;
temp->len = len;
memset(temp->data, 0, len);
return temp;
}
实现make_bignumber_fromstr
函数:
// 从字符串构造一个大数
bignumber_s *make_bignumber_fromstr(const char *str)
{
// 处理负数字符串,即"-123"
int sign = 0;
if (str[0]=='-') {
sign = 1;
str++;
}
// 处理数字字符串冗余的0,即"00123" -> "123"
const char * striped_str = strip_str(str);
int len = strlen(striped_str);
// 指定数据位长度即符号,创建一个大数模板
bignumber_s *temp = make_bignumber_temp(len ,sign);
// 将字符串数据填充进模板中,完成大数创建
fill_data_fromstr(temp, striped_str);
return temp;
}
// 处理数字字符串冗余的0,即"00123" -> "123"
const char *strip_str(const char *str)
{
int i = 0;
int len = strlen(str);
for (i=0; i<len-1&&str[i]=='0'; i++) ;
return str+i;
}
// 将字符串数据填充进模板中
void fill_data_fromstr(bignumber_s *n, const char *str)
{
int i = 0;
int len = n->len;
for (i=0; i<len; i++) {
int d = str[len-1-i]-'0';
if (d>=0 && d<=9)
n->data[i] = d;
else {
fprintf(stderr, "Invalid Number:%s\n", str);
exit(-1);
}
}
}
实现print_bignumber
函数:
// 以字符串的形式打印输出一个大数
void print_bignumber(bignumber_s* b)
{
int len = b->len;
char *str = malloc(len+1);
int i = 0;
for (i=0; i<len; i++) {
str[i] = b->data[len-i-1]+'0';
}
str[len] = '\0';
fprintf(stdout,"%s%s\n", b->sign==1?"-":"", strip_str(str));
free(str);
}
2.2 实现计算器的壳
我们的计算器最终编译生成为一个名为calculator
的可执行程序,使用计算器的方法如下:
$ ./calculator 123 + 123 # 执行加法运算
$ ./calculator 123 - 123 # 执行减法运算
$ ./calculator 123 x 123 # 执行乘法运算
$ ./calculator 123 / 123 # 执行除法运算
实现我们的main
函数:
void usage(const char *s)
{
fprintf(stderr, "Usage:%s number1 op(+-x/) number2.\n",s);
exit(-1);
}
bignumber_s *calc_add(bignumber_s *a, bignumber_s *b)
{
// 实现加法
}
bignumber_s *calc_sub(bignumber_s *a, bignumber_s *b)
{
// 实现减法
}
bignumber_s *calc_mul(bignumber_s *a, bignumber_s *b)
{
// 实现乘法
}
bignumber_s *calc_div(bignumber_s *a, bignumber_s *b)
{
// 实现除法
}
int main(int argc, char *argv[])
{
bignumber_s *a = make_bignumber_fromstr(argv[1]);
bignumber_s *b = make_bignumber_fromstr(argv[3]);
if (argc!=4) usage(argv[0]);
if (0 == strcmp(argv[2],"+"))
print_bignumber(calc_add(a,b));
else if (0 == strcmp(argv[2],"-"))
print_bignumber(calc_sub(a,b));
else if (0 == strcmp(argv[2],"x"))
print_bignumber(calc_mul(a,b));
else if (0 == strcmp(argv[2],"/"))
print_bignumber(calc_div(a,b));
else usage(argv[0]);
return 0;
}
2.3 实现无符号加减法
2.3.1 实现无符号加法
加法的实现比较简单,我们借鉴小学时学过的竖式计算过程的思路:
思路大致是:
- 从最低位开始,对应位置相加,和大于10的向高位进位,取个位数为该位置相加的和
- 对应位置相加时,要加上来自低位的进位值
- n位数+m位数,其和最大为max(n,m)+1位数
实现无符号加法:
#define BASE (10)
#define MAX(x,y) ((x)>(y)?(x):(y))
// 实现无符号加法,a和b为加数,r为和
void add_impl(bignumber_s *a, bignumber_s *b, bignumber_s *r)
{
int i = 0;
char carry = 0;
int len = r->len;
for (i=0; i<len; i++) {
if (i<a->len)carry += a->data[i];
if (i<b->len)carry += b->data[i];
r->data[i] = carry%BASE;
carry /= BASE;
}
}
bignumber_s *add(bignumber_s *a, bignumber_s *b)
{
// n位数+m位数,其和最大为max(n,m)+1位数
int len = MAX(a->len, b->len) + 1;
bignumber_s *result = make_bignumber_temp(len,a->sign);
//
add_impl(a,b,result);
return result;
}
2.3.2 实现无符号减法
减法的实现与加法的实现思路相似,依旧请出的我们竖式计数法:
思路大致是:
- 从最低位开始,对应位置相减,不够减的向高位借位,得到的差作为该位置的差
- 对应位置相减时,要减去来自低位的借位值
- n位数-m位数,其和最大为n位数
实现无符号减法:
// 实现无符号减法,a-b , r为差
void sub_impl(bignumber_s *a, bignumber_s *b, bignumber_s *r)
{
int i=0;
int borrow = 0;
int len = r->len;
int temp = 0;
for (i=0; i<len; i++) {
temp = a->data[i]+BASE-borrow-((i<b->len)?b->data[i]:0);
r->data[i] = temp%BASE;
borrow = 1-temp/BASE;
}
}
bignumber_s *sub(bignumber_s *a, bignumber_s *b)
{
int len = a->len;
bignumber_s *result = make_bignumber_temp(len,0);
sub_impl(a,b,result);
return result;
}
2.3.3 大数计算器v1版本
大数计算器v1版本:
/*
* file name : calculatorv1.c
* desp : calculator version 1
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BASE (10)
#define MAX(x,y) ((x)>(y)?(x):(y))
// 大数数据结构
typedef struct bignumber_s{
char sign; // 代表大数的符号,1为负数,0为正数
int len; // 代表大数的位数
char data[]; // 大数的数据内容,data[0]代表个位,data[1]代表十位,data[2]代表百位....
} bignumber_s;
// 构造大数模板,len指向大数的数据位数,sign表示该大数的符号
bignumber_s *make_bignumber_temp(int len, int sign)
{
// 分配bignumber_s及其所代表数据 所需的内存
bignumber_s *temp = malloc(sizeof(bignumber_s)+len);
if (NULL == temp) {
perror("Malloc");
exit(-1);
}
temp->sign = sign;
temp->len = len;
memset(temp->data, 0, len);
return temp;
}
// 处理数字字符串冗余的0,即"00123" -> "123"
const char *strip_str(const char *str)
{
int i = 0;
int len = strlen(str);
for (i=0; i<len-1&&str[i]=='0'; i++) ;
return str+i;
}
// 将字符串数据填充进模板中
void fill_data_fromstr(bignumber_s *n, const char *str)
{
int i = 0;
int len = n->len;
for (i=0; i<len; i++) {
int d = str[len-1-i]-'0';
if (d>=0 && d<=9)
n->data[i] = d;
else {
fprintf(stderr, "Invalid Number:%s\n", str);
exit(-1);
}
}
}
// 从字符串构造一个大数
bignumber_s *make_bignumber_fromstr(const char *str)
{
// 处理负数字符串,即"-123"
int sign = 0;
if (str[0]=='-') {
sign = 1;
str++;
}
// 处理数字字符串冗余的0,即"00123" -> "123"
const char * striped_str = strip_str(str);
int len = strlen(striped_str);
// 指定数据位长度即符号,创建一个大数模板
bignumber_s *temp = make_bignumber_temp(len ,sign);
// 将字符串数据填充进模板中,完成大数创建
fill_data_fromstr(temp, striped_str);
return temp;
}
// 以字符串的形式打印输出一个大数
void print_bignumber(bignumber_s* b)
{
int len = b->len;
char *str = malloc(len+1);
int i = 0;
for (i=0; i<len; i++) {
str[i] = b->data[len-i-1]+'0';
}
str[len] = '\0';
fprintf(stdout,"%s%s\n", b->sign==1?"-":"", strip_str(str));
free(str);
}
void usage(const char *s)
{
fprintf(stderr, "Usage:%s number1 +-x/ number2.\n",s);
exit(-1);
}
// 实现无符号加法,a和b为加数,r为和
void add_impl(bignumber_s *a, bignumber_s *b, bignumber_s *r)
{
int i = 0;
char carry = 0;
int len = r->len;
for (i=0; i<len; i++) {
if (i<a->len)carry += a->data[i];
if (i<b->len)carry += b->data[i];
r->data[i] = carry%BASE;
carry /= BASE;
}
}
bignumber_s *calc_add(bignumber_s *a, bignumber_s *b)
{
// n位数+m位数,其和最大为max(n,m)+1位数
int len = MAX(a->len, b->len) + 1;
bignumber_s *result = make_bignumber_temp(len, 0
);
//
add_impl(a,b,result);
return result;
}
// 实现无符号减法,a-b , r为差
void sub_impl(bignumber_s *a, bignumber_s *b, bignumber_s *r)
{
int i=0;
int borrow = 0;
int len = r->len;
int temp = 0;
for (i=0; i<len; i++) {
temp = a->data[i]+BASE-borrow-((i<b->len)?b->data[i]:0);
r->data[i] = temp%BASE;
borrow = temp/BASE?0:1;
}
}
bignumber_s *calc_sub(bignumber_s *a, bignumber_s *b)
{
int len = a->len;
bignumber_s *result = make_bignumber_temp(len,0);
sub_impl(a,b,result);
return result;
}
bignumber_s *calc_mul(bignumber_s *a, bignumber_s *b)
{
// 实现乘法
return NULL;
}
bignumber_s *calc_div(bignumber_s *a, bignumber_s *b)
{
// 实现除法
return NULL;
}
int main(int argc, char *argv[])
{
bignumber_s *a = make_bignumber_fromstr(argv[1]);
bignumber_s *b = make_bignumber_fromstr(argv[3]);
if (argc!=4) usage(argv[0]);
if (0 == strcmp(argv[2],"+"))
print_bignumber(calc_add(a,b));
else if (0 == strcmp(argv[2],"-"))
print_bignumber(calc_sub(a,b));
else if (0 == strcmp(argv[2],"x"))
print_bignumber(calc_mul(a,b));
else if (0 == strcmp(argv[2],"/"))
print_bignumber(calc_div(a,b));
else usage(argv[0]);
return 0;
}
2.3.4 演示
编译命令:
$ gcc -o calculatorv1 calculatorv1.c
结果演示:
可以使用python交互界面验证我们的计算结果:
2.4 实现有符号加减法
2.4.1 有符号加法
先来看有符号的加法,假设有两个数A和B,有符号加法分以下几种情况:
// 情况一:A > 0 并且 B > 0
A + B --> |A| + |B| (|A|表示求A的绝对值,下同)
// 情况二:A > 0 并且 B < 0
A + B --> |A| - |B|
// 情况三:A < 0 并且 B > 0
A + B --> |B| - |A|
// 情况四:A < 0 并且 B < 0
A + B --> -(|B| + |A|) (此处-表示取负值)
情况一和情况四可以用第2节的无符号加法求解,情况四在用加法求值后再取负值;情况二和情况三转化为有符号减法操作。
因此我们的加法函数可以这么实现:
bignumber_s *calc_add(bignumber_s *a, bignumber_s *b)
{
// 情况一和情况四
if (a->sign==b->sign) {
// n位数+m位数,其和最大为max(n,m)+1位数
int len = MAX(a->len, b->len) + 1;
bignumber_s *result = make_bignumber_temp(len,a->sign);
add_impl(a,b,result);
return result;
} else if (a->sign == 0 && b->sign == 1) {//情况二
b->sign = 0; //b数去绝对值
return calc_sub(a,b);
} else if (a->sign == 1 && b->sign == 0) {//情况三
a->sign = 0; //a数去绝对值
return calc_sub(b,a);
}
}
2.4.2 有符号减法
接下来看有符号的减法,假设有两个数A和B,有符号减法分以下几种情况:
// 情况一:A > 0 并且 B > 0
A - B --> |A| - |B| (|A|表示求A的绝对值,下同)
// 情况二:A > 0 并且 B < 0
A - B --> |A| + |B|
// 情况三:A < 0 并且 B > 0
A - B --> -(|A| + |B|) (此处-表示取负值)
// 情况四:A < 0 并且 B < 0
A - B --> |B| - |A|
情况二和情况三可以转化为第2节的无符号加法求解,情况三在用加法求值后再取负值;
情况一,当被减数大于减数时,结果为正数,可以直接用第2节的减法实现求解;
当被减数小于减数时,结果为负数,即:
// A < B, A>=0 且B>=0 时
A - B ---> -(B-A)
情况四,将减数和被减数调换就成了情况一了。
我们需要先实现一个用以判断两个大数大小的辅助函数,用以判断被减数和减数的大小:
int valid_len(bignumber_s *a)
{
int len = a->len;
int i = len-1;
for (i=len-1; i>=0; i--) {
if (a->data[i]==0) len--;
else break;
}
return len;
}
// 判断两个大数的大小
// a > b 返回1 ,a<b 返回 -1 , a==b 返回0
int cmp(bignumber_s *a, bignumber_s *b)
{
if (a->sign==0&&b->sign==1) return 1;
if (a->sign==1&&b->sign==0) return -1;
int sign = a->sign;
int alen = valid_len(a);
int blen = valid_len(b);
if (alen>blen) return (sign==1?-1:1);
else if (alen<blen) return (sign==1?1:-1);
else {
int i = 0;
int len = alen;
for (i=len-1; i>=0; i--) {
if (a->data[i]>b->data[i])return (sign==1?-1:1);
else if (a->data[i]<b->data[i]) return (sign==1?1:-1);
}
return 0;
}
}
有符号的减法实现如下:
bignumber_s *sub(bignumber_s *a, bignumber_s *b)
{
// 情况一
if(a->sign==0 && b->sign==0) {
if (cmp(a,b)>=0) { // 被减数大于等于减数,即差大于等于0时
int len = a->len;
bignumber_s *result = make_bignumber_temp(len,0);
sub_impl(a,b,result);
return result;
} else { // 被减数小于减数,即差小于0时
int len = b->len;
bignumber_s *result = make_bignumber_temp(len,1);
sub_impl(b,a,result);
return result;
}
} else if (a->sign==1 && b->sign==1) { //情况四
b->sign=0;
a->sign=0;
return sub(b,a); //调换减数和被减数,变成情况一
}else if (a->sign==0 && b->sign==1) { // 情况二
b->sign=0;
bignumber_s *result = add(a,b);
result->sign = 0;
return result;
} else if (a->sign==1 && b->sign==0) { // 情况三
a->sign=0;
bignumber_s *result = add(a,b);
result->sign = 1;
return result;
}
}
2.4.3 大数计算器v2版本
完整代码如下(calculatorv2.c):
/*
* file name : calculatorv2.c
* desp : calculator version 2
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BASE (10)
#define MAX(x,y) ((x)>(y)?(x):(y))
// 大数数据结构
typedef struct bignumber_s{
char sign; // 代表大数的符号,1为负数,0为正数
int len; // 代表大数的位数
char data[]; // 大数的数据内容,data[0]代表个位,data[1]代表十位,data[2]代表百位....
} bignumber_s;
bignumber_s *calc_add(bignumber_s *a, bignumber_s *b);
bignumber_s *calc_sub(bignumber_s *a, bignumber_s *b);
// 构造大数模板,len指向大数的数据位数,sign表示该大数的符号
bignumber_s *make_bignumber_temp(int len, int sign)
{
// 分配bignumber_s及其所代表数据 所需的内存
bignumber_s *temp = malloc(sizeof(bignumber_s)+len);
if (NULL == temp) {
perror("Malloc");
exit(-1);
}
temp->sign = sign;
temp->len = len;
memset(temp->data, 0, len);
return temp;
}
// 处理数字字符串冗余的0,即"00123" -> "123"
const char *strip_str(const char *str)
{
int i = 0;
int len = strlen(str);
for (i=0; i<len-1&&str[i]=='0'; i++) ;
return str+i;
}
// 将字符串数据填充进模板中
void fill_data_fromstr(bignumber_s *n, const char *str)
{
int i = 0;
int len = n->len;
for (i=0; i<len; i++) {
int d = str[len-1-i]-'0';
if (d>=0 && d<=9)
n->data[i] = d;
else {
fprintf(stderr, "Invalid Number:%s\n", str);
exit(-1);
}
}
}
// 从字符串构造一个大数
bignumber_s *make_bignumber_fromstr(const char *str)
{
// 处理负数字符串,即"-123"
int sign = 0;
if (str[0]=='-') {
sign = 1;
str++;
}
// 处理数字字符串冗余的0,即"00123" -> "123"
const char * striped_str = strip_str(str);
int len = strlen(striped_str);
// 指定数据位长度即符号,创建一个大数模板
bignumber_s *temp = make_bignumber_temp(len ,sign);
// 将字符串数据填充进模板中,完成大数创建
fill_data_fromstr(temp, striped_str);
return temp;
}
// 以字符串的形式打印输出一个大数
void print_bignumber(bignumber_s* b)
{
int len = b->len;
char *str = malloc(len+1);
int i = 0;
for (i=0; i<len; i++) {
str[i] = b->data[len-i-1]+'0';
}
str[len] = '\0';
fprintf(stdout,"%s%s\n", b->sign==1?"-":"", strip_str(str));
free(str);
}
void usage(const char *s)
{
fprintf(stderr, "Usage:%s number1 +-x/ number2.\n",s);
exit(-1);
}
// 实现无符号加法,a和b为加数,r为和
void add_impl(bignumber_s *a, bignumber_s *b, bignumber_s *r)
{
int i = 0;
char carry = 0;
int len = r->len;
for (i=0; i<len; i++) {
if (i<a->len)carry += a->data[i];
if (i<b->len)carry += b->data[i];
r->data[i] = carry%BASE;
carry /= BASE;
}
}
bignumber_s *calc_add(bignumber_s *a, bignumber_s *b)
{
// 情况一和情况四
if (a->sign==b->sign) {
// n位数+m位数,其和最大为max(n,m)+1位数
int len = MAX(a->len, b->len) + 1;
bignumber_s *result = make_bignumber_temp(len,a->sign);
add_impl(a,b,result);
return result;
} else if (a->sign == 0 && b->sign == 1) {//情况二
b->sign = 0; //b数去绝对值
return calc_sub(a,b);
} else if (a->sign == 1 && b->sign == 0) {//情况三
a->sign = 0; //a数去绝对值
return calc_sub(b,a);
}
}
// 实现无符号减法,a-b , r为差
void sub_impl(bignumber_s *a, bignumber_s *b, bignumber_s *r)
{
int i=0;
int borrow = 0;
int len = r->len;
int temp = 0;
for (i=0; i<len; i++) {
temp = a->data[i]+BASE-borrow-((i<b->len)?b->data[i]:0);
r->data[i] = temp%BASE;
borrow = temp/BASE?0:1;
}
}
int valid_len(bignumber_s *a)
{
int len = a->len;
int i = len-1;
for (i=len-1; i>=0; i--) {
if (a->data[i]==0) len--;
else break;
}
return len;
}
// 判断两个大数的大小
// a > b 返回1 ,a<b 返回 -1 , a==b 返回0
int cmp(bignumber_s *a, bignumber_s *b)
{
if (a->sign==0&&b->sign==1) return 1;
if (a->sign==1&&b->sign==0) return -1;
int sign = a->sign;
int alen = valid_len(a);
int blen = valid_len(b);
if (alen>blen) return (sign==1?-1:1);
else if (alen<blen) return (sign==1?1:-1);
else {
int i = 0;
int len = alen;
for (i=len-1; i>=0; i--) {
if (a->data[i]>b->data[i])return (sign==1?-1:1);
else if (a->data[i]<b->data[i]) return (sign==1?1:-1);
}
return 0;
}
}
bignumber_s *calc_sub(bignumber_s *a, bignumber_s *b)
{
// 情况一
if(a->sign==0 && b->sign==0) {
if (cmp(a,b)>=0) { // 被减数大于等于减数,即差大于等于0时
int len = a->len;
bignumber_s *result = make_bignumber_temp(len,0);
sub_impl(a,b,result);
return result;
} else { // 被减数小于减数,即差小于0时
int len = b->len;
bignumber_s *result = make_bignumber_temp(len,1);
sub_impl(b,a,result);
return result;
}
} else if (a->sign==1 && b->sign==1) { //情况四
b->sign=0;
a->sign=0;
return calc_sub(b,a); //调换减数和被减数,变成情况一
}else if (a->sign==0 && b->sign==1) { // 情况二
b->sign=0;
bignumber_s *result = calc_add(a,b);
result->sign = 0;
return result;
} else if (a->sign==1 && b->sign==0) { // 情况三
a->sign=0;
bignumber_s *result = calc_add(a,b);
result->sign = 1;
return result;
}
}
bignumber_s *calc_mul(bignumber_s *a, bignumber_s *b)
{
// 实现乘法
return NULL;
}
bignumber_s *calc_div(bignumber_s *a, bignumber_s *b)
{
// 实现除法
return NULL;
}
int main(int argc, char *argv[])
{
bignumber_s *a = make_bignumber_fromstr(argv[1]);
bignumber_s *b = make_bignumber_fromstr(argv[3]);
if (argc!=4) usage(argv[0]);
if (0 == strcmp(argv[2],"+"))
print_bignumber(calc_add(a,b));
else if (0 == strcmp(argv[2],"-"))
print_bignumber(calc_sub(a,b));
else if (0 == strcmp(argv[2],"x"))
print_bignumber(calc_mul(a,b));
else if (0 == strcmp(argv[2],"/"))
print_bignumber(calc_div(a,b));
else usage(argv[0]);
return 0;
}
2.4.4 演示
编译命令:
$ gcc -o calculatorv2 calculatorv2.c
结果演示:
2.5 实现乘法
2.5.1 正数乘法实现
乘法的实现稍微比加减法复杂一些,但是和加减法是同样的实现原理。
依旧搬出我们的竖式计算法:
假设x[0...n]乘以y[0...m],其中0代表x数的个位,n代表x数的最高位,假设用x代表上述的145
这个数。那么x[0]就是5
, x[1]就是4
, x[1]就是1
; 类似的y代表12
, 则y[0]为2
, y[1]为1
。
乘法的实现思路:
- x与y的积,位数为n+m,即积为z[0...n+m]
- 先用y的每个位去乘以x,如上述例子先用
2
乘以145
,再用1
乘以145
, 从而得出对应于y每个位的积。 - 再将步骤2计算出来的所有积,按其基数累加到积中。
这里有两点需要注意:
- y[i]乘以x[j]时,其积应在z[i+j]。
- y[i]乘以x[0...n]其积,应累加到z[i...m+n]中
代码实现如下:
void mul_impl(bignumber_s *x, bignumber_s *y, bignumber_s *z)
{
int n = x->len;
int m = y->len;
int i = 0;
int j = 0;
int carry = 0;
for (i=0; i<m; i++) {
// y的每一位乘以x,即计算y[i] * x[0...n] 并累加到z[i...i+n]中
for (j=0; j<n; j++) {
carry += y->data[i]*x->data[j] + z->data[i+j];
z->data[i+j] = carry%BASE;
carry /= BASE;
}
// 将剩余的进位,继续在z[i+n...n+m]中累加,从而完成y[i]乘以x[0...n]其积累加到z[i...m+n]中
for (; j+i<n+m; j++) {
carry += z->data[i+j];
z->data[i+j] = carry % BASE;
carry /= BASE;
}
}
}
bignumber_s *calc_mul(bignumber_s *a, bignumber_s *b)
{
int len = a->len + b->len;
bignumber_s *result = make_bignumber_temp(len,0);
mul_impl(a,b,result);
return result;
}
2.5.2 有符号乘法
有符号的 乘法可以转化为无符号的乘法,分以下两种情况:
// 情况一 (A>0 && B>0) 或者 (A>0 && B>0)
A x B --> |A| x |B|
// 情况二 (A>0 && B<0) 或者 (A<0 && B>0)
A x B --> -(|A| x |B|)
代码实现如下:
bignumber_s *calc_mul(bignumber_s *a, bignumber_s *b)
{
int len = a->len + b->len;
bignumber_s *result = make_bignumber_temp(len,a->sign==b->sign?0:1); //a->sign==b->sign为情况一,否则为情况二。
mul_impl(a,b,result);
return result;
}
2.5.3大数计算器v3版本
/*
* file name : calculatorv3.c
* desp : calculator version 3
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BASE (10)
#define MAX(x,y) ((x)>(y)?(x):(y))
// 大数数据结构
typedef struct bignumber_s{
char sign; // 代表大数的符号,1为负数,0为正数
int len; // 代表大数的位数
char data[]; // 大数的数据内容,data[0]代表个位,data[1]代表十位,data[2]代表百位....
} bignumber_s;
bignumber_s *calc_add(bignumber_s *a, bignumber_s *b);
bignumber_s *calc_sub(bignumber_s *a, bignumber_s *b);
// 构造大数模板,len指向大数的数据位数,sign表示该大数的符号
bignumber_s *make_bignumber_temp(int len, int sign)
{
// 分配bignumber_s及其所代表数据 所需的内存
bignumber_s *temp = malloc(sizeof(bignumber_s)+len);
if (NULL == temp) {
perror("Malloc");
exit(-1);
}
temp->sign = sign;
temp->len = len;
memset(temp->data, 0, len);
return temp;
}
// 处理数字字符串冗余的0,即"00123" -> "123"
const char *strip_str(const char *str)
{
int i = 0;
int len = strlen(str);
for (i=0; i<len-1&&str[i]=='0'; i++) ;
return str+i;
}
// 将字符串数据填充进模板中
void fill_data_fromstr(bignumber_s *n, const char *str)
{
int i = 0;
int len = n->len;
for (i=0; i<len; i++) {
int d = str[len-1-i]-'0';
if (d>=0 && d<=9)
n->data[i] = d;
else {
fprintf(stderr, "Invalid Number:%s\n", str);
exit(-1);
}
}
}
// 从字符串构造一个大数
bignumber_s *make_bignumber_fromstr(const char *str)
{
// 处理负数字符串,即"-123"
int sign = 0;
if (str[0]=='-') {
sign = 1;
str++;
}
// 处理数字字符串冗余的0,即"00123" -> "123"
const char * striped_str = strip_str(str);
int len = strlen(striped_str);
// 指定数据位长度即符号,创建一个大数模板
bignumber_s *temp = make_bignumber_temp(len ,sign);
// 将字符串数据填充进模板中,完成大数创建
fill_data_fromstr(temp, striped_str);
return temp;
}
// 以字符串的形式打印输出一个大数
void print_bignumber(bignumber_s* b)
{
int len = b->len;
char *str = malloc(len+1);
int i = 0;
for (i=0; i<len; i++) {
str[i] = b->data[len-i-1]+'0';
}
str[len] = '\0';
fprintf(stdout,"%s%s\n", b->sign==1?"-":"", strip_str(str));
free(str);
}
void usage(const char *s)
{
fprintf(stderr, "Usage:%s number1 +-x/ number2.\n",s);
exit(-1);
}
// 实现无符号加法,a和b为加数,r为和
void add_impl(bignumber_s *a, bignumber_s *b, bignumber_s *r)
{
int i = 0;
char carry = 0;
int len = r->len;
for (i=0; i<len; i++) {
if (i<a->len)carry += a->data[i];
if (i<b->len)carry += b->data[i];
r->data[i] = carry%BASE;
carry /= BASE;
}
}
bignumber_s *calc_add(bignumber_s *a, bignumber_s *b)
{
// 情况一和情况四
if (a->sign==b->sign) {
// n位数+m位数,其和最大为max(n,m)+1位数
int len = MAX(a->len, b->len) + 1;
bignumber_s *result = make_bignumber_temp(len,a->sign);
add_impl(a,b,result);
return result;
} else if (a->sign == 0 && b->sign == 1) {//情况二
b->sign = 0; //b数去绝对值
return calc_sub(a,b);
} else if (a->sign == 1 && b->sign == 0) {//情况三
a->sign = 0; //a数去绝对值
return calc_sub(b,a);
}
}
// 实现无符号减法,a-b , r为差
void sub_impl(bignumber_s *a, bignumber_s *b, bignumber_s *r)
{
int i=0;
int borrow = 0;
int len = r->len;
int temp = 0;
for (i=0; i<len; i++) {
temp = a->data[i]+BASE-borrow-((i<b->len)?b->data[i]:0);
r->data[i] = temp%BASE;
borrow = temp/BASE?0:1;
}
}
int valid_len(bignumber_s *a)
{
int len = a->len;
int i = len-1;
for (i=len-1; i>=0; i--) {
if (a->data[i]==0) len--;
else break;
}
return len;
}
// 判断两个大数的大小
// a > b 返回1 ,a<b 返回 -1 , a==b 返回0
int cmp(bignumber_s *a, bignumber_s *b)
{
if (a->sign==0&&b->sign==1) return 1;
if (a->sign==1&&b->sign==0) return -1;
int sign = a->sign;
int alen = valid_len(a);
int blen = valid_len(b);
if (alen>blen) return (sign==1?-1:1);
else if (alen<blen) return (sign==1?1:-1);
else {
int i = 0;
int len = alen;
for (i=len-1; i>=0; i--) {
if (a->data[i]>b->data[i])return (sign==1?-1:1);
else if (a->data[i]<b->data[i]) return (sign==1?1:-1);
}
return 0;
}
}
bignumber_s *calc_sub(bignumber_s *a, bignumber_s *b)
{
// 情况一
if(a->sign==0 && b->sign==0) {
if (cmp(a,b)>=0) { // 被减数大于等于减数,即差大于等于0时
int len = a->len;
bignumber_s *result = make_bignumber_temp(len,0);
sub_impl(a,b,result);
return result;
} else { // 被减数小于减数,即差小于0时
int len = b->len;
bignumber_s *result = make_bignumber_temp(len,1);
sub_impl(b,a,result);
return result;
}
} else if (a->sign==1 && b->sign==1) { //情况四
b->sign=0;
a->sign=0;
return calc_sub(b,a); //调换减数和被减数,变成情况一
}else if (a->sign==0 && b->sign==1) { // 情况二
b->sign=0;
bignumber_s *result = calc_add(a,b);
result->sign = 0;
return result;
} else if (a->sign==1 && b->sign==0) { // 情况三
a->sign=0;
bignumber_s *result = calc_add(a,b);
result->sign = 1;
return result;
}
}
// 实现无符号乘法,axb , z为积
void mul_impl(bignumber_s *x, bignumber_s *y, bignumber_s *z)
{
int n = x->len;
int m = y->len;
int i = 0;
int j = 0;
int carry = 0;
for (i=0; i<m; i++) {
// y的每一位乘以x,即计算y[i] * x[0...n] 并累加到z[i...i+n]中
for (j=0; j<n; j++) {
carry += y->data[i]*x->data[j] + z->data[i+j];
z->data[i+j] = carry%BASE;
carry /= BASE;
}
// 将剩余的进位,继续在z[i+n...n+m]中累加,从而完成y[i]乘以x[0...n]其积累加到z[i...m+n]中
for (; j+i<n+m; j++) {
carry += z->data[i+j];
z->data[i+j] = carry % BASE;
carry /= BASE;
}
}
}
bignumber_s *calc_mul(bignumber_s *a, bignumber_s *b)
{
int len = a->len + b->len;
bignumber_s *result = make_bignumber_temp(len,a->sign==b->sign?0:1); //a->sign==b->sign为情况一,否则为情况二。
mul_impl(a,b,result);
return result;
}
bignumber_s *calc_div(bignumber_s *a, bignumber_s *b)
{
// 实现除法
return NULL;
}
int main(int argc, char *argv[])
{
bignumber_s *a = make_bignumber_fromstr(argv[1]);
bignumber_s *b = make_bignumber_fromstr(argv[3]);
if (argc!=4) usage(argv[0]);
if (0 == strcmp(argv[2],"+"))
print_bignumber(calc_add(a,b));
else if (0 == strcmp(argv[2],"-"))
print_bignumber(calc_sub(a,b));
else if (0 == strcmp(argv[2],"x"))
print_bignumber(calc_mul(a,b));
else if (0 == strcmp(argv[2],"/"))
print_bignumber(calc_div(a,b));
else usage(argv[0]);
return 0;
}
2.5.4 演示
编译命令:
$ gcc -o calculatorv3 calculatorv3.c
结果演示:
2.6 实现除法
2.6.1 除法实现
除法的实现,我们直接转化为减法,大致思路如下:
- 用被除数减去除数,
- 差大于等于0时,商的值加1,将差赋值给被除数,重新回到步骤1
- 差小于0时,整个除法计算过程完毕。
这种实现方法,在某种情况下效率会非常低,但是实现起来比较简单。
有符号的除法,其处理更乘法是一致的。
除法实现代码如下:
// 大数加一操作
void plusone(bignumber_s *a)
{
int len = a->len ;
int i;
int carry = 1;
for (i=0; i<len; i++) {
carry += a->data[i];
a->data[i] = carry%BASE;
carry /= BASE;
}
}
// 大数除法实现
bignumber_s *calc_div(bignumber_s *a, bignumber_s *b)
{
bignumber_s *zero = make_bignumber_temp(1,0);
if (cmp(b,zero)==0) {// 除数为0 报错
fprintf(stderr,"Integer division by zero\n");
exit(-1);
}else if (cmp(a,zero)==0) { // 被除数为0 ,结果为0
return zero;
}
int len = a->len;
bignumber_s *result = make_bignumber_temp(len,a->sign==b->sign?0:1);
a->sign = 0;
b->sign = 0;
bignumber_s *temp = make_bignumber_temp(len, 0);
bignumber_s *aa = a;
while(1) {
if (cmp(aa, b)>=0) {
sub_impl(aa, b, temp);
plusone(result);
aa = temp;
} else {
free(temp);
return result;
}
}
}
2.6.2 大数计算器v4版本
/*
* file name : calculatorv4.c
* desp : calculator version 4
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BASE (10)
#define MAX(x,y) ((x)>(y)?(x):(y))
// 大数数据结构
typedef struct bignumber_s{
char sign; // 代表大数的符号,1为负数,0为正数
int len; // 代表大数的位数
char data[]; // 大数的数据内容,data[0]代表个位,data[1]代表十位,data[2]代表百位....
} bignumber_s;
bignumber_s *calc_add(bignumber_s *a, bignumber_s *b);
bignumber_s *calc_sub(bignumber_s *a, bignumber_s *b);
// 构造大数模板,len指向大数的数据位数,sign表示该大数的符号
bignumber_s *make_bignumber_temp(int len, int sign)
{
// 分配bignumber_s及其所代表数据 所需的内存
bignumber_s *temp = malloc(sizeof(bignumber_s)+len);
if (NULL == temp) {
perror("Malloc");
exit(-1);
}
temp->sign = sign;
temp->len = len;
memset(temp->data, 0, len);
return temp;
}
// 处理数字字符串冗余的0,即"00123" -> "123"
const char *strip_str(const char *str)
{
int i = 0;
int len = strlen(str);
for (i=0; i<len-1&&str[i]=='0'; i++) ;
return str+i;
}
// 将字符串数据填充进模板中
void fill_data_fromstr(bignumber_s *n, const char *str)
{
int i = 0;
int len = n->len;
for (i=0; i<len; i++) {
int d = str[len-1-i]-'0';
if (d>=0 && d<=9)
n->data[i] = d;
else {
fprintf(stderr, "Invalid Number:%s\n", str);
exit(-1);
}
}
}
// 从字符串构造一个大数
bignumber_s *make_bignumber_fromstr(const char *str)
{
// 处理负数字符串,即"-123"
int sign = 0;
if (str[0]=='-') {
sign = 1;
str++;
}
// 处理数字字符串冗余的0,即"00123" -> "123"
const char * striped_str = strip_str(str);
int len = strlen(striped_str);
// 指定数据位长度即符号,创建一个大数模板
bignumber_s *temp = make_bignumber_temp(len ,sign);
// 将字符串数据填充进模板中,完成大数创建
fill_data_fromstr(temp, striped_str);
return temp;
}
// 以字符串的形式打印输出一个大数
void print_bignumber(bignumber_s* b)
{
int len = b->len;
char *str = malloc(len+1);
int i = 0;
for (i=0; i<len; i++) {
str[i] = b->data[len-i-1]+'0';
}
str[len] = '\0';
fprintf(stdout,"%s%s\n", b->sign==1?"-":"", strip_str(str));
free(str);
}
void usage(const char *s)
{
fprintf(stderr, "Usage:%s number1 +-x/ number2.\n",s);
exit(-1);
}
// 实现无符号加法,a和b为加数,r为和
void add_impl(bignumber_s *a, bignumber_s *b, bignumber_s *r)
{
int i = 0;
char carry = 0;
int len = r->len;
for (i=0; i<len; i++) {
if (i<a->len)carry += a->data[i];
if (i<b->len)carry += b->data[i];
r->data[i] = carry%BASE;
carry /= BASE;
}
}
bignumber_s *calc_add(bignumber_s *a, bignumber_s *b)
{
// 情况一和情况四
if (a->sign==b->sign) {
// n位数+m位数,其和最大为max(n,m)+1位数
int len = MAX(a->len, b->len) + 1;
bignumber_s *result = make_bignumber_temp(len,a->sign);
add_impl(a,b,result);
return result;
} else if (a->sign == 0 && b->sign == 1) {//情况二
b->sign = 0; //b数去绝对值
return calc_sub(a,b);
} else if (a->sign == 1 && b->sign == 0) {//情况三
a->sign = 0; //a数去绝对值
return calc_sub(b,a);
}
}
// 实现无符号减法,a-b , r为差
void sub_impl(bignumber_s *a, bignumber_s *b, bignumber_s *r)
{
int i=0;
int borrow = 0;
int len = r->len;
int temp = 0;
for (i=0; i<len; i++) {
temp = a->data[i]+BASE-borrow-((i<b->len)?b->data[i]:0);
r->data[i] = temp%BASE;
borrow = temp/BASE?0:1;
}
}
int valid_len(bignumber_s *a)
{
int len = a->len;
int i = len-1;
for (i=len-1; i>=0; i--) {
if (a->data[i]==0) len--;
else break;
}
return len;
}
// 判断两个大数的大小
// a > b 返回1 ,a<b 返回 -1 , a==b 返回0
int cmp(bignumber_s *a, bignumber_s *b)
{
if (a->sign==0&&b->sign==1) return 1;
if (a->sign==1&&b->sign==0) return -1;
int sign = a->sign;
int alen = valid_len(a);
int blen = valid_len(b);
if (alen>blen) return (sign==1?-1:1);
else if (alen<blen) return (sign==1?1:-1);
else {
int i = 0;
int len = alen;
for (i=len-1; i>=0; i--) {
if (a->data[i]>b->data[i])return (sign==1?-1:1);
else if (a->data[i]<b->data[i]) return (sign==1?1:-1);
}
return 0;
}
}
bignumber_s *calc_sub(bignumber_s *a, bignumber_s *b)
{
// 情况一
if(a->sign==0 && b->sign==0) {
if (cmp(a,b)>=0) { // 被减数大于等于减数,即差大于等于0时
int len = a->len;
bignumber_s *result = make_bignumber_temp(len,0);
sub_impl(a,b,result);
return result;
} else { // 被减数小于减数,即差小于0时
int len = b->len;
bignumber_s *result = make_bignumber_temp(len,1);
sub_impl(b,a,result);
return result;
}
} else if (a->sign==1 && b->sign==1) { //情况四
b->sign=0;
a->sign=0;
return calc_sub(b,a); //调换减数和被减数,变成情况一
}else if (a->sign==0 && b->sign==1) { // 情况二
b->sign=0;
bignumber_s *result = calc_add(a,b);
result->sign = 0;
return result;
} else if (a->sign==1 && b->sign==0) { // 情况三
a->sign=0;
bignumber_s *result = calc_add(a,b);
result->sign = 1;
return result;
}
}
// 实现无符号乘法,x * y , z为积
void mul_impl(bignumber_s *x, bignumber_s *y, bignumber_s *z)
{
int n = x->len;
int m = y->len;
int i = 0;
int j = 0;
int carry = 0;
for (i=0; i<m; i++) {
// y的每一位乘以x,即计算y[i] * x[0...n] 并累加到z[i...i+n]中
for (j=0; j<n; j++) {
carry += y->data[i]*x->data[j] + z->data[i+j];
z->data[i+j] = carry%BASE;
carry /= BASE;
}
// 将剩余的进位,继续在z[i+n...n+m]中累加,从而完成y[i]乘以x[0...n]其积累加到z[i...m+n]中
for (; j+i<n+m; j++) {
carry += z->data[i+j];
z->data[i+j] = carry % BASE;
carry /= BASE;
}
}
}
bignumber_s *calc_mul(bignumber_s *a, bignumber_s *b)
{
int len = a->len + b->len;
bignumber_s *result = make_bignumber_temp(len,a->sign==b->sign?0:1); //a->sign==b->sign为情况一,否则为情况二。
mul_impl(a,b,result);
return result;
}
// 大数加一操作
void plusone(bignumber_s *a)
{
int len = a->len ;
int i;
int carry = 1;
for (i=0; i<len; i++) {
carry += a->data[i];
a->data[i] = carry%BASE;
carry /= BASE;
}
}
// 大数除法实现
bignumber_s *calc_div(bignumber_s *a, bignumber_s *b)
{
bignumber_s *zero = make_bignumber_temp(1,0);
if (cmp(b,zero)==0) {// 除数为0 报错
fprintf(stderr,"Integer division by zero\n");
exit(-1);
}else if (cmp(a,zero)==0) { // 被除数为0 ,结果为0
return zero;
}
int len = a->len;
bignumber_s *result = make_bignumber_temp(len,a->sign==b->sign?0:1);
a->sign = 0;
b->sign = 0;
bignumber_s *temp = make_bignumber_temp(len, 0);
bignumber_s *aa = a;
while(1) {
if (cmp(aa, b)>=0) {
sub_impl(aa, b, temp);
plusone(result);
aa = temp;
} else {
free(temp);
return result;
}
}
}
int main(int argc, char *argv[])
{
bignumber_s *a = make_bignumber_fromstr(argv[1]);
bignumber_s *b = make_bignumber_fromstr(argv[3]);
if (argc!=4) usage(argv[0]);
if (0 == strcmp(argv[2],"+"))
print_bignumber(calc_add(a,b));
else if (0 == strcmp(argv[2],"-"))
print_bignumber(calc_sub(a,b));
else if (0 == strcmp(argv[2],"x"))
print_bignumber(calc_mul(a,b));
else if (0 == strcmp(argv[2],"/"))
print_bignumber(calc_div(a,b));
else usage(argv[0]);
return 0;
}
2.6.3 演示
编译命令:
$ gcc -o calculatorv4 calculatorv4.c
结果演示:
大数计算器在解决除法问题的时候仅实现了商的求解,并未对余数进行过多的处理,其实这是不完善的。大家可以在此基础上,思考思考如何改进能够让计算器更加完善。
三、实验总结
本实验通过完成一个大数计算器的玩具程序,一方面有助于理解计算机数学运算的实现机理,另一方面有助于提升c语言的编程能力。 当然我们的大数计算器,在除法方面的性能还是不太令人满意的,希望有兴趣的读者朋友,后续能够对计算器实现进行改进。