您的位置:首页 > 其它

大数运算

2016-03-31 09:37 441 查看
在进行大数运算的时候,因考虑到内存问题,所以直接采用算术运算的逻辑对数据进行处理,必定会导致结果的溢出,而无法保证所得结果的正确性。

为了避免上述情况,在数据运算过程中,有时需采用字符串模拟数据的运算,从而提高结果的可靠性。

//Bigdata.h

#ifndef BIG_DATA_H
#define BIG_DATA_H

#include <iostream>
using namespace std;
#include <string>

#define UN_INIT 0xcccccccccccccccc
#define MAX_INT64 0x7fffffffffffffff
#define MIN_INT64 0x8000000000000000

typedef long long INT64;

class BigData
{
public:
BigData(INT64 data = UN_INIT);
BigData(const char *pData);

BigData operator+(BigData& bigData);
BigData operator-(const BigData& bigData);
BigData operator*(const BigData& bigData);
BigData operator/(const BigData& bigData);
friend ostream& operator<<(ostream& _cout, const BigData& bigData);

protected:
string Add(string left, string right);//处理加/减中的同号
string Sub(string left, string right);//处理加/减中的异号
string Mul(string left, string right);
string Div(string left, string right);
void INT64ToString();//把数据进行字符串的存储
bool IsINT64Overflow()const;//以字符串形式给出的数据,转化为数值时,是否会溢出
bool IsLeftStrBig(char *pLeft, size_t LSize, char *pRight, size_t RSize);//
char SubLoop(char *pLeft, size_t LSize, char *pRight, size_t RSize);//循环相减

private:
INT64 m_llValue;  //可转化为数值 long long value
string m_strData; //数字大小已经溢出了,被放置到字符串中操作
};
#endif


//Bigdata.cpp

<strong><span style="font-size:18px;">#include<iostream>
#include "Bigdata.h"
#include <assert.h>

BigData::BigData(INT64 data) //在构造函数中要同时对传入的数据做两种类型的处理,一个是数值,另一个是字符串
: m_llValue(data)
, m_strData("")
{
INT64ToString();
}

BigData::BigData(const char *_pData)
{
// "-12345789"  "1234567" "+" "12457aaa123" "000001234567"
// "a23456789"
// atoi
assert(NULL != _pData);//检查指针

char cSybom = _pData[0];//拿下所给出的字符串的第0位
char* pData = (char*)_pData;
if ('+' == cSybom || '-' == cSybom)//给定的数字字符串是带有符号位的
{
pData++;
}
else if (*pData >= '0' && *pData <= '9')
{
cSybom = '+';
}
else //当给定的字符串的第0位既不是符号(‘+’/‘-’),也不是数字时,它就不能去模拟数字的运算
{
m_llValue = 0; //在函数 atoi 中,对这样的字符串进行数字转换时,该函数会返回0,所以在模拟时,就直接赋为0
m_strData = "0";
return;
}

// 去掉前置0  "000001234567"
while ('0' == *pData)
pData++;

m_strData.resize(strlen(pData) + 1);//调整字符串m_strData的大小(刚好够存储给定的字符串)
m_llValue = 0;
m_strData[0] = cSybom;
int iCount = 1;
while (pData)
{
if (*pData >= '0' && *pData <= '9')
{
m_llValue = m_llValue * 10 + *pData - '0';
m_strData[iCount++] = *pData++;
}
else
{    //可能中间会被截断的情况: "12457aaa123",则只需对前面出现的数字进行操作就可以了
break;
}
}
//再次重置空间大小,是因为若遇到中间被不合法字符阶段时,刚刚第一次设置的空间大小就会有一些被浪费。
m_strData.resize(iCount);

if ('-' == cSybom)
{//前面在转存为数字时,是抛开了符号位的,所以得到的结果是正数
m_llValue = 0 - m_llValue;
}
}

