大数运算之字符串模拟

相信大家被特别大的两个数据做运算折磨过。当两个操作数或者运算结果超过类型的表示范围后会有意想不到的错误,这时候我们的电脑还不如我们高中用过的科学计算器,这是作为一个程序员所不能忍受的。所以我们得找到其他的方式来计算。这就是我们今天要讨论的字符串模拟大数运算。

我们的运算一般使用int类型来算的,那么首先我们先复习一下各种int类型的数据表示范围

unsigned int 0~4294967295   
int   -2147483648~2147483647 
unsigned long 0~4294967295
long   -2147483648~2147483647
long long的最大值:9223372036854775807
long long的最小值:-9223372036854775808
unsigned long long的最大值:1844674407370955161
__int64的最大值:9223372036854775807
__int64的最小值:-9223372036854775808
unsigned __int64的最大值:18446744073709551615

可以看到,在64位操作系统下,long long int表示的最大范围是-9223372036854775808--9223372036854775807所以当我们的两个操作数或者运算结果超过这个范围我们就定义它已经溢出,得用字符串来模拟运算。所以我们得有一个_IsINT64OverFlow()函数,用来判断是否溢出:

bool BigData:: _IsINT64OverFlow()const
{
	if (_value >= Min_INT64 && _value <= Max_INT64)
		return false;
	return true;
}

我们是用字符串来模拟的,用一个类来封装大数运算的加减乘除这些功能,所以先设计一下BigData这个类的基本构架。

#ifndef BIGDATA1_H
#define BIGDATA1_H
#include<iostream>
#include<string>
#include<assert.h>
#define Max_INT64 9223372036854775807
#define Min_INT64 (-9223372036854775807-1)
//不能直接用-9223372036854775808,当编译器看到9223372036854775808时直接判定
//9223372036854775808>INT64_MAX,直接用unsigned int64表示。当编译器看到负号时,
//直接对9223372036854775808取反,直接是它本身,编译器存不了那么大的数,报错
#define INT64 long long int
using namespace std;

class BigData
{
public:
	BigData(INT64 data);
	BigData(const char *str);
	BigData operator+(BigData& d);//加法
	BigData operator-(BigData& d);//减法
	BigData operator*(BigData& d);//乘法
	BigData operator/(BigData& d);//除法
private:
	friend string Add(string& left, string& right);
	friend string Sub(string& left, string& right);
	friend string Mul(string& left, string& right);
	friend string Div(string& left, string& right);
	bool _IsINT64OverFlow()const;//判断数据是否溢出
	friend ostream& operator<<(ostream& _cout, const BigData& d);
	void _INT64ToString();//将long long int数据转换成字符串
private:
	string _strvalue;
	INT64 _value;
};

#endif

这里有一个问题就是在用-9223372036854775807表示INT64_MIN时出现了一些问题;

error C4146: 一元负运算符应用于无符号类型,结果仍为无符号类型。

那时候各种搞不懂,然后就查了一下各位大神的解释,大体意思就是不能直用-9223372036854775808表示。当编译器看到9223372036854775808时直接判定9223372036854775808>INT64_MAX,直接用unsigned int64表示。当编译器看到负号时,直接对9223372036854775808取反,直接是它本身,编译器存不了那么大的数,编译器就报错。详细解释见一元负运算符

现在大体的框架已经搭好了。来看详细的实现过程:

(一)两个构造函数

