大数处理——c++实现
本课题来自我的c++编程作业,文章利用大数处理类,类名:hugeNumber来对大数(编译器自定义的数值类型无法处理的数)进行四则运算(大数加法、大数减法及大数乘法的运算,除暂时没实现)和按精度四舍五入,自定义科学计数法等。内容广泛涉及运算符重载、字符连接、字符加减和字符乘除等作者原创函数。重要提示:本文涉及的所有函数使用的进制皆为10进制。(备注:已将该博客搬迁至CSDN)
一、解题思路
1 核心思想
文章用hugeNumber类对大数进行操作。前提假设,处理的数都使用科学计数法(未用科学计数法表示的大数,指数默认为0),hugeNumber类数据成员如下:
class hugeNumber{ //定义大数,10进制
private:
/*char deciNum[MAXSIZE]; */
char *deciNum; //尾数
long exponent; //指数
short sign; //符号位,取-1或1
int set_dot; //小数点位置,-1表示没有小数点
我将科学计数法分解为各个组成部分如图 1所示,其中:
①deciNum,科学计数法中的尾数,我用最大512字节表示该数;
②exponent,科学计数法中的指数,我用long int 表示;
③sign,整个数据的正负号;
④set_dot,当前尾数小数点真实位置,从0开始计数。
图 1
当从cin输入流获得一串字符,首先判断其是否为数值
if(!isNumber(nptr))
{
cout<<"This is not a number! " ;
exit(EXIT_FAILURE); //非正常退出
}
确定为数值后,hugeNumber类接受该字符串,并将其转换成如上所示4个属性,其构造函数原型为:
hugeNumber::hugeNumber(const char *nptr);
也可按提示,分别录入尾数部分、指数部分和正负号。其函数实现:
istream& operator>>(istream &in,hugeNumber &obj) //该函数声明为hugeNumber类友员函数
{
char ch;
in>>ch;
if(ch = '-')obj.sign = -1;
else obj.sign = 1;
in>>obj.deciNum;
in>>obj.exponent;
return in;
}
当我们获得某一hugeNumber类对象的各个属性值,便可以对该对象进行操作了。
2 大数按精度四舍五入
函数原型及方法如下:
void my_round(hugeNumber &hu ,int prec) //precision,规定的精度位
首先,对hugeNumber类的对象hu按指定自定义规格化。
norm(hu, -hu. exponent - prec);
该语句表示,将hu对象按指定精度规格化,并保持数值大小不变。例如,hu为3.14159e2,现在要求精确度为2,那么先将hu规格化为31415.9e-2。
接下来,判断小数点后一位是否大于0.5,如果大于0.5,给尾数个位加1,否则不加1,并且将小数点位的 ’.’ 修改为字符串结束符 ‘\0’,因为尾数使用字符串表示,所以规格化后的小数全部被截断。如图2所示
图2
2 大数加法运算
函数原型及基本算法思想描述:
2.1 两个整数大数相加
字符串a与字符串b整数字符串相加,加法考虑到进位,因为较大字符串a的2倍也只能产生1个进位,所有只预留1个进位给最终结果字符串c。
加法算法过程:
首先,判断a、b皆为整数,当hugeNumber类对象的数据成员set_dot为-1,表示该大数为整数;a、b对介后相加;c的指数为a、b对介后的指数。
其次,strlen(a)、strlen(b)获得a、b字符串长度,并确定保存结果的字符串长度,c字符串长度为a、b最长的长度加1(假设a是较长的大数)clength = strlen(a) + 1;
最后,从a、b字符串尾部开始从后往前相加,相当于从数的个位开始相加,如有进位,设置int变量 flag表述进位,flag最大为1,你知道为什么不能为2吗?待短字符串相加结束,将a(假设a是较长的大数)剩余的部位复制到c中,但仍然考虑与b累加的进位,如果a余下的是9999,切好又有进位,那么就要一直进位到数的最高位。如图 3所示。
图 3
2.2整数大数与带小数大数相加
整数大数与带小数大数相加,又分为两种情况:整数大数的整数部分大于带小数大数的整数部分和整数大数的整数部分小于带小数大数的整数部分。无论如何,a、b对介后才能相加;c的指数为a、b对介后的指数。所有这里尾数出现的整数和小数都是相当而已的,你完全可以把a、b都规格化成大整数,再相加。分别见图4 、图 5 。
图 4 整数大数的整数部分大于带小数大数的整数部分
图 5整数大数的整数部分小于带小数大数的整数部分
由图 4 所知,c字符串(保存最终结果)小数部分为b字符串小数部分,直接复制,b小数点前半段(b字符串整数部分)与整数大数a字符串相加同图 3 所示。
c的整个长度 = a 的整个长度 + b的小数长度 + 1 ;
图 5表示整数大数b的整数部分小于带小数大数a的整数部分,c的整个长度 = a 的整个长度 + 1 ;a的小数点后半段直接复制到c字符串中,a整数部分与整数大数b相加算法同图 3 所示。
2.3 两个带小数的大数相加
两个带小数的大数相加,分两张情况讨论:一种是a的整数部分大于b的整数部分,但a的小数部分短于b的小数部分,如图 6 所示。另一种情况,a无论是整数部分还是小数部分,都长于等于b,如图 7 所示。无论如何,a、b对介后才能相加;c的指数为a、b对介后的指数。所有这里尾数出现的整数和小数都是相当而已的,你完全可以把a、b都规格化成大整数,再相加。
图 6 字符串a的整数部分大于b的整数部分,但a的小数部分短于b的小数部分
图 6-1 补0处理,a、b字符可以从个位向高位依次相加
图 7 a无论是整数部分还是小数部分,都长于等于b
图 7-1 补0处理小数,a、b字符可以从个位向高位依次相加
3 大数减法运算
前提:无论如何,a、b对介后才能相减;c的指数为a、b对介后的指数。
3.1 两个整数大数相减
减法虽然没有溢出,但是会有正负,我用sign返回两个大数相减后的正负。因此,在做减法运算时,先判断结果的正负,在用较大的数减去较小的数。假设c = a – b;a > b,sign为正;当a < b,sign为负,如图 8 所示。
图 8两个整数大数相减
3.2 一个整数大数与另一个带小数大数间相减
减法虽然没有溢出,但是会有正负,同样用sign返回两个大数相减后的正负。此处有两种情形:一种是整数大数大于带小数的大数,另一种情形是整数大数小于带小数的大数。假设a是整数大数,b是带小数的大数。
图 9 整数大数大于带小数的大数
图 9-1 整数大数大于带小数的大数处理
第一种情形整数大数大于带小数的大数,先将整数大数a整数减 1,其小数 设为0.999+0.001;分别将0.999与b的小数相减,整数-1后与b的整数相减。见图9-1。
图 10整数大数小于带小数的大数
图 10-1 整数大数小于带小数的大数处理后相减
第二种情形整数大数小于带小数的大数,sign取-1,c = a – b变换为 c = -(b - a);
b 小数位直接复制给c,整数部分按照两个整数大数相减处理。
3.3 带小数大数间相减
有下列四种情形:
图 11 a的整数大于b,a的小数短于b
图 12 a的整数、小数都短于b
图 13 a的整数、小数都长于b
图 14 a的整数小于b,a的小数长于b
3 大数乘法运算
首先将a、b规格化为尾数为整数的大数,c的指数等于a、b规格化后的指数相加。有效位数长的做被乘数,位短的做乘数。假设a的长度大于b;
图 15
整个乘法过程,仿真实数据相乘,将所有中间结果累加到c中,如 c = tmp1 + tmp2 + tmp3 + … ;得到最终结果,由于博主比较懒,具体看算法实现。见图 15所示。
#include "hugeNumber.h"
extern char * strAdd(char *c , const char *a, const char *b);//数值型字符串相加
extern void integerAdd(char *c,const char *a,const char *b,int k,int i,int j,int flag);//整数字符串数组相加
int isNumber(const char *nptr);//判断字符串是否符号数据特征
void expandArray(char a[], int n);//n表示数组头插入的元素个数
void jumpZero(char * nptr);//跳过尾数前面的无用的0,除了小数点前的
int findDot(const char *nptr);//返回小数点位
void expandArray(char a[], int n)//n表示数组头插入的元素个数
{
char *tmp = new char[MAXSIZE];
if(!tmp)
{
cout<<"allocation failure!"<= 0;)//从第一个不是0的数开始记录
{
tmp[i++] = *s++;
got_digit = 1;
}
if (*s == '.')
{
if (!got_digit) // if (!i) 尾数的整数部分全是0或没有
{
tmp[0] = '0';
i = 1;
}
tmp[i] = '.';
s++;
i++;
while ( *s != '\0' && *s - '0' < 10 && *s - '0' >= 0)//提取尾数的小数
{
tmp[i++] = *s++;
}
}
tmp[i] = '\0'; //标记尾数结束
i = 0; //回头
while(tmp[i] != '\0') //复制字符数组
nptr[i++] = tmp[i];
nptr[i] = '\0'; //标记结束位
delete []tmp;
}
//返回小数点真实数组下标的位置
int findDot(const char *nptr)
{
const char *s = nptr;
int count = -1;
while(*s != '\0')
{
count++;
if(*s== '.')
return count;
s++;
}
return -1;//标识未找到小数点
}
////返回一个对应的小写字母
char TOLOWER(const char *s) //传入字符指针,地址
{
char tmp;
if(*s >= 'A' && *s <= 'Z')
tmp = *s + 'a' - 'A';
else if(*s >= 'a' && *s <= 'z')
;
else
errno = EINVAL; //#define EINVAL 22 无效值异常
return tmp;
}
//判断是否是实数,此处仅仅处理10进制
int isNumber(const char *nptr)
{
const char *s = nptr;
if (nptr == NULL) //判断字符串是否为空
{
goto noNumber;
}
s = nptr;
while (*s == 32) //跳过前面的空格,正确
++s;
if (*s == '-' || *s == '+') //碰到正负号,取下一个字符元素
++s;
int flag = 1; //遇到非数值字符标记
int got_dot = 0; //获得圆点个数
int got_digit = 0;//0-9数字
for(; (*s != '\0') ;++s)
{
//if( (*s - '0'< 10) && (*s - '0'>= 0)) //是否是字符型数字
if(isdigit(*s))
got_digit = 1 ;
else if (!got_dot && *s == '.') //没有遍历到小数点,又刚好碰到小数点时,设置函数状态
got_dot = 1;
else
break; //这个字符不是数值型的字符,可能碰见e
}
if (!got_digit ) //没有收集到数字
goto noNumber;
if( *s != '\0' && *s != 'e' && *s != 'E')//判断异常字符
goto noNumber;
if( *s == 'e'||*s == 'E')//没有遍历到E,又刚好碰到E时,设置函数状态
{
s++;
if (*s == '-' || *s == '+') //碰到正负号,取下一个字符元素
++s;
for(;*s != '\0';++s) //指数是否合法
{
//if( (*s - '0'< 10) && (*s - '0'>= 0)) //是否是字符型数字
if(isdigit(*s))
;
else
goto noNumber;
}
}
return 1;
noNumber:
errno = EINVAL; //#define EINVAL 22
return 0;
}
//按科学计数规格化,如12.34e5,规格化后1.234e6
void norm(hugeNumber &hu) //大染缸
{
int i = findDot(hu.deciNum); //小数点位置
int k = 0; //标记首个非0数字位置,
char *s = hu.deciNum;
for(; *s - '0'== 0 || *s == '.';) // 跳过首字符0或小数点,直到扫描到第一个非0
{
++s;++k;
}
//k++;//首个即非0也不是小数点,***k不能再加***
if(i == -1)//尾数为整数
{
i++;
while(hu.deciNum[i])i++;//记录尾数长度
hu.deciNum[i] = '.';
hu.deciNum[i+1] = '\0';
while(i > 1)
{
hu.deciNum[i] = hu.deciNum[i-1];
hu.deciNum[i-1] = '.'; //小数点前一位向右移一位
i--;
hu.exponent++;
}
}
else if (i-1 > k)
{
while(i-1 > k && i > 1)//小数点在首个非0数字后
{
hu.deciNum[i] = hu.deciNum[i-1];
hu.deciNum[i-1] = '.'; //小数点前一位向右移一位
i--;
hu.exponent++;
}
}
else if(i < k)
{
while(i < k )//小数点在首个非0数字前
{
hu.deciNum[i] = hu.deciNum[i+1];//小数点与后一个数据交换位置
hu.deciNum[++i] = '.';
hu.exponent--;
}
}
else
;
jumpZero(hu.deciNum);
//==========================================
//剔除末尾是小数点
//==========================================
int j = 0;
while(hu.deciNum[j]!= '\0') //有效字符长度
j++;
if(hu.deciNum[j-1] == '.' ) //最后一位是小数点
{
hu.deciNum[--j] = '\0';
hu.set_dot = -1; //没有小数点啦!
}
else
hu.set_dot = findDot(hu.deciNum);//找到小数点位置
}
//按exp介规格化
void norm(hugeNumber &hu, long exp ) //按exp介规格化
{
int i = hu.getdot(); //小数点位置
if(exp == hu.exponent)return;
else if(exp > hu.exponent)//小数点左移
{
while(exp > hu.exponent)//小数点左移
{
hu.exponent++;
if(i == -1)//没有小数点
{
int j = 0;
while(hu.deciNum[j]!= '\0') //有效字符长度
j++;
hu.deciNum[j] = '.';
i=j;//记录小数点位置
hu.deciNum[++j] = '\0';
}
if(i == 1) //例如1.234时
{
//char *tmp = new char [512];
//int j = 0;
//for(; hu.deciNum[j] != '\0'; j++)
// tmp[j+1] = hu.deciNum[j];
//tmp[j+1] = '\0';
//tmp[0] = '0';
//for(j = 0; tmp[j] != '\0'; j++)
// hu.deciNum[j] = tmp[j];
//delete tmp;
expandArray(hu.deciNum,1);//数组头插入1个元素0
hu.deciNum[0] = '0';
i++;//复制的小数点向右移动一位
}
hu.deciNum[i] = hu.deciNum[i-1];
hu.deciNum[--i] = '.'; //小数点前一位向右移一位
}
}
else //当exp < hu.exponent 时,小数点右移
{
if(i == -1)//没有小数点
{
int j = 0;
while(hu.deciNum[j]!= '\0') //有效字符长度
j++;
while(exp < hu.exponent) //尾数*10,即尾数添加0
{
hu.deciNum[j++] = '0';
hu.exponent--;
}
hu.deciNum[j] = '\0';
}
else
{
while(exp < hu.exponent) //有小数点,尾数*10,小数点右移
{
hu.exponent--;
if(hu.deciNum[i+1] == '\0') //一次右移
{
hu.deciNum[i+1] = '0';
hu.deciNum[i+2] = '\0';
}
hu.deciNum[i] = hu.deciNum[i+1];
hu.deciNum[++i] = '.';
}
jumpZero(hu.deciNum);
}
}
//==========================================
//剔除末尾是小数点
//==========================================
int j = 0;
while(hu.deciNum[j]!= '\0') //有效字符长度
j++;
if(hu.deciNum[j-1] == '.' ) //最后一位是小数点
{
hu.deciNum[--j] = '\0';
hu.set_dot = -1; //没有小数点啦!
}
else
hu.set_dot = findDot(hu.deciNum);//找到小数点位置
}
//对大数按规定精度,四舍五入
void my_round(hugeNumber &hu ,int prec) //precision,规定的精度位(不能再是大数)
{
int i = -1; //标记小数位,默认-1表示没有小数点
int k = 0; //标记首个非0数字位置,
norm(hu ,0); //按精度规整大数,数值大小不变
//cout<<"niming "< '9')
// flag = 1;
// hu.deciNum[j] %= 10;
// j--;
// while(flag)
// {
// if(hu.deciNum[j] > '9')
// flag = 1;
// hu.deciNum[j] %= 10;
// j--;
// }
do{
hu.deciNum[j] += flag;
if(hu.deciNum[j] <= '9')
{
flag = 0;
}
else
{
hu.deciNum[j] -= 10;
flag = 1;
j--;
}
}while(flag && j >= 0);
if(flag && j < 0) //数据溢出最高位
{
//char *tmp = new char [512];
//int j = 0;
//for(; hu.deciNum[j] != '\0'; j++)
// tmp[j+1] = hu.deciNum[j];
//tmp[j+1] = '\0';
//tmp[0] = '1';
//for(j = 0; tmp[j] != '\0'; j++)
// hu.deciNum[j] = tmp[j];
//delete tmp;
expandArray(hu.deciNum, 1);//数组头插入1个元素
hu.deciNum[0] = '1';
}
}
//norm(hu);//大数规格化
}
}
hugeNumber::hugeNumber(const hugeNumber &b) //拷贝构造函数
{
this->deciNum = new char [strlen(b.deciNum)+1]; //开辟尾数空间
if(!this->deciNum )
{
cout<<"allocation failure!"<deciNum, b.deciNum);
//memcpy(this->deciNum, b.deciNum, strlen(b.deciNum)*sizeof(char));//错
this->exponent = b.exponent;
this->sign = b.sign;
this->set_dot = b.set_dot;
//cout<<"你调用了复制构造函数"<deciNum = new char [MAXSIZE]; //开辟尾数空间
if(!this->deciNum )
{
cout<<"allocation failure!"<sign = *s == '-' ? -1 : 1; //保存正负号
if (*s == '-' || *s == '+') //碰到正负号,取下一个字符元素
++s;
int i = 0;
for(; *s != '\0' && *s - '0' < 10 ; i++)//遇到第一个不是数字的字符,停止
*++s;
if (*s == '.')
{
int set_dot = i;
}
strcpy(this->deciNum , deciNum);
this->exponent = exponent;
}
//==========================
//核心构造函数
//==========================
hugeNumber::hugeNumber(const char *nptr)
{
if(!isNumber(nptr))
{
cout<<"This is not a number! " ;
exit(EXIT_FAILURE); //非正常退出
}
this->deciNum = new char [MAXSIZE]; //开辟尾数空间
if(!this->deciNum )
{
cout<<"allocation failure!"<= 0; )//提取尾数的整数部分 for(; *s != '\0' && isdigit(*s) ; i++) ; for(; *s != '\0' && *s - '0' < 10 && *s != '.'; i++)
{
deciNum[i++] = *s++;
got_digit = 1;
}
//提取尾数的小数部分
if (*s == '.')
{
if (!got_digit) // if (!i) 尾数整数部分全是0或没有
{
deciNum[0] = '0';
set_dot = 1; //小数点在第二位
i = 1;
}
else
{
set_dot = i; //小数点在i位
}
deciNum[set_dot] = '.';
s++;
i++;
while ( *s != '\0' && *s - '0' < 10 && *s - '0' >= 0)//提取尾数的小数 for(; *s != '\0' && *s - '0' < 10 && TOLOWER(*s) != 'e'; i++)
{
deciNum[i++] = *s++;
}
}
deciNum[i] = '\0'; //标记尾数结束
//提取指数
if( *s == 'e'||*s == 'E')//刚好碰到E时,设置函数状态
{
s++;
int sign2 = 1 ; //指数可能是负数
sign2 = (*s == '-') ? -1 : 1; //保存正负号
if (*s == '-' || *s == '+') //碰到正负号,取下一个字符元素
++s;
while( *s != '\0' ) //提取指数的整数
{
exponent = exponent*10 + (*s++ - '0');
}
exponent *= sign2;
}
}
ostream& operator<<(ostream &out,hugeNumber &obj) //输出
{
if(obj.sign < 0)out<<"-";
out<>(istream &in,hugeNumber &obj) //输入
{
//char ch;
//in>>ch;
//if(ch = '-')obj.sign = -1;
//else
//{
// obj.sign = 1;
//
//}
//in>>obj.deciNum;
//in>>obj.exponent;
char *nptr = new char[MAXSIZE];
in>>nptr;
obj = hugeNumber(nptr);
return in;
delete []nptr;
}
//大数相加运算
hugeNumber hugeNumber::operator +(hugeNumber &b)
{
if(exponent != b.exponent)
{
if((exponent < b.exponent)) // 按指数小的对介
norm(b,this->exponent);
else
norm(*this,b.exponent);
}
//cout<<"b符号= "<sign > 0 && b.sign > 0)//判断正负号
{
c.sign = 1;
strAdd(c.deciNum, this->deciNum, b.deciNum);
}
else if (this->sign < 0 && b.sign < 0)
{
c.sign = -1;
strAdd(c.deciNum, this->deciNum, b.deciNum);
}
else if(this->sign > 0 && b.sign < 0)
{
/*if(this->sign < 0)
c = b - *this;
if(b.sign < 0)
c = *this - b;*/
int sgn = 1;
strSub(c.deciNum, this->deciNum, b.deciNum, sgn);
c.sign = sgn;
}
else
{
int sgn = 1;
strSub(c.deciNum, b.deciNum, this->deciNum, sgn);
c.sign = sgn;
}
//cout<exponent;
c.set_dot = findDot(c.deciNum);
// cout<<"niming"<exponent);
else
norm(*this,b.exponent);
}
hugeNumber c; //返回该对象
if(this->sign > 0 && b.sign > 0)//判断正负号
{
int sgn = 1;
strSub(c.deciNum, this->deciNum, b.deciNum,sgn);
c.sign =sgn;
}
else if (this->sign < 0 && b.sign < 0)
{
int sgn = 1;
strSub(c.deciNum, this->deciNum, b.deciNum,sgn);
c.sign = -sgn;
}
else if(this->sign > 0 && b.sign < 0)
{
strAdd(c.deciNum, this->deciNum, b.deciNum);
c.sign = 1;
}
else
{
strAdd(c.deciNum, this->deciNum, b.deciNum);
c.sign = -1;
}
//cout<exponent;
c.set_dot = findDot(c.deciNum);
return c;
}
//大数相减运算2
hugeNumber hugeNumber:: operator -(char str[])
{
if(!isNumber(str))
{
cout<<"This is not a number! " ;
exit(EXIT_FAILURE); //非正常退出
}
hugeNumber b (str);
return(*this - b);
}
//大数相乘运算
hugeNumber hugeNumber:: operator *(hugeNumber &b)
{
//if((exponent != b.exponent)) // 对介
// norm(b,this->exponent);
hugeNumber c; //返回该对象
int clen = strlen(this->deciNum)+strlen(b.deciNum)+1;
int i = 0;
for(; iexponent + b.exponent;
int cexp = 0;
if(this->sign > 0 && b.sign > 0)//判断正负号
{
strMul(c.deciNum, this->deciNum, b.deciNum, cexp, clen);
c.sign = 1;c.exponent += cexp;
}
else if (this->sign < 0 && b.sign < 0)
{
strMul(c.deciNum, this->deciNum, b.deciNum, cexp, clen);
c.sign = 1;c.exponent += cexp;
}
else if(this->sign > 0 && b.sign < 0)
{
strMul(c.deciNum, this->deciNum, b.deciNum, cexp, clen);
c.sign = -1;c.exponent += cexp;
}
else
{
strMul(c.deciNum, this->deciNum, b.deciNum, cexp, clen);
c.sign = -1;c.exponent += cexp;
}
//cout<
三、算法测试
1 测试按精度四舍五入
int main()
{
char str[MAXSIZE];
do{
cout<<"请输入一个实数(退出请输入esc):"<= 0 );
my_round(a,prec);
cout<
该主函数的实验结果如图 16 所示:
2 测试大数四则运算
//测试大数四则运算
int main()
{
char str[MAXSIZE];
do{
cout<<"请输入第一个实数(退出请输入esc):"<实验结果如图 17 所示
图17 大数四则运算实验结果
该文为作者原创,版权归云南师范大学信息学院所有,引用请注明出处!索取全部源码,请加 qq: 292729249
若编译出现下列错误:error C4996: 'strcat': This function or variable may be unsafe.
属兼容问题,请参照:http://heyunhuan513.blog.163.com/blog/static/160204220153894725887/