JS大數(shù)運(yùn)算與精度

起因

最近在項(xiàng)目上涉及到大數(shù)的展示,不僅是個(gè)大數(shù)垃喊,還是個(gè)小數(shù)猾普。然后我們對(duì)數(shù)字進(jìn)行驗(yàn)證的時(shí)候,發(fā)現(xiàn)數(shù)字太大了本谜,前端這邊根本無(wú)法算出正確的結(jié)果初家,而且小數(shù)部分還存在精度誤差問(wèn)題。這時(shí)候想到了利用 bignumber.js 來(lái)解決這個(gè)問(wèn)題乌助;但是我們的系統(tǒng)已經(jīng)基本進(jìn)入了后期優(yōu)化階段溜在,因?yàn)楦鞣N原因,這個(gè)時(shí)候再引入一個(gè)新的庫(kù)有些得不償失他托,而且用到的地方就這一個(gè)(其他涉及到數(shù)字的地方都有專(zhuān)門(mén)的方案用來(lái)解決精度問(wèn)題掖肋,但是無(wú)法解決大數(shù)的問(wèn)題)。所以我就想寫(xiě)個(gè)方法專(zhuān)門(mén)用來(lái)解決這個(gè)地方的精度問(wèn)題以及計(jì)算問(wèn)題赏参。

過(guò)程

精度問(wèn)題

造成精度丟失的原因目前我見(jiàn)過(guò)的常見(jiàn)的可能有以下幾種:

  1. 后臺(tái)傳過(guò)來(lái)的就是浮點(diǎn)型志笼,數(shù)字太大了,在傳輸?shù)斤@示的過(guò)程中把篓,哪怕不加任何運(yùn)算纫溃,精度也會(huì)丟失;
  2. toFixed()方法造成的精度丟失;
  3. 浮點(diǎn)數(shù)加減法造成的精度丟失。

下面我們來(lái)分別討論下這三種問(wèn)題產(chǎn)生的原因以及解決方法韧掩。

大數(shù)精度

我們發(fā)現(xiàn)在js中紊浩,數(shù)字一旦超過(guò)安全值,就開(kāi)始變得不再精準(zhǔn)疗锐,哪怕是簡(jiǎn)單的加法運(yùn)算坊谁。產(chǎn)生這種問(wèn)題的原因是js采用的是 IEEE 754 即IEEE二進(jìn)制浮點(diǎn)數(shù)算術(shù)標(biāo)準(zhǔn)中的雙精度浮點(diǎn)數(shù)。何為 IEEE 754滑臊?網(wǎng)上已經(jīng)又很多詳細(xì)的解釋了口芍,這里不再贅述。

js的安全值范圍是(-9007199254740991 ~ 9007199254740991)简珠。也就是 -(Math.pow(2, 53) - 1) ~ (Math.pow(2, 53) - 1)阶界。為了避免超出安全值范圍導(dǎo)致精度丟失,只需要讓后端傳String類(lèi)型即可聋庵。

toFixed()

我們先看以下幾個(gè)toFixed結(jié)果膘融。

(1.345).toFixed(2) // 1.34 -- 錯(cuò)誤
(1.375).toFixed(2) // 1.38 -- 正確
(1.666).toFixed(2) // 1.67 -- 正確
(1.636).toFixed(2) // 1.64 -- 正確
(1.423).toFixed(2) // 1.42 -- 正確
(1.483).toFixed(2) // 1.48 -- 正確

經(jīng)過(guò)幾次試探,我們發(fā)現(xiàn)x.toFixed(f)偶爾會(huì)發(fā)生精度丟失的問(wèn)題祭玉。
現(xiàn)在看看為什么會(huì)出現(xiàn)這樣的問(wèn)題氧映。研究了一下ECMA 262中對(duì)Number.prototype.toFixed9(fractionDigits)指定的規(guī)則。純英文的脱货,我就不翻譯了岛都。涉及到精度的步驟大概是下面這樣律姨。