BigData::BigData(INT64 data)
	:_value(data)
{
	_INT64ToString();
}
BigData::BigData(const char *str)
	: _value(0)
{
	if (str == NULL)
	{
		assert(false);
		return;
	}
	char symbol;
	if (str[0] == ‘+‘)
	{
		symbol = ‘+‘;
		str++;
	}
	else if (str[0] == ‘-‘)
	{
		symbol = ‘-‘;
		str++;
	}
	else if (str[0] >= ‘0‘&&str[0] <= ‘9‘)
	{
		symbol = ‘+‘;
	}
	else
	{
		return;
	}
	char* tmpstr = (char*)str;
	while (*tmpstr == ‘0‘)//跳过前面的‘0’
		tmpstr++;
	int i = 0;//剩下字符串的长度
	while (*tmpstr >= ‘0‘&& *tmpstr <= ‘9‘)
	{
		i++;
		_value = _value * 10 + *tmpstr - ‘0‘;
		tmpstr++;
	}
	if (symbol == ‘-‘)
	{
		_value = 0 - _value;
	}
	_strvalue.resize(i + 1);//相当于给_strvalue开辟空间
	_strvalue[0] = symbol;
	int j = 1;
	while (i--)
	{
		_strvalue[j++] = *str++;
	}

}
void BigData::_INT64ToString()
{
	INT64 tmp = _value;
	INT64 sym = tmp;
	string str;
	if (sym >= 0)
	{
		str.push_back(‘+‘);
	}
	else
	{
		str.push_back(‘-‘);
		tmp = 0 - tmp;
	}
	while (tmp)
	{
		char ctmp = tmp % 10 + ‘0‘;
		str.push_back(ctmp);
		tmp /= 10;
	}
	int right = str.size()-1;
	int left = 1;
	while (left < right)
	{
		swap(str[left++], str[right--]);
	}
	_strvalue = str;
}

使用字符串构造比较麻烦,我们在构造_strvalue的时候还要把字符串数据转换为long long int类型的_value,方便以后计算,如果字符串表示的数据没有溢出的话直接用内置的long long int来计算。字符串转换为int的重点就是要从字符串的最后一个字符开始转化,每次循环数据乘以10。最后可以算出整个字符串的值,如果是负数,用0-_value即可。

还有long long int类型转换为字符串函数。算法不难,只是字符串的第一个字符统一保存数据的符号,方便以后好计算。

(二)加法

BigData BigData::operator+( BigData& d)
{

	if (!_IsINT64OverFlow() && !d._IsINT64OverFlow() 
		&& (_value + d._value) <= Max_INT64 && (_value + d._value) >= Min_INT64)
	{
		_value += d._value;
	}
	else
	{
		OverflowFlag = true;
		_strvalue = Add(_strvalue, d._strvalue);
	}
	return *this;
}
string Add(string& left, string& right)
{
	if (left[0] != right[0])//符号不等
	{
		if (left[0] == ‘+‘)
		{
			right[0] = ‘+‘;
			return Sub(left, right);
		}
		else
		{
			left[0] = ‘+‘;
			return Sub(right, left);
		}
	}
	else
	{
		int lsize = left.size();
		int rsize = right.size();
		if (lsize == rsize)
		{
			int carry = 0;
			while (--lsize && --rsize)
			{
				char tmp = left[lsize];
				left[lsize] = (left[lsize] - ‘0‘ + right[rsize] - ‘0‘) % 10 + carry + ‘0‘;
				carry = (tmp - ‘0‘ + right[rsize] - ‘0‘) / 10;
			}
			if (carry == 1)
			{
				left.insert(1, "1");
			}
			return left;
		}
		else
		{
			if (lsize > rsize)
			{
				int carry = 0;//进位
				while (--lsize && --rsize)//不能为--rsize&&-lsize
				{
					char tmp = left[lsize];
					left[lsize] = (left[lsize] - ‘0‘ + right[rsize] - ‘0‘) % 10 + carry + ‘0‘;
					carry = (tmp - ‘0‘ + right[rsize] - ‘0‘) / 10;
				}
				while (carry == 1)
				{
					left[lsize] = left[lsize] + carry;
					carry = (left[lsize] - ‘0‘ + carry) / 10;
					lsize--;
				}

				return left;
			}
			else
			{
				int carry = 0;
				while (--rsize && --lsize)//注意不能为--lsize&&--rsize,
					//当lsize为1时不执行--lsize直接跳出
				{
					char tmp = right[rsize];
					right[rsize] = (left[lsize] - ‘0‘ + right[rsize] - ‘0‘) % 10 
						+ ‘0‘ + carry;
					carry = (tmp - ‘0‘ + left[lsize] - ‘0‘) / 10;
				}
				while (carry == 1)//当进位为1就一直往前加进位
				{
					right[rsize] = right[rsize] + carry;
					carry = (right[rsize] - ‘0‘ + carry) / 10;
					rsize--;
				}
				return right;
			}

		}
	}
}

