高精度减法

高精度减法

给定两个正整数(不含前导 0),计算它们的差,计算结果可能为负数,即大数减法。

所用方法和基本原理

该代码通过模拟竖式减法的方式实现大数减法,具体原理如下:

  1. 初始化与数位拆分
    • 首先确定两个数 ab 的长度 aLenbLen,并取二者长度的最大值 maxLen
    • 创建两个长度为 maxLen 的数组 subAsubB,用于存储两个数按位拆分后的数字。从字符串末尾开始遍历,将每个字符转换为对应的数字存入数组,较短的数高位补 0。
  2. 确定结果符号
    • 通过比较两个数的长度初步判断结果的正负。若 a 的长度大于 b 的长度,结果为正(sign = 1);若 a 的长度小于 b 的长度,结果为负(sign = -1)。
    • 若两个数长度相等,则从高位到低位逐位比较 subAsubB 中的数字,以确定结果的正负。
  3. 交换减数与被减数(若必要)
    • 如果结果为负(sign = -1),交换 subAsubB,使得后续按较大数减较小数的方式进行计算。
  4. 模拟减法运算
    • 创建结果数组 res 用于存储减法结果。初始化借位变量 carry 为 0。
    • subAsubB 逐位相减,并考虑借位 carry。如果当前位相减结果小于 0,则需要向高位借 1(即当前位加上 10 再减),同时更新借位 carry
  5. 构建结果字符串
    • 从结果数组的高位开始,跳过前导 0,将剩余数字添加到 StringBuffer 中。
    • 根据之前确定的符号 sign,如果 sign = -1,则在结果字符串前添加负号,最后返回结果字符串。

代码及注释

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"

  1. 初始化与数位拆分
    • aLen = 3bLen = 2maxLen = 3
    • subA = [3, 2, 1]subB = [5, 4, 0]
  2. 确定结果符号
    • 因为 aLen > bLen,所以 sign = 1
  3. 模拟减法运算
    • 第一次循环:i = 0res[0] = (3 - 5 - 0) < 0,则 res[0] = (3 + 10 - 5 - 0) % 10 = 8carry = 1
    • 第二次循环:i = 1res[1] = (2 - 4 - 1) < 0,则 res[1] = (2 + 10 - 4 - 1) % 10 = 7carry = 1
    • 第三次循环:i = 2res[2] = (1 - 0 - 1) = 0carry = 0。所以 res = [8, 7, 0]
  4. 构建结果字符串
    • startIdx = 2,从 startIdx 开始添加元素到 resStr,得到 "78"。由于 sign = 1,最终结果为 "78"

再假设 a = "45"b = "123"

  1. 初始化与数位拆分:同前例。
  2. 确定结果符号
    • 因为 aLen < bLen,所以 sign = -1
  3. 交换减数与被减数
    • 交换后 subA = [3, 2, 1]subB = [5, 4, 0]
  4. 模拟减法运算:同第一个例子的运算过程,得到 res = [8, 7, 0]
  5. 构建结果字符串
    • startIdx = 2,得到 "78"。由于 sign = -1,最终结果为 "-78"

方法的优劣

  1. 时间复杂度
    • 主要操作包括一次数位拆分(遍历两个字符串)、一次比较大小确定符号(最坏情况下遍历一次较长字符串)以及一次模拟减法运算(遍历一次较长字符串)。总体时间复杂度为 (O(n)),其中 n 是两个数中较大数的位数。
  2. 空间复杂度
    • 创建了三个数组(subAsubBres),其大小与较长数字的位数相关,再加上一个 StringBuffer 用于构建结果字符串。总体空间复杂度为 (O(n)),n 为两个数中较大数的位数。

优点
- 思路直观,模拟竖式减法的方式符合人类计算习惯,代码逻辑清晰,易于理解和实现。
- 能够处理任意长度正整数的减法,不受编程语言内置整数类型范围的限制。

缺点
- 空间复杂度较高,需要额外的数组来存储数字的每一位,对于非常大的数可能会消耗较多内存。
- 处理过程中包含较多的条件判断和数组操作,在处理大规模数据时,性能可能不如一些基于更高效数据结构或算法的大数减法实现。

posted @ 2025-06-26 17:05  起个数先  阅读(40)  评论(0)    收藏  举报