// (1.345).toFixed(2)
// 步驟10.a
134 / Math.pow(10, 2) - 1.345 // -0.004999999999999893
135 / Math.pow(10, 2) - 1.345 // 0.0050000000000001155
// 我們?nèi)∽罱咏?的值為 -0.004999999999999893,然后根據(jù)步驟10.c得到值為 1.34

// (1.375).toFixed(2)
// 步驟10.a
137 / Math.pow(10, 2) - 1.375 // -0.004999999999999893
138 / Math.pow(10, 2) - 1.375 // 0.004999999999999893
// 兩個(gè)值的絕對(duì)值大小相同臼疫,所以我們?nèi)≥^大的值 0.004999999999999893择份,然后根據(jù)步驟10.c得到值為 1.38

*為什么1.345對(duì)應(yīng)的步驟10.a要用134和135?

在規(guī)范中沒(méi)有解釋這個(gè)n的來(lái)源烫堤,我根據(jù)上下文理解應(yīng)該是 n = (x * Math.pow(10, f)).toString().split('.')[0]荣赶,其中x為原值,f為參數(shù)鸽斟;然后又因?yàn)樗纳嵛迦胫豢赡転楫?dāng)前值或者當(dāng)前值加1拔创,所以用的是134和135。

顯然富蓄,根據(jù)內(nèi)部的運(yùn)算規(guī)則剩燥,toFixed的精度丟失是不可避免的,所以我們可以通過(guò)重寫(xiě)toFixed方法來(lái)解決這個(gè)問(wèn)題立倍。

// 未優(yōu)化
Number.prototype.toFixed = function (f) {
    let params = Number(f)
    const num = this
    if (isNaN(num)) return `${num}` // 處理NaN返回
    if (isNaN(params)) params = 0 // 處理參數(shù)NaN情況
    if (params > 100 || params < 0) throw new RangeError('toFixed() digits argument must be between 0 and 100') // 處理參數(shù)大小問(wèn)題
    let temp = num * Math.pow(10, params) //  這里是為了使得需要保留的放在整數(shù)位灭红,需要舍去的放在小數(shù)位
    const tempInteger = temp.toString().split('.')[0] // temp的整數(shù)位
    const judgeInteger = (temp + 0.5).toString().split('.')[0] // temp + 0.5的整數(shù)位
    const tempArr = tempInteger.split('')
    tempArr.splice(tempArr.length - f, 0, '.')
    const judgeArr = judgeInteger.split('')
    judgeArr.splice(judgeArr.length - f, 0, '.')
    // 判斷temp + 0.5之后是否大于temp,大于則說(shuō)明尾數(shù)需要進(jìn)位帐萎,相等則代表不需要
    return judgeInteger > tempInteger ? `${judgeArr.join('')}` : `${tempArr.join('')}`
}

浮點(diǎn)數(shù)加減

我們經(jīng)常會(huì)遇到這種問(wèn)題比伏,0.1 + 0.2 !== 0.3。這是因?yàn)閖s在運(yùn)算的時(shí)候會(huì)先把數(shù)字轉(zhuǎn)換為二進(jìn)制疆导,但是一些小數(shù)轉(zhuǎn)為二進(jìn)制是無(wú)限循環(huán)的,所以會(huì)造成結(jié)果的誤差葛躏〕憾危看以下代碼。

(0.1).toString(2)       // 0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 101   --> 對(duì)于后三位:1001 最后一個(gè)1進(jìn)位得到 101,即 101
(0.2).toString(2)       // 0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 01    --> 對(duì)于后三位:0011 最后一個(gè)1進(jìn)位得到 010,即 01
(0.3).toString(2)       // 0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 11    --> 對(duì)于后三位:1100 最后兩個(gè)0舍去得到 11
(0.1 + 0.2).toString(2) // 0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1101       --> 轉(zhuǎn)換為十進(jìn)制為 0.30000000000000004