加减乘除法都是用+-*/的重载来实现,实现时自己写的ADD,SUB,MUL,DIV。+调用ADD,-调用SUB,*调用MUL,/调用DIV。以后+-*/的重载函数我就不贴出来了,换个调用函数就行。这样的话方便以后的相互调用,只需要修改一下符号位。因为乘法是用加法模拟的,除法使用减法模拟的,减法用加法模拟的,按理来说我们使用加法就可以实现所有的运算。但是那个效率真的是惨不忍睹。

在这里,ADD的算法核心就是要保存低位向高位的进位。和我们手算是一样的。从两个字符串的最后一位开始往前相加,直到有一个字符串遇到_strvalue[0]的字符位为止,最后还要记得把最后的进位加上。在这里要考虑被加数加上进位以后还有进位的情况,所以在这我们使用了while来循环加。不用担心字符串的空间不够,因为两个位数一样的数相加,最多进位为1.



(三)减法

string Sub(string& left, string& right)
{
	if (left[0] != right[0])
	{
		if (left[0] == ‘+‘)
		{
			right[0] = ‘+‘;
			return Add(left, right);
		}
		else
		{
			right[0] = ‘-‘;
			return Add(left, right);
		}
	}
	else
	{
		int lsize = left.size();
		int rsize = right.size();
		if (lsize == rsize)
		{
			int borrow =0;
			while (--lsize && --rsize)
			{

				if (left[lsize] < right[rsize])
				{
					left[lsize] = left[lsize] + 10 - right[rsize] - borrow + ‘0‘;
					borrow = 1;
				}
				else
				{
					left[lsize] = left[lsize] - right[rsize] - borrow + ‘0‘;
					borrow = 0;
				}
			}
			return left;
		}
		else if (lsize > rsize)
		{
			int borrow = 0;
			while (--lsize && --rsize)
			{
				if (left[lsize] < right[rsize])
				{
					left[lsize] = left[lsize] + 10 - right[rsize] - borrow + ‘0‘;
					borrow = 1;
				}
				else
				{
					left[lsize] = left[lsize] - right[rsize] - borrow + ‘0‘;
					borrow = 0;
				}
			}
			while ( borrow==1 )
			{
				if (left[lsize] == ‘0‘)
				{
					left[lsize] = left[lsize] - ‘0‘ + 10 - borrow + ‘0‘;//若借位为0,
					//向更高位借位,eg:1000-10
					lsize--;
				}
				else
				{
					left[lsize] = left[lsize] - ‘0‘ - borrow + ‘0‘;
					borrow = 0;
				}

			}

			return left;
		}
		else
		{
			int borrow = 0;
			while (--rsize && --lsize)
				//得先让rsize--,若--lsize为0;将不会执行--rsize;
			{
				if (right[rsize] < left[lsize])
				{
					right[rsize] = right[rsize] + 10 - left[lsize] - borrow + ‘0‘;
					borrow = 1;
				}
				else
				{
					right[rsize] = right[rsize] - left[lsize] - borrow + ‘0‘;
					borrow = 0;
				}
			}
			while (borrow == 1)
			{
				if (right[rsize] == ‘0‘)
				{
					right[rsize] = right[rsize] - ‘0‘ + 10 - borrow + ‘0‘;//若借位为0,
					//向更高位借位,eg:1000-10
					rsize--;
				}
				else
				{
					right[rsize] = right[rsize] - ‘0‘ - borrow + ‘0‘;
					borrow = 0;
				}

			}
			return right;
		}
	}
}

减法的算法核心和加法差不多,每次从两个字符串的最后一位开始计算。要定义一个借位,低位向高位的借位。只要借位borrow为1就一直循环借位。

加法和减法之间可以相互调用,当一个正数加一个负数时就可以调用减法,会很方便,而且易懂。这里就体现了我们封装ADD,SUB,MUL,DIV的好处。

还有要注意的就是要用最大的字符串(最长的字符串)来减小的字符串。这样可以保证结果用最长的字符串就可以保存,不用考虑空间的问题。

(四)乘法

