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语言中表示一个大数,有两种方式:字符串和自行构造数据结构。

  1. 字符串表示,适合人解读,但不适用于机器运算

  2. 数据结构表示,适用于机器运算,但人对其进行解读比较困难

我们的设计,综合考虑上述两种方式的优缺点,我们自行构造数据用于表示大数以提供给机器进行运算,同时提供字符串与我们构造的数据结构进行互转的辅助性函数:

// 大数数据结构
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 实现无符号加法

加法的实现比较简单,我们借鉴小学时学过的竖式计算过程的思路:

思路大致是:

  1. 从最低位开始,对应位置相加,和大于10的向高位进位,取个位数为该位置相加的和
  2. 对应位置相加时,要加上来自低位的进位值
  3. 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 实现无符号减法

减法的实现与加法的实现思路相似,依旧请出的我们竖式计数法:

思路大致是:

  1. 从最低位开始,对应位置相减,不够减的向高位借位,得到的差作为该位置的差
  2. 对应位置相减时,要减去来自低位的借位值
  3. 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

乘法的实现思路:

  1. x与y的积,位数为n+m,即积为z[0...n+m]
  2. 先用y的每个位去乘以x,如上述例子先用2乘以145,再用1乘以145, 从而得出对应于y每个位的积。
  3. 再将步骤2计算出来的所有积,按其基数累加到积中。

这里有两点需要注意:

  1. y[i]乘以x[j]时,其积应在z[i+j]。
  2. 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 除法实现

除法的实现,我们直接转化为减法,大致思路如下:

  1. 用被除数减去除数,
  2. 差大于等于0时,商的值加1,将差赋值给被除数,重新回到步骤1
  3. 差小于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 ,结果为0return 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 ,结果为0return 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语言的编程能力。 当然我们的大数计算器,在除法方面的性能还是不太令人满意的,希望有兴趣的读者朋友,后续能够对计算器实现进行改进。

四、参考链接

  • C语言接口与实现

C语言实现大数计算器相关推荐

  1. 软件工程-c语言--基于at89c51单片机c语言编写的计算器,基于AT89C1单片机C语言编写的计算器.doc...

    基于AT89C1单片机C语言编写的计算器 #include #define uint unsigned int #define uchar unsigned char sbit dula=P2^6; ...

  2. linux 计算器 c语言,大神教你如何用C语言实现科学计算器

    原标题:大神教你如何用C语言实现科学计算器 用C实现的科学计算器 使用C语言写的科学计算器,可以实现四则运算.三角函数运算.指对数运算:优先级正确:能智能屏蔽空格,能识别输入错误和运算错误,能实现继续 ...

  3. GO语言基础----简易计算器

    GO语言基础----简易计算器 该计算器可以执行两个数字和一个计算符号的计算. 例如,1+1,3*5,9/2- 代码实现: package mainimport("fmt")fun ...

  4. Java语言GUI实现计算器

    Java语言GUI实现计算器以及源代码 一. 设计分析 1.1任务及其目的 设计一个计算器应用程序,完成复杂的科学运算.设计的计算器应用程序可以完成加法.减法.乘法.除法和科学函数运算.且有小数点.正 ...

  5. 7-105 sdut-C语言实验——三个数排序7-106 sdut-C语言实验——模拟计算器7-107 sdut-C语言实验——找中间数

    目录 7-105 sdut-C语言实验--三个数排序 7-106 sdut-C语言实验--模拟计算器 7-107 sdut-C语言实验--找中间数 7-105 sdut-C语言实验--三个数排序 分数 ...

  6. C语言实现简单计算器

    C语言实现简单计算器 一.背景 二.代码 1.数据结构 2.弟弟行为的编程 三.基本逻辑 中缀转后缀 (1)为什么要转 (2)怎么转 (3)注意事项 四.演示图片 一.背景 自己希望通过这个处女帖,来 ...

  7. PTA 7-106 sdut-C语言实验——模拟计算器

    PTA 7-106 sdut-C语言实验--模拟计算器 分数 12 作者 马新娟 单位 山东理工大学 简单计算器模拟:输入两个整数和一个运算符,输出运算结果. 输入格式: 第一行输入两个整数,用空格分 ...

  8. C++程序设计语言——一个桌面计算器示例

    目录 C++程序设计语言--一个桌面计算器示例 程序代码 C++程序设计语言--一个桌面计算器示例 C++程序设计语言一书中第10章 10.2节的桌面计算器示例. 程序代码 #include < ...

  9. C语言练手项目--C 语言制作简单计算器

    C 语言制作简单计算器 一.需求 二.实现 1.思路 2.代码 3.运行结果 三.参考链接 一.需求 使用 C 语言做一个简单的计算器,能执行加.减.乘.除操作 二.实现 1.思路 (1)标准输入到s ...

  10. Android开发:kotlin语言实现简易计算器

    Android开发:kotlin语言实现简易计算器 1. 实现效果 2. 主要文件代码: 界面布局:activity_main.xml文件代码 字符定义:string.xml文件代码 逻辑实现:Mai ...

最新文章

  1. shell版俄罗斯方块
  2. 机器视觉与机器学习牛人博客
  3. 【Groovy】MOP 元对象协议与元编程 ( 方法注入 | 使用 MetaClass 进行方法注入普通方法 )
  4. Scala集合List的常用方法:take/flatMap/filter/zip/union/intersect/diff及WordCount集合实现
  5. 突破RHEL各种版本高阶应用限制!群集,虚拟化想怎么装就怎么装!!
  6. mmsegmentation的demo测试-模型加载
  7. unity怪物攻击玩家减血_怪物猎人发布15周年 — 回顾历代封面怪之三大传奇怪物...
  8. python 一题多解 —— ndarray 一维数组的拼接
  9. 树状数组---Squared Permutation
  10. mac os 使用记录
  11. lua 远程调试 【zeroBrane 使用mobdebug】(good转)
  12. 区块链软件开发:DApps的五个制胜要点
  13. 点歌机终端服务器停止服务怎么办,服务器版点歌系统常见问题处理
  14. 同步时钟配置电脑自动校时
  15. 2022-2028年中国工业级无人机行业市场全景调查及投资潜力研究报告
  16. django mysql graphql_GraphQL graphene-django 基本使用文档
  17. “食物链”的顶端合约ALOKEX 顺势而行一万年太久只争朝夕
  18. selenium如何添加新标签页
  19. OFFICE 您正试图运行的函数包含有宏或需要宏语言的解决方法
  20. 软件众包网站有哪些?

热门文章

  1. 幼儿园观察记录的目的和目标_幼儿园观察记录应该怎么写?
  2. 李沐动手学深度学习V2-语义分割和Pascal VOC2012数据集加载代码实现
  3. Object-C 介绍
  4. 处理word 多级标题编号不联动的问题
  5. java excel模板中列表_java中自定义excel模板并且填充内容
  6. 计算机主机前耳机没声音,电脑前面耳机没声音的解决办法 电脑前面插耳机没声音怎么办...
  7. Java Web之EL表达式和JSTL
  8. MATLAB的图像显示函数imshow()详解
  9. 从零开始系类——模拟电子技术
  10. php连接打印机代码,PHP连接打印机