BigData BigData::operator+(BigData& bigData)
{
// 8 + -2  10
if (!IsINT64Owerflow() && !bigData.IsINT64Owerflow())//两者本身都没有溢出
{
if (m_strData[0] != bigData.m_strData[0])//两者异号,便可直接用算术的运算方法
{
return BigData(m_llValue + bigData.m_llValue);
}
else //同号
{
// 2 + 8  10 - 6 > 2
// -3 + -8  -10 - (-6) = -4 < -3
/*当两者都是正数的时候,假设所能表示的最大正数是10,再给定其中一个值 2,那么另一个数必须<=10-2,才能保证
两者相加的结果是小于 10 的。例:5 < 10 -2 ,那5+2肯定不会超过10,;相反,9 > 10-2,那么2+9>10,溢出*/
/*负数同理*/

// 同号,但相加结果没有溢出,也可直接用算术的运算方法
if (('+' == m_strData[0] && (INT64)(MAX_INT64 - m_llValue) >= bigData.m_llValue) ||
('-') == m_strData[0] && (INT64)(MIN_INT64 - m_llValue) <= bigData.m_llValue)
{
return BigData(m_llValue + bigData.m_llValue);
}
}
}

/*2 + 2 / -2 + -2 == -(2+2)
2 + -1
至少有一个溢出
计算结果溢出*/
string strRet;
if (m_strData[0] == bigData.m_strData[0])
{
strRet = Add(m_strData, bigData.m_strData);
}
else
{
strRet = Sub(m_strData, bigData.m_strData);
}

return BigData(strRet.c_str());
}

BigData BigData::operator-(const BigData& bigData)
{
if (!IsINT64Owerflow() && !bigData.IsINT64Owerflow())
{
if (m_strData[0] == bigData.m_strData[0])//同号相减,结果肯定不会溢出,直接进行算术运算
{
return BigData(m_llValue - bigData.m_llValue);
}
else//异号相减
{
// 10 + (-8) = 2 > 1// 3 - (-8); 1 - (-8)
// -10  -8  3    -8  2  -10 + 3 = -7 <=
if (('+' == m_strData[0] && (INT64)(MAX_INT64 + bigData.m_llValue)>= m_llValue) ||
('-' == m_strData[0] && (INT64)(MIN_INT64 + bigData.m_llValue) <= m_llValue))
{
return BigData(m_llValue - bigData.m_llValue);
}
}
}

// 1、至少有一个操作数溢出
// 2、相减的结果一定会溢出
// "999999999" "-111111"  "-9999999" "1111111"
string strRet;
if (m_strData[0] != bigData.m_strData[0])
{
strRet = Add(m_strData, bigData.m_strData);
}
else
{
strRet = Sub(m_strData, bigData.m_strData);
}
return BigData(strRet.c_str());
}

BigData BigData::operator*(const BigData& bigData)
{
if (0 == m_llValue || 0 == bigData.m_llValue)//只要一个为0,则结果必为0
{
return BigData(INT64(0));
}

if (!IsINT64Owerflow() && !bigData.IsINT64Owerflow())//两者都没有溢出
{
if (m_strData[0] == bigData.m_strData[0])//两者同号
{
// 10 /2 = 5 >= 1 2 3 4 5
// 10 /-2 = -5 <= -5 -4 -3 -2 -1
if (('+' == m_strData[0] && MAX_INT64 / m_llValue >= bigData.m_llValue) ||
('-' == m_strData[0] && MAX_INT64 / m_llValue <= bigData.m_llValue))
{
return BigData(m_llValue*bigData.m_llValue);
}
}
else
{
// -10 /2 = -5 <=
// -10/-2 = 5 >
if (('+' == m_strData[0] && MIN_INT64 / m_llValue <= bigData.m_llValue) ||
('-' == m_strData[0] && MIN_INT64 / m_llValue >= bigData.m_llValue))
{
return BigData(m_llValue*bigData.m_llValue);
}
}
}

return BigData(Mul(m_strData, bigData.m_strData).c_str());
}

BigData BigData::operator/(const BigData& bigData)
{
if (0 == bigData.m_llValue)
{
assert("除数不能为0!");
return BigData(INT64(0));
}

if (!IsINT64Owerflow() && !bigData.IsINT64Owerflow())
{
return BigData(m_llValue / bigData.m_llValue);
}

return BigData(Div(m_strData, bigData.m_strData).c_str());
}

// +
// +=
string BigData::Add(string left, string right)
{
int iLSize = left.size();
int iRSize = right.size();
if (iLSize < iRSize)//保证左串比右串的 size 大
{
swap(left, right);
swap(iLSize, iRSize);
}

string strRet;
strRet.resize(iLSize + 1); //考虑两者相加最高位产生进位,则结果的size比iLSize大1
strRet[0] = left[0];
char cStep = 0; //进位

//left = "+9999999"  size = 9
// right="1"   "+10000000"
for (int iIdx = 1; iIdx < iLSize; ++iIdx)
{
char cRet = left[iLSize - iIdx] - '0' + cStep;

if (iIdx < iRSize)
{
cRet += (right[iRSize - iIdx] - '0');
}

strRet[iLSize - iIdx + 1] = (cRet % 10 + '0');
cStep = cRet / 10;
}

strRet[1] = (cStep + '0');

return strRet;
}