小數(shù)轉(zhuǎn)換二進(jìn)制時(shí)的無(wú)限循環(huán)不可避免舰攒。所以我有個(gè)想法就是將其轉(zhuǎn)換為字符串败富,然后按小數(shù)點(diǎn)分割成兩部分,每部分都一位一位算摩窃,最后再將兩部分和小數(shù)點(diǎn)拼接起來(lái)兽叮,因?yàn)橛?jì)算的時(shí)候都是18以?xún)?nèi)(為何是18?單位最大為9猾愿,9 + 9 = 18)的整數(shù)加減法鹦聪,所以這樣可以避免因?yàn)樾?shù)轉(zhuǎn)二進(jìn)制而造成的誤差。下一節(jié)蒂秘,詳細(xì)介紹一下這個(gè)思路的實(shí)現(xiàn)過(guò)程泽本。

大數(shù)運(yùn)算(浮點(diǎn)數(shù)運(yùn)算)

現(xiàn)在我們?cè)敿?xì)介紹一下上一節(jié)所說(shuō)的思路的實(shí)現(xiàn)過(guò)程。首先我們看加法姻僧。

大數(shù)加法

在開(kāi)始以前规丽,我們先做一些準(zhǔn)備蒲牧,考慮一下都有哪些可能性,以及可能出現(xiàn)的BUG赌莺。

符號(hào)及NaN

先寫(xiě)一個(gè)簡(jiǎn)單的add(x, y)方法冰抢。

const add = (x, y) => x + y

通過(guò)傳不同的參數(shù),可能會(huì)出現(xiàn)以下幾種情況:

  1. 傳入兩個(gè)非負(fù)數(shù)艘狭,正常計(jì)算挎扰;
  2. 一正一負(fù),加法變減法缓升;
  3. 均為負(fù)數(shù)鼓鲁,絕對(duì)值加法運(yùn)算,然后取負(fù)港谊;
  4. 一個(gè)或多個(gè)為非數(shù)字骇吭,即為NaN,會(huì)導(dǎo)致結(jié)果出錯(cuò)歧寺;
  5. 一個(gè)或多個(gè)為Boolean類(lèi)型或者null時(shí)燥狰,需先轉(zhuǎn)換為其對(duì)應(yīng)的數(shù)值再進(jìn)行計(jì)算;

然后我們?cè)赼dd方法里面處理一下這幾種情況斜筐。

/**
 * 
 * @param {String} x 
 * @param {String} y 
 */