string Mul(string& left, string& right)
{
	string newstr;//创建一个临时sting存放相乘后的结果
	int lsize = left.size();
	int rsize = right.size();
	newstr.resize(lsize + rsize);
	int newsize = newstr.size();
	while (--newsize)//初始化string,如果不初始化,string里存的是‘\0’
	{
		newstr[newsize] = ‘0‘;
	}
	if (left[0] != right[0])//符号不等
	{
		newstr[0] = ‘-‘;
	}
	else
	{
		newstr[0] = ‘+‘;
	}
	int flag = 0;//标志每次积的最低位
	int carry = 0;
	if (lsize <= rsize)
	{
		while (--lsize)
		{
			newsize = newstr.size() - flag;
			rsize = right.size();
			while (--rsize)
			{
				char tmp = left[lsize];
				newstr[--newsize] = ((left[lsize] - ‘0‘) * (right[rsize] - ‘0‘)
					+ newstr[newsize]-‘0‘) % 10 + carry + ‘0‘;
				carry = ((tmp - ‘0‘) * (right[rsize] - ‘0‘)) / 10;
			}
			newstr[--newsize] = carry + ‘0‘;//把最后的进位存起来
			flag++;
		}
	}
	else
	{
		while (--rsize)
		{
			newsize = newstr.size() - flag;
			lsize = left.size();
			while (--lsize)
			{
				char tmp = left[lsize];
				newstr[--newsize] = ((left[lsize] - ‘0‘) * (right[rsize] - ‘0‘)
					+ newstr[newsize] - ‘0‘) % 10 + carry + ‘0‘;
				carry = ((tmp - ‘0‘) * (right[rsize] - ‘0‘)) / 10;
			}
			newstr[--newsize] = carry + ‘0‘;
			flag++;
		}
	}
	return newstr;
}

乘法的话就略微抽象一点,只要把握住一点,保存进位就会非常简单。在写之前应该想清楚的是进位的最大值,乘法中进位的最大值为9,所以也不用考虑空间的问题。最长的字符串完全可以存下来。

乘法中要注意的是不能破环两个乘数的值,如果修改了会产生意想不到的结果。所以定义一个newstr来存放结果而不像加减法那样直接在最长的串上操作。



(四)除法

string Div(string& left, string& right)
{
	string newstr;//创建一个临时sting存放相除后的结果
	int lsize = left.size();
	int rsize = right.size();
	newstr.resize(lsize);
	if (left[0] != right[0])
	{
		newstr[0] = ‘-‘;
	}
	else
	{
		newstr[0] = ‘+‘;
	}
	if (lsize < rsize)
	{
		newstr.push_back(‘0‘);
		return newstr;
	}
	else
	{
		left[0] = ‘+‘;
		right[0] = ‘+‘;
		int i = 0;
		int flag = rsize;
		int j = 0;
		string tmp;
		tmp.resize(rsize);
		while (j < flag)//将left的高位复制给临时变量
		{
			tmp[j] = left[j];
			j++;
		}
		j--;
		while (j < lsize)
		{
			newstr[j] = ‘0‘;
			while (Compare(tmp, right))
			{
				newstr[j]++;
				tmp = Sub(tmp, right);
			}
			tmp.push_back(left[++j]);
		}
		return newstr;
	}
}

除法说难也难,说简单也简单。要想简单的话我们直接用一个循环就可以搞定,循环相减,直到被减数小于减数。但是程序员总是不会屑于写这种效率低到爆的代码的。现在限于个人的知识范围,能想到效率最高的算法就是从被除数字符串截下和除数字符串一样长的字符串相减,使用一个newstr来标记商,newstr长度和被除数长度一样,全部初始化为‘0’。每次在与被除数相同下标的值++。直到被除数小于除数,再将原字符串的下一位push_back()到newstr,重复以上步骤。

 

其他函数较为简单,在这就不一一详述了,现在一个字符串模拟大数运算就写好了,可以丢弃手中的科学计算器,让我们的代码跑起来。

时间: 2024-11-04 19:56:24

大数运算之字符串模拟的相关文章

大数运算——字符串操作结合“竖式计算“思想的实现