string BigData::Sub(string left, string right)
{
// 1、左操作数 > 右操作数
// 2、确定符号位
int iLSize = left.size();
int iRSize = right.size();
char cSymbol = left[0];//先把符号位初始化,与left串的符号位保持一致
if (iLSize < iRSize ||
(iLSize == iRSize && left < right)) //保证左串比右串长,且大于右串
{
swap(left, right);
swap(iLSize, iRSize);
//进到这个条件就说明,符号位应该和原right串的符号保持一致
if ('+' == cSymbol)
{
cSymbol = '-';
}
else
{
cSymbol = '+';
}
}

string strRet;
strRet.resize(iLSize);
strRet[0] = cSymbol;

// 逐位相减
// 1、取left每一位,从后往前取
// 2、在right没有超出  取right每一位从后往前取
// 3、直接相减
// 4、 保存结果
for (int iIdx = 1; iIdx < iLSize; iIdx++)
{
char cRet = left[iLSize - iIdx] - '0';
if (iIdx < iRSize)
{
cRet -= (right[iRSize - iIdx] - '0');
}

if (cRet < 0)
{
left[iLSize - iIdx - 1] -= 1;
cRet += 10;
}

strRet[iLSize - iIdx] = (cRet + '0');
}

return strRet;
}

string BigData::Mul(string left, string right)
{
int iLSize = left.size();
int iRSize = right.size();
if (iLSize > iRSize)
{
swap(left, right);
swap(iLSize, iRSize);
}

char cSymbol = '+';
if (left[0] != right[0])
{
cSymbol = '-';
}

string strRet;
//strRet.resize(iLSize + iRSize - 1);
strRet.assign(iLSize + iRSize - 1, '0');
strRet[0] = cSymbol;
int iDataLen = strRet.size();
int iOffset = 0;//乘法,分开乘后,各个是要错位相加的;只要乘数一换,ioffset就该改变

for (int iIdx = 1; iIdx < iLSize; ++iIdx)
{
char cLeft = left[iLSize - iIdx] - '0';
char cStep = 0;
if (0 == cLeft)
{
iOffset++;
continue;
}

for (int iRIdx = 1; iRIdx < iRSize; ++iRIdx)
{
char cRet = cLeft*(right[iRSize - iRIdx] - '0');
cRet += cStep;
cRet += (strRet[iDataLen - iOffset - iRIdx] - '0');
strRet[iDataLen - iOffset - iRIdx] = cRet % 10 + '0';
cStep = cRet / 10;
}

strRet[iDataLen - iOffset - iRSize] += cStep;
iOffset++;
}
return strRet;
}

string BigData::Div(string left, string right)
{
char cSymbol = '+';
if (left[0] != right[0])
{
cSymbol = '-';
}

int iLSize = left.size();
int iRSize = right.size();
if (iLSize < iRSize ||
iLSize == iRSize && strcmp(left.c_str() + 1, right.c_str() + 1) < 0) //除数大于被除数,相除结果为0
{
return "0";
}
else
{
if ("+1" == right || "-1" == right)//除数为1,结果值为被除数
{
left[0] = cSymbol;
return left;
}
}

string strRet; //返回商的串
strRet.append(1, cSymbol);
char *pLeft = (char*)(left.c_str() + 1);//指向被除数,抛开符号位
char *pRight = (char*)(right.c_str() + 1);//指向除数,抛开符号位
int iDataLen = 1;//被除数串的长度
// "2422222222"  33
for (int iIdx = 0; iIdx < iLSize-1;)
{
if ('0' == *pLeft) //990000000000099 / 33  *pLeft为0时,就直接把0作为商
{
strRet.append(1, '0');
pLeft++;
iIdx++; //pLeft与iIdx是同步走的
continue;
}

if (!IsLeftStrBig(pLeft, iDataLen, pRight, iRSize - 1))//找到可以除以除数的子串
{
strRet.append(1, '0');//不够除时,商0
iDataLen++;//再扩大子串,子串范围:pLeft--pLeft+IDataLen-1
if (iIdx + iDataLen > iLSize)//在扩子串时,扩到了最后一位
{
break;
}
continue;
}
else
{
// 循环相减
strRet.append(1, SubLoop(pLeft, iDataLen, pRight, iRSize - 1));// SubLoop的返回值即为商值

// pLeft
while ('0' == *pLeft && iDataLen > 0)//上述循环相减后,得到的余数可能会把被除数串的最前面的几位改为0,
{									 //所以再找新的子串时,要把前面的0都跳过
pLeft++;
iIdx++;
iDataLen--;
}

iDataLen++; //紧接着要从上面一位下来
if (iIdx + iDataLen > iLSize)
{
break;
}
}
}
return strRet;
}

