说到加减乘除,那应该是计算机的基本运算了,计算机之所以叫“计算机”就是因为拥有这些运算能力。但是当任何小的问题扩大化,都会成为一个大问题。因为计算机硬件的天然计算能力是限定位数的。例如32位CPU的运算能力就是2的32次方以内,64位CPU就是2的64次方以内。这在大多数的时候都是够用了,但在许多特殊情况下,却是不够用的。比如计算:107 的 50 次方;金融领域计算某个公式推演到20年后的情况,简化一点比如 1.102 的 20 次方,要求精确值,比如气象运算,医学模拟。在一些领域中2的64次是绝对不够用的!
那么如何解决这个问题呢?答案是:【软件模拟CPU的硬件运算】
这里我给出【大正整数】的【加法】和【乘法】,浮点数的加减乘除可以以此类推,后续也可能补充这部分的代码。
1.这里是大正整数【加法】的运算函数,在注释部分有详细的工作机制说明
string add(string &a,string &b) { /**********************************/ /* 采取模拟CPU运算器的方法 */ /* 1.字符ASCII码可以进行加减乘除 */ /* 例如:'0'+'1'= 48 + 49 =97 */ /* '6'+'1'-'0'='7' */ /* 所以:要进行两个字符的加法 */ /* 可采用a+b-48 */ /* 或者用a+b-'0' */ /* */ /* 2.每次运算后考虑采用一个carry */ /* 记录进位,所以每次加法应该加 */ /* 前次运算的进位,所以运算为: */ /* a + b + carry - '0' */ /* */ /* 3.要计算两个字符串代表的数值的 */ /* 加和,那么应该采用逆序运算: */ /* 比如:求"133"、"123"的和 */ /* 运算次序应该是 */ /* 3+3->3+2->1+1 */ /* 这种求和顺序导致逆序记录更加 */ /* 方便,即先记录成"652" */ /* 最后所有运算结束的时候再逆序 */ /* 得到"256" */ /* */ /* 4.运算的时候应当考虑字符串长度 */ /* 不等的情况,如"12""3456" */ /* 应先运算可运算的等长部分,即 */ /* "12""56" */ /* 最后将"34"与进位加和的结果添 */ /* 加到最终结果的前面 */ /**********************************/ int a_len = a.length(); int b_len = b.length(); /*存放最终结果*/ string result; /*存放临时进位、临时结果*/ int carry = 0; char consq = 0; int i,j; /*i,j在循环退出后,仍然需要用到,故在循环外声明*/ for(i=a_len-1,j=b_len-1; i>=0&&j>=0; --i,--j) /*循环能直接相加的部分*/ { consq = a.at(i) + b.at(j) - 48 + carry; carry = 0; if( consq > '9') { carry = 1; consq = consq - 10; } result.push_back(consq); } if(i>=0)/*如果a仍然有没加完的部分*/ { while(i>=0) { consq = a.at(i) + carry; carry = 0; if( consq > '9') { carry = 1; consq = consq - 10; } result.push_back(consq); --i; } } else if( j>=0 )/*如果b仍然有没加完的部分*/ { while(j>=0) { consq = b.at(j) + carry; carry = 0; if( consq > '9') { carry = 1; consq = consq - 10; } result.push_back(consq); --j; } } if(carry ==1) /*处理最后的进位*/ { result.push_back('1'); } /*翻转*/ std::reverse(result.begin(),result.end()); return result; };
string multiply(const string& a, const string& b) { /**********************************/ /* 采取模拟CPU运算器的方法 */ /* 1.字符ASCII码可以进行加减乘除 */ /* 例如:('2'-'0')*('5'-'0')=10 */ /* */ /* 所以:要进行两个字符的乘法 */ /* 可采用(a-'0')*(b-'0') */ /* */ /* 2.每次运算后考虑采用一个carry */ /* 记录进位,所以每次加法应该加 */ /* 前次运算的进位,所以运算为: */ /* (a-'0')*(b-'0') + carry */ /* */ /* 3.要计算两个字符串代表的数值的 */ /* 乘积,那么应该采用逆序记录, */ /* 最后所有运算结束的时候再逆序 */ /* */ /* 4.乘积运算要考虑移位操作,但其 */ /* 实可以用i、j循环变量确定当前 */ /* 与上次循环结果的偏移量,其实 */ /* 就是正序时就是i+j,但现在采用*/ /* 逆序记录,那么就是 */ /* str_a.length -1 -i + */ /* str_b.length -1 -j ; */ /**********************************/ int a_len = a.length(); int b_len = b.length(); /*最终结果*/ string result; /*设置初始结果,"0000...000",与b的位数相同*/ result.assign(b_len,'0'); /*临时结果、临时进位*/ int consq = 0; int carry = 0; for(int i=a_len-1; i>=0; --i) { /* a中某一位数乘以b中每个数*/ for(int j=b_len-1; j>=0; --j) { /*结果由三部分构成*/ consq = (a.at(i)-'0') * ( b.at(j)-'0') /*1. a*b的临时结果*/ + (result.at(a_len-i+b_len-j-2)-'0') /*2. 最终结果中这个位置的值*/ + carry ; /*3. 前面运算的进位*/ carry = 0; if( consq >9 ) { int temp = consq/10; carry = temp; consq = consq - carry*10; } result.at(a_len-i+b_len-j-2) = (consq+'0'); } /*处理最后的进位*/ result.push_back(carry+'0'); /*可能是0-9,0是有意义的,可以保证for循环中consq运算时*/ carry = 0; /*result.at(i+j)总是能够取到值的*/ /*carry使用后清零*/ } /*处理最终结果*/ if( result.at(a_len+b_len-1) == '0') { result.pop_back(); } /*翻转得到最终结果*/ reverse(result.begin(),result.end()); return result; };