const add = (x = '', y = '') => {
    if (Number.isNaN(Number(x)) || Number.isNaN(Number(y))) return x + y // 當(dāng)一個(gè)或多個(gè)為非數(shù)字龙致,直接拼接字符串

    if (typeof x === 'boolean' || x === null) x = Number(x).toString() // 當(dāng)x為boolean類(lèi)型或者null時(shí),轉(zhuǎn)換為其對(duì)應(yīng)的數(shù)值
    if (typeof y === 'boolean' || y === null) y = Number(y).toString() // 當(dāng)y為boolean類(lèi)型或者null時(shí)顷链,轉(zhuǎn)換為其對(duì)應(yīng)的數(shù)值

    let calMethood = true // 運(yùn)算方式目代,true為加法運(yùn)算,false為減法運(yùn)算(一正一負(fù)時(shí)需要減法運(yùn)算)
    let allAegative = false // 是否需要給結(jié)果添加負(fù)號(hào)嗤练,true需要榛了,false不需要
    let sum = '' // 和,字符串加減煞抬,所以定義為空串
    let flag = 0 // 進(jìn)位標(biāo)志霜大,加法:當(dāng)當(dāng)前位計(jì)算大于9時(shí),需要進(jìn)位革答,加法進(jìn)位只可能為0或1战坤,減法:當(dāng)當(dāng)前位計(jì)算被減數(shù)不夠減時(shí),需要借位残拐,減法借位只可能為0或-1

    // 為了方便一正一負(fù)時(shí)的減法計(jì)算途茫,將x和y存為默認(rèn)的減數(shù)與被減數(shù)
    let subtracted = x // 被減數(shù),默認(rèn)為x
    let minus = y // 減數(shù)蹦骑,默認(rèn)為y

    if (x.includes('-') && y.includes('-')) { // 全是負(fù)數(shù)時(shí)慈省,計(jì)算方法同全正數(shù)計(jì)算,只需要在最后的結(jié)果將負(fù)號(hào)加上即可,所以在此處將負(fù)號(hào)刪去
        allAegative = true
        calMethood = true
        subtracted = x.split('-')[1]
        minus = y.split('-')[1]
    } else if (x.includes('-') || y.includes('-')) { // x為負(fù)數(shù)或y為負(fù)數(shù)時(shí),執(zhí)行減法運(yùn)算,絕對(duì)值小的為減數(shù)
        // 減法運(yùn)算總是大的減小的
        calMethood = false
        let tempX = x.split('-')[0] ? x.split('-')[0] : x.split('-')[1]
        let tempY = y.split('-')[0] ? y.split('-')[0] : y.split('-')[1]
        if (+tempX > +tempY) {
            subtracted = tempX
            minus = tempY
            allAegative = x.includes('-')
        } else { // 默認(rèn)為x - y边败,如果改為y - x需要給結(jié)果添加負(fù)號(hào)
            subtracted = tempY
            minus = tempX
            allAegative = y.includes('-')
        }
    }

    // todo:計(jì)算過(guò)程

    return Number(x) + Number(y)
}

核心計(jì)算過(guò)程

處理完了符號(hào)袱衷,以及可能出現(xiàn)的報(bào)錯(cuò),下面就開(kāi)始計(jì)算部分了笑窜。這里采用的是先將字符串用split轉(zhuǎn)換為數(shù)組致燥,然后反轉(zhuǎn)數(shù)組,使得數(shù)組從第零位到最后一位分別對(duì)應(yīng)數(shù)字的個(gè)位到最大位排截,最后一位一位計(jì)算得到結(jié)果嫌蚤。可以寫(xiě)一個(gè)方法用來(lái)計(jì)算断傲。整個(gè)實(shí)現(xiàn)過(guò)程也非常簡(jiǎn)單

/**
 * 數(shù)組求和
 * @param {Array} arr1 被減數(shù)轉(zhuǎn)換的數(shù)組
 * @param {Array} arr2 減數(shù)轉(zhuǎn)換的數(shù)組
 * @param {String} sum 和
 * @param {Number} flag 進(jìn)位標(biāo)志
 */
const arrSum = (arr1, arr2, sum, flag) {
    // 以位數(shù)大的數(shù)的長(zhǎng)度為標(biāo)準(zhǔn)遍歷脱吱,其中用到的未定義變量均為上一節(jié)中定義的變量
    for(let i = 0; i < Math.max(arr1.length, arr2.length); i ++) {
        if (calMethood) { // 加法
            // 當(dāng)前位計(jì)算,沒(méi)有則為0认罩,同時(shí)加上進(jìn)位
            const temp = (+arr1[i] || 0) + (+arr2[i] || 0) + flag
            if (temp < 10) { // 判斷是否需要進(jìn)位
                sum = `${temp}${sum}`
                flag = 0
            } else {
                sum = `${temp - 10}${sum}`
                flag = 1
            }
        } else { // 減法
            let temp = (arr1[i] || 0) - (arr2[i] || 0) + flag
            if ((+arr1[i] || 0) < (+arr2[i] || 0)) { // 被減數(shù)太小,需要借位
                temp += 10
                flag = -1
            } else {
                flag = 0
            }
            sum = `${temp}${sum}`
        }
    }
    // 返回flag是為了判斷是否有溢出的進(jìn)位
    return {
        sum,
        flag,
    }
}