bool BigData::IsLeftStrBig(char *pLeft, size_t LSize, char *pRight, size_t RSize)
{
assert(NULL != pLeft && NULL != pRight);
if (LSize > RSize ||
LSize == RSize && strncmp(pLeft, pRight, LSize) >= 0)
{
return true;
}

return false;
}

char BigData::SubLoop(char *pLeft, size_t LSize, char *pRight, size_t RSize)
{
assert(NULL != pLeft && NULL != pRight);

char cRet = '0';
while (true)
{
if (!IsLeftStrBig(pLeft, LSize, pRight, RSize))
{
break;
}

// 做-=
int iLDataLen = LSize - 1;
int iRDataLen = RSize - 1;
while (iRDataLen >= 0 && iLDataLen >= 0)
{
if (pLeft[iLDataLen] < pRight[iRDataLen])
{
pLeft[iLDataLen - 1] -= 1;
pLeft[iLDataLen] += 10;
}

pLeft[iLDataLen] = pLeft[iLDataLen] - pRight[iRDataLen] + '0';
iLDataLen--;
iRDataLen--;
}

// "990000000000000000000000000099" 剔除被改变后的被除数中的前置0
while ('0' == *pLeft && LSize > 0)
{
pLeft++;
LSize--;
}

cRet++;
}

return cRet;
}

void BigData::INT64ToString()
{
//12345
char cSymbol = '+'; //设置符号位
INT64 temp = m_llValue;
if (temp < 0)
{
cSymbol = '-';
temp = 0 - temp;
}

m_strData.append(1, cSymbol);//在字符串m_strData后,追加一个 cSymbol,那么该字符串的0位就被符号(正负)占了
int iCount = 1;//从字符串的1号位置开始填数

//填充字符串的时候,更容易先得到数的低位,这样低位就会填充到字符串的高位上去,所以最后还需要一步翻转字符串的操作
// 54321
//(1)先把数字都填充到字符串上去
while (temp)
{
m_strData.append(1, temp % 10 + '0');
temp /= 10;
}

//(2)翻转字符串
char *pLeft = (char*)(m_strData.c_str() + 1);
char *pRight = (char*)(m_strData.c_str() + m_strData.size() - 1);
while (pLeft < pRight)
{
char ctemp = *pLeft;
*pLeft++ = *pRight;
*pRight-- = ctemp;
}

// 1 符号位
// 2 m_strData = 54321
}

bool BigData::IsINT64Overflow()const
{
string strTemp;
if ('+' == m_strData[0]) //若字符串的第0位是‘+’,则说明进来的数据是一个正数;反之,即为负数
{
strTemp = "+9223372036854775807"; //最大正数 7FFFFFFFFFFFFFFF 的十进制表示的大小
}
else
{
strTemp = "-9223372036854775808"; //最小负数 8000000000000000 的十进制表示的大小
}

//(1)如果被数字填充起来的字符串的长度>能表示的最大数字的长度,那么该进入的数据在存储的时候肯定会溢出
if (m_strData.size() > strTemp.size())
{
return true;
}
//(2)当size相等时,在比较两个字符串的大小,若前者大于后者,溢出
else if (m_strData.size() == strTemp.size() && m_strData > strTemp)
{
return true;
}
//总结上述两种情况:上述两种情况必须分开考虑,不能直接比较字符串的大小;
/*以正数来举例:"+95233"与"+9223372036854775807"直接比较,那么在第一个字符上就能判断出前者大于后者,但实际上
"+95233"并没有溢出*/

return false; //其余的情况,数据的存储都是不会发生溢出的
}

ostream& operator<<(ostream& _cout, const BigData& bigData)
{
if (!bigData.IsINT64Owerflow()) // 没有溢出
{
_cout<<bigData.m_llValue;
}
else
{
char* pData = (char*)bigData.m_strData.c_str();
if (pData[0] == '+')
{
pData++;
}
_cout<<pData;
}
return _cout;
}

</span></strong>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: