大數運算
我們都知道變量都有一個數據類型,每個數據類型都有自己所表示的範圍,若當數據超過這個類型所表示的範圍,就會出現錯誤,我們稱這種現象叫做“溢出”。當然這樣就要求每個變量的地址中所存儲的數據不能夠超過數據類型所表示的範圍。整形int的表示範圍是-128~127,數據類型表示範圍最大的就屬long long型,表示範圍爲:0x7FFFFFFFFFFFFFFF~0x8000000000000000。Long long型的數據類型也不能夠表示更大的數據,那如果我們想要對一些大數據進行操作,那應當如何解決這種問題呢?
解決大數據的方法一般都是以一個字符串對其進行表示,操作時同樣用的是字符串。下面主要討論利用字符串進行大數據之間的加、減、乘、除的運算:
◆加法
加法操作主要有以下幾種情況:兩個數據都沒有溢出,其結果也沒有溢出的情況下,就可以直接進行“+”操作,若兩個沒有溢出且兩個數據爲異號,則相加之後也不會溢出,則直接進行“+”,其餘的情況都要利用字符串進行保存,然後從最後一位進行相加,期間要設立一個變量用來保存進位。
◆減法
減法操作一般是:兩個數據同號且都沒有溢出,或者兩個數據沒有溢出其結果也沒有溢出的情況下,就可以直接“-”,若兩個數據中至少有一個溢出,且兩個數據異號,可以調用“+”來實現“-”的操作,若兩個數據同號時,就需要重新定義“-”操作,對其進行運算。
◆乘法
當兩個數據中有一個數據爲0時,則其結果也爲0,當兩個數據都沒有溢出且其結果也不溢出,則可以直接進行“*”,否則,就和前面一樣重新定義“*”。
◆除法
要對兩個數據進行除運算,就必須要保證除數不爲0,若被除數<除數,則其結果爲0,若除數爲+/-1,則結果的絕對值和被除數絕對值相等,符號與除數和被除數的符號有關,若兩個數據的絕對值相等,則結果爲+/-1,。當兩個數據都沒有溢出的情況下,直接進行“/”,否則,重新定義“/”。
下面是具體的程序代碼:(附帶詳細的註釋)
#pragma once
#include <iostream>
using namespace std;
//處理大數據的方法就是利用字符串進行操作
#include <string.h>
#include <assert.h>
#define UN_INT 0xcccccccccccccccc
#define MAX_INT64 0x7FFFFFFFFFFFFFFF
#define MIN_INT64 0x8000000000000000
typedef long long INT64;
class BigData
{
public:
BigData::BigData(INT64 value = 0xcccccccccccccccc) //構造函數
:_value(value)
{
INT64ToString();
}
BigData(const char *ptr) //字符串構造函數
{
if (NULL == ptr) //檢查傳入的指針是否爲空的情況
{
return;
}
char *src = (char *)ptr;
char cSymbol = ptr[0]; //記錄符號位
if (cSymbol == '+' || cSymbol == '-') //傳入的字符串首字符是+/-
{
src++;
}
else if (cSymbol >= '0' && cSymbol <= '9') //傳入的字符串沒有+/-,直接是12345,默認爲正數
{
cSymbol = '+';
}
else
{
return;
}
while ('0' == *src) //防止這種情況000001234567
{
src++;
}
_strData.resize(strlen(ptr) + 1); //_strData大小,調用string類resize方法
_strData[0] = cSymbol; //給+/-留第一個空間(cSymbol爲‘+’)
_value = 0; //保存字符串轉換後的數據
int count = 1;
while (*src >= '0' && *src <= '9') //將字符進行拷貝
{
_value = _value * 10 + (*src - '0');
_strData[count++] = *src;
src++;
}
_strData.resize(count);
if (cSymbol == '-') //處理爲'-'的情況
{
_value = (-1) * _value;
}
}
//加法 溢出 無溢出
BigData operator+(const BigData& bigData) //+運算符重載(採用傳值的方式,傳引用則會更改this)
{
if (IsINT64Overflow() && bigData.IsINT64Overflow())
{
if (_strData[0] != bigData._strData[0]) //兩個值異號,不會溢出
{
return BigData(_value + bigData._value); //調用構造
}
//兩個值同號,有可能溢出,下面爲不溢出的條件
//10 - (2) = 8 10(溢出) 7(不溢出)
//(-10) - (-2) = -8 -13(溢出) -7(不溢出)
else if ((_value >= 0 && MAX_INT64 - _value >= bigData._value) ||
(_value < 0 && MIN_INT64 - _value <= bigData._value))
{
return BigData(_value + bigData._value);
}
}
return BigData(Add(_strData, bigData._strData).c_str());
}
BigData operator-(const BigData& bigData) //運算符-重載
{
if (IsINT64Overflow() && bigData.IsINT64Overflow()) //兩個數都沒有溢出
{
if (_strData[0] == bigData._strData[0]) //減法、同號
{
return BigData(_value - bigData._value);
}
else
{
//-10 + 3 = -7 -6(不溢出) -8(溢出)
//10 + (-2) = 8 7(不溢出) 9(溢出)
//異號、結果不溢出
if ((_value > 0 && MIN_INT64 + _value <= bigData._value) ||
(_value < 0 && MAX_INT64 + _value >= bigData._value))
{
return BigData(_value - bigData._value);
}
}
}
else
{
if (_strData[0] != bigData._strData[0]) //異號
{
return BigData(Add(_strData, bigData._strData).c_str());
}
else
{
return BigData(Sub(_strData, bigData._strData).c_str());
}
}
}
BigData operator*(const BigData& bigData) //運算符*重載
{
if (_value == 0 || bigData._value == 0)
{
return BigData(INT64(0)); //返回long long型0
}
if (IsINT64Overflow() && bigData.IsINT64Overflow()) //兩個數據都沒有溢出
{
//10 / 2 = 5 6(溢出) 3(不溢出)
//10 / (-2) = -5 -6(溢出) -2(不溢出)
if (_strData[0] == bigData._strData[0])
{
if ((_value > 0 && MAX_INT64 / _value >= bigData._value) ||
(_value < 0 && MAX_INT64 / _value <= bigData._value))
{
return BigData(_value * bigData._value);
}
}
else
{
//2: 10 / 2 = 5 -(-6)(溢出) -(-4)(不溢出)
//-2: 10 / -2 = -5 -6(溢出) -4(不溢出)
if ((_value > 0 && MAX_INT64 / _value >= -bigData._value) ||
(_value < 0 && MAX_INT64 / _value <= -bigData._value))
{
return _value * bigData._value;
}
}
}
return BigData(Mul(_strData, bigData._strData).c_str());
}
//除法 除數不爲零 無溢出直接除
//left < right 0
//right = +/-1;
//left = right 數值相等爲+、-1;
BigData operator/(const BigData& bigData) //運算符/重載
{
if ('0' == bigData._strData[1]) //檢查除數是否爲零
{
assert(false);
return BigData(INT64(0));
}
if (IsINT64Overflow() && bigData.IsINT64Overflow()) //兩個都不溢出的情況
{
return BigData(_value / bigData._value);
}
//left < right
if (_strData.size() < bigData._strData.size() ||
(_strData.size() == bigData._strData.size()
&& strcmp(_strData.c_str() + 1, bigData._strData.c_str() + 1) < 0))
{
return BigData(INT64(0));
}
//除數爲1或者-1
if (bigData._strData == "+1" || bigData._strData == "-1")
{
string ret = _strData;
if (_strData[0] != bigData._strData[0])
{
ret[0] = '-';
}
else
{
ret[0] = '+';
}
return BigData(ret.c_str());
}
//left = right
if (strcmp(_strData.c_str() + 1, bigData._strData.c_str() + 1) == 0)
{
string tmp = "+1";
if (_strData[0] != bigData._strData[0])
{
tmp[0] = '-';
}
return BigData(tmp.c_str());
}
return BigData(Div(_strData, bigData._strData).c_str());
}
private:
string Add(string left, string right) //字符串相加,left與right是同號的
{
int Lsize = left.size(); //返回的是字符串所佔的字節數
int Rsize = right.size();
//將兩個數據更改爲第一個數據大,第二個數據小,便於相加
if (Lsize < Rsize)
{
swap(Lsize, Rsize);
swap(left, right);
}
string ret; //ret用來存放相加的結果
ret.resize(Lsize + 1); //resize的作用就相當於開闢一塊Lsize+1大小的空間,相加最大超過Lsize的1個空間
ret[0] = left[0]; //先將符號位進行拷貝
char tmp = 0; //保存進位
for (int i = 1; i < Lsize; i++)
{
int src = left[Lsize - i] + tmp - '0';
if (i < Rsize) //防止第二個數據會一直進行取字符操作,會越界
{
src += right[Rsize - i] - '0';
}
ret[Lsize - i + 1] = src % 10 + '0';
tmp = src / 10;
}
ret[1] = tmp + '0';
return ret;
}
string Sub(string left, string right) //字符串相減
{
int Lsize = left.size();
int Rsize = right.size();
char cSymbol = left[0];
if ((left < right && Lsize == Rsize) || Lsize < Rsize)
{
swap(left, right);
swap(Lsize, Rsize);
if ('+' == cSymbol)
{
cSymbol = '-';
}
else
{
cSymbol = '+';
}
}
string ret; //保存相減結果
ret.resize(left.size());
ret[0] = cSymbol;
for (int i = 1; i < Lsize; i++)
{
char src = left[Lsize - i] - '0';
if (i < Rsize)
{
src -= right[Rsize - i] - '0';
}
if (src < 0) //判斷借位
{
left[Lsize - i - 1] -= 1;
src += 10;
}
ret[Lsize - i] = src + '0';
}
return ret;
}
string Mul(string left, string right) //字符串相乘
{
int Lsize = left.size();
int Rsize = right.size();
char cSymbol = '+';
if (left[0] != right[0])
{
cSymbol = '-';
}
if (Lsize > Rsize) //將數字較小的作爲第一個數據,能夠提高循環的效率
{
swap(left, right);
swap(Lsize, Rsize);
}
string ret;
ret.assign(Lsize + Rsize - 1, '0'); //assign申請Lsize+Rsize-1個空間,並初始化爲‘0’
int len = ret.size();
int num = 0;
ret[0] = cSymbol;
for (int i = 1; i < Lsize; i++)
{
char src = left[Lsize - i] - '0';
char dst = 0;
if (src == 0)
{
dst++;
continue;
}
for (int j = 1; j < Rsize; j++)
{
char ptr = src * (right[Rsize - j] - '0') + dst;
ptr += ret[len - j - num] - '0';
ret[len - j - num] = ptr % 10 + '0';
dst = ptr / 10;
}
ret[len - Rsize - num] += dst;
num++;
}
return ret;
}
string Div(string left, string right) //字符串相除
{
string ret;
ret.resize(1, '+');
if (left[0] != right[0]) //確定商的符號
{
ret[0] = '-';
}
char* pleft = (char*)(left.c_str() + 1);
char* pright = (char*)(right.c_str() + 1);
int len = right.size() - 1; //因爲有符號位
for (int i = 0; i < left.size() - 1; )
{
if (!(IsLeftString(pleft, len, pright, right.size()-1)))
{
ret.append(1, '0');
len++;
}
else
{
ret.append(1, loopmove(pleft, len, pright, right.size() - 1));
while (*pleft == '0' && len > 0)
{
pleft++;
i++;
len--;
}
len++;
}
if (len > right.size())//pLeft比pRight大一位結果爲0,則pLeft中含有0
{
pleft++;
len--;
i++;
}
if (len + i > left.size() - 1)
{
break;
}
}
return ret;
}
char loopmove(char* pleft, int Lsize, const char* pright, int Rsize)
{
assert(pleft != NULL && pright != NULL);
char pret = '0';
while (1) //被除數>除數
{
if (!IsLeftString(pleft, Lsize, pright, Rsize))
{
break;
}
for (int i = 0; i < Rsize; i++)
{
char ret = pleft[Lsize - i - 1] - '0';
ret -= pright[Rsize - i - 1] - '0';
if (ret < 0)
{
pleft[Lsize - i - 2] -= 1;
ret += 10;
}
pleft[Lsize - i - 1] = ret + '0';
}
while (*pleft == '0' && Lsize > 0)
{
pleft++;
Lsize--;
}
pret++;
}
return pret;
}
bool IsLeftString(const char* pleft, int Lsize, const char* pright, int Rsize) //判斷被除數大於或等於除數
{
if (Lsize > Rsize || (Lsize == Rsize && strcmp(pleft, pright) >= 0))
{
return true;
}
return false;
}
friend std::ostream& operator<<(std::ostream& _cout, const BigData& bigData) //<<運算符重載(友元)
{
if (bigData.IsINT64Overflow()) //無溢出的情況
{
_cout << bigData._value << std::endl;
}
else
{
char* ptr = (char*)bigData._strData.c_str();
//c_str是以const char*類型返回string中的字符串,因此對其要進行強制轉換
if (ptr[0] == '+')
{
ptr++;
}
_cout << ptr << std::endl;
}
return _cout;
}
bool IsINT64Overflow() const //const聲明方法是靜態的,聲明最大值
{
string temp("+9223372036854775807"); //string的對象temp實例化
if ('-' == _strData[0])
{
temp = "-9223372036854775808";
}
if (_strData.size() < temp.size()) //無溢出爲true
{
return true;
}
else if ((_strData.size() == temp.size()) && (_strData <= temp))
{
return true;
}
else
{
return false;
}
}
void INT64ToString() //將_value轉換爲字符串
{
char cSymbol = '+';
if (_value < 0)
{
cSymbol = '-';
}
_strData.append(1, cSymbol); //考慮符號位
INT64 pnumber = _value;
while (pnumber) //轉化字符串,轉換後的字符串與_value是相反的
{
int num = pnumber % 10;;
if (pnumber < 0)
{
num = 0 - num;
}
_strData.append(1, num + '0');
pnumber /= 10;
}
char* pleft = (char*)_strData.c_str() + 1;
char* pright = pleft + _strData.size() - 2;
while (pleft < pright) //字符串逆置
{
char tmp = *pleft;
*pleft = *pright;
*pright = tmp;
pleft++;
pright--;
}
}
private:
INT64 _value;
std::string _strData;
};
本文出自 “無心的執着” 博客,謝絕轉載!