然后我們?cè)谔砑右幌伦址D(zhuǎn)換數(shù)組的過(guò)程箱蝠。需要注意的是,我們需要特殊考慮一下小數(shù)垦垂,因?yàn)樾?shù)的字符串在分割時(shí)會(huì)將小數(shù)點(diǎn)也作為一位分割宦搬,所以我們先按小數(shù)點(diǎn)分割,將字符串分割為整數(shù)和小數(shù)兩部分劫拗。

let integerA = subtracted.split('.')[0].split('').reverse() // 被減數(shù)的整數(shù)部分的反轉(zhuǎn)數(shù)組间校,方便遍歷時(shí)從個(gè)位開(kāi)始計(jì)算
let decimalA = [] // 被減數(shù)的小數(shù)部分的反轉(zhuǎn)數(shù)組
let integerB = minus.split('.')[0].split('').reverse() // 減數(shù)的整數(shù)部分的反轉(zhuǎn)數(shù)組
let decimalB = [] // 減數(shù)的小數(shù)部分的反轉(zhuǎn)數(shù)組

if (x.includes('.')) { // 是小數(shù)再去計(jì)算小數(shù)部分的數(shù)組
    decimalA = subtracted.split('.')[1].split('')
}
if (y.includes('.')) {
    decimalB = minus.split('.')[1].split('')
}

// 根據(jù)小數(shù)的特殊性,需要根據(jù)兩個(gè)數(shù)字的最長(zhǎng)長(zhǎng)度去給另一個(gè)填充0
for(let i = 0; i < Math.max(decimalA.length, decimalB.length); i ++) {
    decimalA[i] = +decimalA[i] || 0
    decimalB[i] = +decimalB[i] || 0
}
decimalA = decimalA.reverse()
decimalB = decimalB.reverse()

然后進(jìn)行計(jì)算页慷,先算小數(shù)后算整數(shù)

decimalA = decimalA.reverse()
decimalB = decimalB.reverse()
const decimalAns = arrSum(decimalA, decimalB, sum, flag)
sum = decimalAns.sum.replace(/0*$/, '') // 去除小數(shù)部分末尾的0
flag = decimalAns.flag

// 小數(shù)部分計(jì)算不為空,則添加小數(shù)點(diǎn)
if (sum !== '') sum = `.${sum}`

const integerAns = arrSum(integerA, integerB, sum, flag)
sum = integerAns.sum
flag = integerAns.flag
// 進(jìn)位溢出憔足,前面再添加一位
if (flag !== 0) {
    sum = `${flag}${sum}`
}
sum = sum.replace(/^0*/, '') // 去除最左側(cè)的0

然后最后只需要將最后的sum和符號(hào)拼起來(lái)就是最終的結(jié)果。

return allAegative ? `-${sum}` : sum

大數(shù)減法

減法與加法類(lèi)似酒繁,且在上面的過(guò)程中四瘫,已經(jīng)有了一個(gè)雛形。
比如說(shuō) x - y可以看成是 x + (-y)欲逃,所以就有了一個(gè)思路是,增加一個(gè)參數(shù)用來(lái)判斷是否是減法饼暑,如果是減法就給y值取反稳析,然后仍然進(jìn)行加法運(yùn)算。

/**
 * 
 * @param {Number} x 
 * @param {Number} y 
 * @param {String} methood 
 */
const add = (x, y, methood = '+') => {
    y = methood === '-' ? -y : y
    return x + y
}

add(2, 3) // 5
add(2, 3, '-') // -1
add(2, -3, '-') // 5

參照這個(gè)思路弓叛,我們可以在已經(jīng)寫(xiě)好的加法上稍作改造彰居,加以下幾行代碼。

/**
 * 
 * @param {String} x 
 * @param {String} y 
 * @param {String} methood 
 */
