大数求和

大数求和,支持负数

/**
 * 比较大小
 * @param {string} a 数字 a
 * @param {string} b 数字 b
 * @returns 去掉符号后,较大的在前,较小的在后,如果相等则位置三为 true [[biggerSym, bigger], [smallerSym, smaller], isEqual]
 */
function biggerFirst(a, b) {
    // 思路:
    //   保存两个数字的符号
    //   数字转数组,比较两个数大小,先根据长度比,长的更大,如果长度一致,就按位比
    //   较大的在前,较小的在后,如果一样大则第三个位置为 true。组成一个数组作为返回值
    let aSym = '', bSym = '';  // a 和 b 的符号
    if (a[0] === '-') {
        aSym = '-';
        a = a.substr(1);
    }
    if (b[0] === '-') {
        bSym = '-';
        b = b.substr(1);
    }
    a = a.split('');
    b = b.split('');
    const equal = () => [[aSym, a], [bSym, b], true];
    const big = () => [[aSym, a], [bSym, b], false];
    const small = () => [[bSym, b], [aSym, a], false];
    if (a.length > b.length) return big();
    if (a.length < b.length) return small();
    for (let i = 0; i < a.length; i++) {
        if (a[i] < b[i]) return small();
        if (a[i] > b[i]) return big();
        continue;
    }
    return equal();
}

/**
 * 大数求和(支持负数)
 * @param {string} s 第一个数字
 * @param {string} t 第二个数字
 * @returns 两数之和
 */
function sum(s, t) {
    // 思路
    //   比较两个数去符号后的大小,大的在前小的在后,如果一样大且符号不同,直接返回 0
    //   两种情况:
    //     1. 符号相同(同正或同负),则两数相加,结果的符号与较大数的符号一致
    //     2. 符号不同(一正一负),则大的减小的,结果的符号与较大数的符号一致
    //   所以,可以把两种情况合并,把不同处提前定义好。比如:
    //     1. 两数相加和相减的不同,可以提前根据符号是否相同来确定 sym 为 -1 还是 1
    //     2. 相加和相减的进位条件不同,通过 updateCarry 函数确定判断条件并更新 carry 的值
    //     3. carry 取余的差异,可以通过加 10 再取余,解决相加和相减两种情况中取余的差异
    //   最后用正则表达式将结果前面的 0 去掉,再根据较大数的符号来决定结果是否需要加负号
    //   返回结果

    const [[aSym, a], [bSym, b], equal] = biggerFirst(s, t);  // 大小比较,较大值优先
    const isDiffSym = aSym !== bSym;  // 是否异号
    if (isDiffSym && equal) return '0';  // 异号且相等,直接返回 0

    let result = '', carry = 0;  // result 存储结果,carry 存储中间值和进位
    // 进位更新函数,根据是否异号有两种更新方案
    const updateCarry = (() => {
        // 因为 carry 是加过 10 的,所以判断条件都要加 10
        if (isDiffSym) return c => c < 10 ? -1 : 0;
        return c => c > 19 ? 1 : 0;
    })();
    // 根据是否异号决定较小的数是加还是减
    const sym = isDiffSym ? -1 : 1;
    // 两个数从个位开始计算,注意最后的进位
    while (a.length || b.length || carry) {
        carry += 10 + ~~a.pop() + ~~b.pop() * sym;  // ~表示按位取反,~~任何非数字都会得到 0,加 10 是为了模拟减法中的借位且不影响加法的取余
        result = carry % 10 + result;  // 由于上一步加 10,因此此处加减法可以统一这么算
        carry = updateCarry(carry);  // 更新进位
    }
    // 将结果前面的 0 去掉
    result = result.replace(/^0+([1-9])/, '$1');
    // 结果的符号与较大数符号一致
    if (aSym === '-') result = '-' + result;
    
    return result;
}

console.log(sum('-90010', '90000'));

版权

本作品采用 CC BY-NC-ND 4.0 授权。