这是高精度算法的第二篇,第一篇描述的是加法运算方式:
高精度加法
这一篇我们描述高精度减法。减法和加法有些类似。如果你理解了高精度数字加法完整的运算法则,我想理解减法也是非常容易的。我们仍然使用一个实例来帮助理解高精度减法的内容。不过,我们需要设置一个小小的限定:减法的最终结果一定是大于等于0
的。在高精度算法中,暂不考虑差为负数的情况。
我们现需要计算684930287-501245612
的值。
同样地,我们需要将两个数字转换成整数数组,并将其逆向存放。这是有必要的。
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
被减数 | 7 | 8 | 2 | 0 | 3 | 9 | 4 | 8 | 6 |
减数 | 2 | 1 | 6 | 5 | 4 | 2 | 1 | 0 | 5 |
这个表格和上一篇的表格几乎一模一样。我只是修改了最左侧一列的名称——从加数变为被减数与减数。
我们仍然回顾一下小学数学中所学习的减法法则。
从个位数开始,也就是表格中下标为0
的位置,7
减去2
,得到结果5
,5
并没有<0
,换句话说,7减5
是减的开的,并不会出现负数。我们可以直接将将结果保留在该数位。如果结果小于0
了,那么就需要考虑借位了。紧接着,下标为1
的8
减去1
得到结果为7
,这也是不需要借位的。下标为2
的2
减去6
的结果为-4
,是<0
的,因此需要向高一位借位,借一位为10
。因此,下标为2
的数位上结果将变成-4+10
。下一位的0
就需要记为-1
,这是比较合适的。-1
减去5
显然是不合适的。又需要向高位借位,使得3
变成2
,同样地,本位上-1-5+10
的结果就可以成为正数4
了。此时,下标已经来到4
的位置。3
被借走一位变成2
,这是需要拿来减去4
的,相减的结果显而易见,-8
是没办法填入最终结果的,我们只能向高位再借一次位,使得-8+10
变成正数2
。下标为5
的9
是不怕借位的,被借完之后,其值为8
,减去2
绰绰有余。结果为6
。下标为6
的4
减去1
结果为3
,不需要考虑借位。后续的8-0
和6-5
的结果均>0
,只需要将计算结果放置在本数位上即可。
这是实例的完整解析。显得又长又麻烦。我们使用表格形式来表达会显得直观不少。
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
被减数 | 7 | 8 | 2 | 0 | 3 | 9 | 4 | 8 | 6 |
减数 | 2 | 1 | 6 | 5 | 4 | 2 | 1 | 0 | 5 |
借位 | 0 | 0 | 0 | -1 | -1 | -1 | 0 | 0 | 0 |
结果 | 5 | 7 | 2-6+10 | 0+(-1)-5+10 | 3+(-1)-4+10 | 9+(-1)-2 | 3 | 8 | 1 |
借位的位置发生在高位,我便将高位处设置为-1
,以此表示借走了一位数字。在结果栏中,我采用加减法的方式表示,希望能够清晰地表达出数值是如何变化的。例如:下标为3
的结果栏中显示0+(-1)-5+10
的内容。0
是被减数,-1
是借给低位的数字,5
是减数,10
是向高位借来的数字。
我猜测,通过这样的描述,减法法则是表述完整的。下面我们尝试使用C++
语言描述这个过程。
//假设a1,a2已经定义,并且存放的方式是逆序的——我的意思是个位数字放在下标为0的位置
//其中a1,a2的假设内容如下:
//a1 {7, 8, 2, 0, 3, 9, 4, 8, 6}
//a2 {2, 1, 6, 5, 4, 2, 1, 0, 5}
//高精度减法——a1为被减数,a2为减数
//这个减法函数假设两个减数的数位均不超过100位
void sub(int a1[], int a2[])
{
int result[100] = {}; //存放两个高精度数字相减的结果
for(int i = 0;i < 100; ++i)
{
int num = result[i]; //记录借位 有借位则为-1,无借位则为0
num += a1[i] - a2[i]; //两个数相减的结果与借位的内容相加
if(num < 0) //小于0就需要考虑借位
{
num += 10; //使其成为正数
result[i + 1] = -1; //借位会使高位减少1
}
result[i] = num; //计算结果存放在本数位上
}// 经过这层循环,所有的数位都已经计算结束了。
//去前导0
int k = 100 - 1; //数组大小为100,下标最大为100-1
while(result[k] == 0 && k > 0) //k > 0 保证至少有一位数字输出
--k;
for(int i = k; i >= 0; --i)
cout << result[i]; //从高位向低位输出
}
与高精度加法类似,这种描述方式将在sub
函数内直接输出。对于某些情况而言,在函数内直接输出是不合时宜的。同样地,我们需要考虑到这种情况:684930287-501245612-13498
我们必须描述出能够将计算结果传递到函数外的方式。
void sub(int minuend[], int subtraction[])
{
for(int i = 0;i < 100; ++i)
{
minuend[i] -= subtraction[i]; //被减数的某一位数字直接减去减数
//减去之后结果可能小于0
if(minuend[i] < 0)
{
minuend[i] += 10; //使其变成正数
//由于原位置存在数字,这里不能采用赋值运算,而是高位减去1
minuend[i + 1] -= 1;
}
}//经过此循环,减数和被减数的每一位都经过运算,得到结果存放在minuend数组中
}
//为了防止有些学习者不能完整描述本道例题的完整代码,在这里补充主函数中内容
int main()
{
string minuend = "684930287"; //被减数
string nums[2] = {"501245612", "13498"}; //两个减数
int minuend_to_nums[100] = {}; //被减数转化为整数数组
for(int i = 0; i < minuend.size(); ++i)
minued_to_nums[i] = minued[minuend.size() - 1 - i] - '0';
for(int i = 0;i < 2; ++i) //两个减数
{
//使减数转化为整数数组
int subtraction[100] = {}; //减数转化为整数数组
string s = nums[i];
for(int j = 0; j < s.size(); ++j)
subtraction[i] = s[s.size() - 1 - i] - '0';
//减法 —— 被减数 - 减数
//减法结果存放在被减数(minuend)数组中
sub(minuend, subtraction);
}
//对结果去前导0
int k = 100 - 1;
while(minuend[k] == 0 && k > 0)
--k;
for(int i = k; i >= 0; --i)
cout << minuend[i];
}
终于在经过一段比较长的代码后,我们完成了关于高精度减法的运算。我猜测,已经描述的足够完整。