const add = (x = '', y = '', methood = '+') => {
    if (methood === '-') {
        b = b.includes('-') ? b.split('-')[1] : `-$撰筷`
    }
    // ---
}

總結(jié)

市面上已經(jīng)有非常成熟的解決方案了陈惰,我這就是屬于重復(fù)造輪子了,純當(dāng)學(xué)習(xí).

參考

雙精度浮點(diǎn)數(shù)
ECMAScript (ECMA-262)

源碼

/**
 * 計(jì)算大數(shù)
 * @param {String} a 
 * @param {String} b 
 * @param {String} mthood 運(yùn)算方式 
 */
const addLargeNumber = (a = '', b = '', methood = '+') => {
    // 傳小數(shù)進(jìn)行計(jì)算在toString的時(shí)候就會(huì)丟失精度毕籽,太大的時(shí)候一拿到就已經(jīng)沒(méi)有精度了抬闯。井辆。
    if (Number.isNaN(Number(a)) || Number.isNaN(Number(b))) return a + b
    if (methood === '-') {
        b = b.includes('-') ? b.split('-')[1] : `-$`
    }
    let calMethood = true // 運(yùn)算方式,true為加法運(yùn)算,false為減法運(yùn)算
    let allAegative = false // 是否需要加負(fù)號(hào)
    let subtracted = a // 被減數(shù),默認(rèn)為a
    let minus = b // 減數(shù),默認(rèn)為b

    if (a.includes('-') && b.includes('-')) { // 全是負(fù)數(shù)時(shí)溶握,計(jì)算方法同全正數(shù)計(jì)算杯缺,只需要在最后的結(jié)果將負(fù)號(hào)加上即可,所以在此處將負(fù)號(hào)刪去
        allAegative = true
        calMethood = true
        subtracted = a.split('-')[1]
        minus = b.split('-')[1]
    } else if (a.includes('-') || b.includes('-')) { // a為負(fù)數(shù)或b為負(fù)數(shù)時(shí),執(zhí)行減法運(yùn)算,絕對(duì)值小的為減數(shù)
        // 減法運(yùn)算總是大的減小的
        calMethood = false
        let tempX = a.split('-')[0] ? a.split('-')[0] : a.split('-')[1]
        let tempY = b.split('-')[0] ? b.split('-')[0] : b.split('-')[1]
        console.log(+tempX, +tempY, +tempX > +tempY)
        if (+tempX > +tempY) {
            subtracted = tempX
            minus = tempY
            allAegative = a.includes('-')
        } else { // 默認(rèn)為x - y睡榆,如果改為y - x需要給結(jié)果添加負(fù)號(hào)
            subtracted = tempY
            minus = tempX
            allAegative = b.includes('-')
        }
        
    }
    let integerA = subtracted.split('.')[0].split('').reverse() // 被減數(shù)的整數(shù)部分的反轉(zhuǎn)數(shù)組萍肆,方便遍歷時(shí)從個(gè)位開(kāi)始計(jì)算
    let decimalA = [] // 被減數(shù)的小數(shù)部分的反轉(zhuǎn)數(shù)組
    let integerB = minus.split('.')[0].split('').reverse() // 減數(shù)的整數(shù)部分的反轉(zhuǎn)數(shù)組
    let decimalB = [] // 減數(shù)的小數(shù)部分的反轉(zhuǎn)數(shù)組


    let flag = 0 // 進(jìn)位標(biāo)志,當(dāng)當(dāng)前位計(jì)算大于9時(shí)胀屿,需要進(jìn)位塘揣,加法進(jìn)位只可能為0或1
    let sum = '' // 和


    if (a.includes('.')) { // 是小數(shù)再去計(jì)算小數(shù)部分的數(shù)組
        decimalA = subtracted.split('.')[1].split('')
    }
    if (b.includes('.')) {
        decimalB = minus.split('.')[1].split('')
    }


    // 根據(jù)小數(shù)的特殊性,需要根據(jù)兩個(gè)數(shù)字的最長(zhǎng)長(zhǎng)度去給另一個(gè)填充0
    for(let i = 0; i < Math.max(decimalA.length, decimalB.length); i ++) {
        decimalA[i] = +decimalA[i] || 0
        decimalB[i] = +decimalB[i] || 0
    }
    decimalA = decimalA.reverse()
    decimalB = decimalB.reverse()
    const decimalAns = arrSum(decimalA, decimalB, sum, flag)
    sum = decimalAns.sum.replace(/0*$/, '') // 去除小數(shù)部分末尾的0
    flag = decimalAns.flag
    // 小數(shù)部分計(jì)算不為空,則添加小數(shù)點(diǎn)
    if (sum !== '') sum = `.${sum}`
    const integerAns = arrSum(integerA, integerB, sum, flag)
    sum = integerAns.sum
    flag = integerAns.flag
    if (flag !== 0) {
        sum = `${flag}${sum}`
    }
    sum = sum.replace(/^0*/, '') || '0' // 去除最左側(cè)的0宿崭,同時(shí)避免因結(jié)果是0而產(chǎn)生空串
    /**
     * 
     * @param {Array} arr1 被減數(shù)轉(zhuǎn)換的數(shù)組
     * @param {Array} arr2 減數(shù)轉(zhuǎn)換的數(shù)組
     * @param {String} sum 和
     * @param {Number} flag 進(jìn)位標(biāo)志
     */
    function arrSum(arr1, arr2, sum, flag) {
        for(let i = 0; i < Math.max(arr1.length, arr2.length); i ++) {
            if (calMethood) { // 加法
                const temp = (+arr1[i] || 0) + (+arr2[i] || 0) + flag
                if (temp < 10) {
                    sum = `${temp}${sum}`
                    flag = 0
                } else {
                    sum = `${temp - 10}${sum}`
                    flag = 1
                }
            } else { // 減法
                let temp = (+arr1[i] || 0) - (+arr2[i] || 0) + flag
                if ((arr1[i] || 0) < (arr2[i] || 0)) { // 被減數(shù)太小,需要借位
                    temp += 10
                    flag = -1
                } else {
                    flag = 0
                }
                sum = `${temp}${sum}`
            }
        }
        return {
            sum,
            flag,
        }
    }
    return allAegative ? `-${sum}` : sum
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末亲铡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子劳曹,更是在濱河造成了極大的恐慌奴愉,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铁孵,死亡現(xiàn)場(chǎng)離奇詭異锭硼,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蜕劝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)檀头,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人岖沛,你說(shuō)我怎么就攤上這事暑始。” “怎么了婴削?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵廊镜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我唉俗,道長(zhǎng)燎潮,這世上最難降的妖魔是什么源祈? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任匿乃,我火速辦了婚禮牲迫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘衡楞。我一直安慰自己吱雏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著歧杏,像睡著了一般镰惦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上得滤,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天陨献,我揣著相機(jī)與錄音,去河邊找鬼懂更。 笑死眨业,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沮协。 我是一名探鬼主播龄捡,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼慷暂!你這毒婦竟也來(lái)了聘殖?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤行瑞,失蹤者是張志新(化名)和其女友劉穎奸腺,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體血久,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡突照,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了氧吐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讹蘑。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖筑舅,靈堂內(nèi)的尸體忽然破棺而出座慰,到底是詐尸還是另有隱情,我是刑警寧澤翠拣,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布版仔,位于F島的核電站,受9級(jí)特大地震影響误墓,放射性物質(zhì)發(fā)生泄漏邦尊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一优烧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧链峭,春花似錦畦娄、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)杖刷。三九已至,卻和暖如春驳癌,著一層夾襖步出監(jiān)牢的瞬間滑燃,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工颓鲜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留表窘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓甜滨,卻偏偏與公主長(zhǎng)得像乐严,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子衣摩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353