总体原则: 字符串转整形数组,然后按照“竖式计算”的思想,按位(对于数组来说,就是对应位置的元素)进行运算,同时处理进位.退位.最后将整形数组转换为字符串输出. Ps:1.字符串转整形,本文采取逆序存储的方式,即将字符串的低位(大数的高位)放置到整形数组的高位. 2.本文提供的四个四则运算方法,所有的输入值(大数)必须为正整数. 一.加法 加法运算遵循从低位到高位运算的法则.将字符串转换为整形数组后,两数组对应元素相加,结果存储至结果数组的相应元素位置.同时对相加后的元素进行整除和取余运算(整除

大数运算

本人在在写这个小项目的时候,首先考虑到数的存储问题.计算机能够表示的最大值为0x7FFFFFFFFFFFFFFF,最小值为0x8000000000000000,那么要运算比这个大的数字该怎么办呢?是否可以使用字符串来保存比计算机能够表示的最大的数呢?怎么初始化大数呢?字符串的加减乘除该怎么实现呢?成员变量需要怎么定义呢? 1.是否可以使用字符串来保存比计算机能够表示的最大的数呢? 学过c++的同学肯定对string很熟悉,是c++用来维护字符串的一个类.可以用来维护字符串. 1).加法:用字符串

大数运算之 Java BigInteger 的基本用法

大数运算之 Java BigInteger 的基本用法 在程序设计竞赛中会遇到高精度运算的问题,C++没有高精度运算,只能手动模拟人工运算,手动实现高精度,而 java.math 包中的 BigInteger 提供了高精度的基本运算,因此竞赛中常用 Java 解决高精度运算问题. 当然如果比赛支持 python 就当我没说. BigInteger 对象的创建 BigInteger 类在 java.math.BigInteger 包中,首先引用该包. import java.math.BigInt

HDU 4927 大数运算

模板很重要 #include <cstdio> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; #define MAXN 9999 #define MAXSIZE 10 #define DLEN 4 class BigInt { private: int a[500]; //可以控制大数的位数 i

大数运算——加法

作为一个对编程没有很深研究的初学者,对于这些虽然没有任何算法的题仍然觉得很难很难,但是或许都是需要一个过渡时期吧,这是这几天的结果,很多有自己的考虑,自己的想法在里面,但是也百度查了很多,也看了很多别人写的关于这方面的. 先看一下关于大数方面的知识: bool型为布尔型,占1个字节,取值0或1. BOOL型为int型,一般认为占4个字节,取值TRUE/FALSE/ERROR. sbyte型为有符号8位整数,占1个字节,取值范围在128~127之间. bytet型为无符号16位整数,占2个字节,取

java 大数运算[转]

用JAVA 实现算术表达式(1234324234324 + 8938459043545)/5 + 343434343432.59845 因为JAVA语言中的long 定义的变量值的最大数受到限制,例如123456789987654321这样的整数就不能存放在long类型的变量中,如果这样两个大数相加或相乘,产生的结果会更大.比如,JAVA语言中如果使用long l = 1000000000这样定义没错,但如果加上2000000000变成 1000000000+2000000000测试结果就为-1

大数运算实现加减乘除四则运算

首先,让我们先来熟悉一下两种防止头文件被重复引用的处理#pragma once和#ifndef的区别 http://10739786.blog.51cto.com/10729786/1730827 接下来,开始我们的主题 <大数运算>    在平时生活中细心的你可能会发现,好多时候我们使用计算器时,总有一个显示的最大值,当计算超过这个值时就不能再计算了,那如果我们想要计算更大的数值要怎么办? 本文通过C++编程实现 大数的加减乘除四则运算 <思路与方法同样都在程序内部> 这次通过标

【002】}链表或字符串模拟加法/加一/乘法

链表模拟加法/字符串模拟二进制加法/数组模拟加一操作/打印1到最大的n位数/字符串模拟乘法 ============================================ Add Two Numbers 两个链表代表两个数字,每个结点的值都是一位数字,单链表逆序存放这两个数字, 构造出一个新的链表,代表这两个链表的和. 链表的尾插法,头结点dummy结点的运用,统一对prev指针的操作, C++ Code 1234567891011121314151617181920212223242

关于大数运算

在接触计算机这么久以来,我一直被大数运算困扰,确切的说是在一些特定方面非常有受挫感,在计算机中的数据类型对数字支持的数位有限制,例如: long   long  int  a = 0; 这里的a 最多可以表示一个9位的长整型数字,要想存储超过9位的数字,最可行的方法是使用数组来存储每一位的值.所以在遇到大数运算的时候可以借用数组来完成相应的运算操作.