高精度减法
高精度减法
给定两个正整数(不含前导 0),计算它们的差,计算结果可能为负数,即大数减法。
所用方法和基本原理
该代码通过模拟竖式减法的方式实现大数减法,具体原理如下:
- 初始化与数位拆分:
- 首先确定两个数
a和b的长度aLen和bLen,并取二者长度的最大值maxLen。 - 创建两个长度为
maxLen的数组subA和subB,用于存储两个数按位拆分后的数字。从字符串末尾开始遍历,将每个字符转换为对应的数字存入数组,较短的数高位补 0。
- 首先确定两个数
- 确定结果符号:
- 通过比较两个数的长度初步判断结果的正负。若
a的长度大于b的长度,结果为正(sign = 1);若a的长度小于b的长度,结果为负(sign = -1)。 - 若两个数长度相等,则从高位到低位逐位比较
subA和subB中的数字,以确定结果的正负。
- 通过比较两个数的长度初步判断结果的正负。若
- 交换减数与被减数(若必要):
- 如果结果为负(
sign = -1),交换subA和subB,使得后续按较大数减较小数的方式进行计算。
- 如果结果为负(
- 模拟减法运算:
- 创建结果数组
res用于存储减法结果。初始化借位变量carry为 0。 - 对
subA和subB逐位相减,并考虑借位carry。如果当前位相减结果小于 0,则需要向高位借 1(即当前位加上 10 再减),同时更新借位carry。
- 创建结果数组
- 构建结果字符串:
- 从结果数组的高位开始,跳过前导 0,将剩余数字添加到
StringBuffer中。 - 根据之前确定的符号
sign,如果sign = -1,则在结果字符串前添加负号,最后返回结果字符串。
- 从结果数组的高位开始,跳过前导 0,将剩余数字添加到
代码及注释
public class BigNumberSubtraction {
// 实现两个正整数字符串的减法
public static String sub(String a, String b) {
// 用于标记结果的符号,1表示正数, -1表示负数
int sign = 1;
int aLen = a.length();
int bLen = b.length();
// 取两个数长度的最大值
int maxLen = Math.max(aLen, bLen);
// 创建数组subA用于存储字符串a按位拆分后的数字
int[] subA = new int[maxLen];
// 创建数组subB用于存储字符串b按位拆分后的数字
int[] subB = new int[maxLen];
// 将字符串a按位拆分并存入subA数组
for (int i = aLen - 1, j = 0; i >= 0; i--, j++) {
subA[j] = a.charAt(i) - '0';
}
// 将字符串b按位拆分并存入subB数组
for (int i = bLen - 1, j = 0; i >= 0; i--, j++) {
subB[j] = b.charAt(i) - '0';
}
// 比较两个数的大小,确定结果的符号
if (aLen > bLen) {
sign = 1;
} else if (aLen < bLen) {
sign = -1;
} else {
// 若长度相等,逐位比较
for (int i = aLen - 1; i >= 0; i--) {
if (subA[i] < subB[i]) {
sign = -1;
break;
} else if (subA[i] > subB[i]) {
sign = 1;
break;
}
}
}
// 创建结果数组res,长度为maxLen
int[] res = new int[maxLen];
// 创建StringBuffer用于构建结果字符串
StringBuffer resStr = new StringBuffer();
// 如果结果为负,交换subA和subB
if (sign == -1) {
int[] tmp = subA;
subA = subB;
subB = tmp;
}
// 初始化借位carry为0
int carry = 0;
// 模拟竖式减法,逐位相减
for (int i = 0; i < maxLen; i++) {
// 计算当前位的差,并处理借位
res[i] = (subA[i] - subB[i] - carry) >= 0?
(subA[i] - subB[i] - carry) % 10 : (subA[i] + 10 - subB[i] - carry) % 10;
// 更新借位
carry = (subA[i] - subB[i] - carry) >= 0? 0 : 1;
}
// 找到结果数组中第一个非零元素的位置,跳过前导0
int startIdx = maxLen - 1;
while (res[startIdx] == 0) {
startIdx--;
if (startIdx < 0) {
return "0";
}
}
// 将结果数组从有效位开始添加到StringBuffer中
for (int i = startIdx; i >= 0; i--) {
resStr.append(res[i]);
}
// 根据符号添加负号
if (sign == -1) {
return "-" + resStr.toString();
} else {
return resStr.toString();
}
}
}
举例说明
假设 a = "123",b = "45"。
- 初始化与数位拆分:
aLen = 3,bLen = 2,maxLen = 3。subA = [3, 2, 1],subB = [5, 4, 0]。
- 确定结果符号:
- 因为
aLen > bLen,所以sign = 1。
- 因为
- 模拟减法运算:
- 第一次循环:
i = 0,res[0] = (3 - 5 - 0) < 0,则res[0] = (3 + 10 - 5 - 0) % 10 = 8,carry = 1。 - 第二次循环:
i = 1,res[1] = (2 - 4 - 1) < 0,则res[1] = (2 + 10 - 4 - 1) % 10 = 7,carry = 1。 - 第三次循环:
i = 2,res[2] = (1 - 0 - 1) = 0,carry = 0。所以res = [8, 7, 0]。
- 第一次循环:
- 构建结果字符串:
startIdx = 2,从startIdx开始添加元素到resStr,得到"78"。由于sign = 1,最终结果为"78"。
再假设 a = "45",b = "123"。
- 初始化与数位拆分:同前例。
- 确定结果符号:
- 因为
aLen < bLen,所以sign = -1。
- 因为
- 交换减数与被减数:
- 交换后
subA = [3, 2, 1],subB = [5, 4, 0]。
- 交换后
- 模拟减法运算:同第一个例子的运算过程,得到
res = [8, 7, 0]。 - 构建结果字符串:
startIdx = 2,得到"78"。由于sign = -1,最终结果为"-78"。
方法的优劣
- 时间复杂度:
- 主要操作包括一次数位拆分(遍历两个字符串)、一次比较大小确定符号(最坏情况下遍历一次较长字符串)以及一次模拟减法运算(遍历一次较长字符串)。总体时间复杂度为 (O(n)),其中
n是两个数中较大数的位数。
- 主要操作包括一次数位拆分(遍历两个字符串)、一次比较大小确定符号(最坏情况下遍历一次较长字符串)以及一次模拟减法运算(遍历一次较长字符串)。总体时间复杂度为 (O(n)),其中
- 空间复杂度:
- 创建了三个数组(
subA、subB和res),其大小与较长数字的位数相关,再加上一个StringBuffer用于构建结果字符串。总体空间复杂度为 (O(n)),n为两个数中较大数的位数。
- 创建了三个数组(
优点:
- 思路直观,模拟竖式减法的方式符合人类计算习惯,代码逻辑清晰,易于理解和实现。
- 能够处理任意长度正整数的减法,不受编程语言内置整数类型范围的限制。
缺点:
- 空间复杂度较高,需要额外的数组来存储数字的每一位,对于非常大的数可能会消耗较多内存。
- 处理过程中包含较多的条件判断和数组操作,在处理大规模数据时,性能可能不如一些基于更高效数据结构或算法的大数减法实现。

浙公网安备 33010602011771号