您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關JavaScript中任意兩數加減的示例分析的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
本文是從初步解決到最終解決的思路,文章篇幅較長
雖然是一篇從0開始的文章,中間的思維跳躍可能比較大
代碼的解析都在文章的思路分析和注釋里,全文會幫助理解的幾個關鍵詞
1.Number.MAX_SAFE_INTEGER 和 Number.MIN_SAFE_INTEGER
2.15長度的字符串
3.padStart 和 padEnd
相信很多人都知道這是怎么回事吧
console.log( 0.1 + 0.2 === 0.3 ) // false console.log( 0.3 - 0.2 === 0.1) // false
不了解的出門右拐自己去查詢下,這里就不一一解釋了!
通過上面的例子可以知道,小數點的加減是存在問題的,那么有什么解決方式呢?
既然小數點加減有問題,那就先來整數進行加減吧,這個應該就沒什么問題了小數點的加減自行百度解決,可以通過浮點計算,這里就不介紹了,那么整數的加減就一定沒有任何問題嗎?來看看下面的例子
const MAX = Number.MAX_SAFE_INTEGER; console.log( MAX ) // 9007199254740991 console.log( MAX + 2 ) // 9007199254740992
Number.MAX_SAFE_INTEGER
是什么?
常量表示在 JavaScript 中最大的安全整數
所以,Number.MIN_SAFE_INTEGER就是最小安全系數
顧名思義,就是在JavaScript中加減法在這兩個范圍內是穩定的,是不是這樣就安全了?好像還是有點小問題:
console.log( 10**21 ) // 1e+21 console.log(9999999999999999) // 9999999999999999 console.log(99999999999999999) // 10000000000000000 console.log(999999999999999999999) // 1e+21
從上面的結果來看,是不安全的
1.最后的結果是科學計數法
2.不知道具體的真實數據是多少
既然數字的顯示存在這樣的問題,把輸入結果和輸出結果都用字符串表示
console.log(`${10 ** 21}`) // '1e+21' console.log('' + 10 ** 21) // '1e+21' console.log((10 ** 21).toString()) // '1e+21'
我們發現即使直接就轉換成字符串仍然會顯示為科學計數法,那么可以直接輸入字符串了,跳過轉成字符串的過程
先分析下可能性
1.輸入的數字在安全系數范圍內,且計算結果也在安全系數范圍內,這種直接輸出結果
2.不符合條件1(TODO)
const MAX = Number.MAX_SAFE_INTEGER; const MIN = Number.MIN_SAFE_INTEGER; /** * @Description: 判斷輸入的數字是否在javascript的安全系數范圍內 * @param { number } 需要檢查的數字 * @return { boolean }: 返回數字是否為安全的整數 */ function isSafeNumber(num) { // 即使 num 成了科學計數法也能正確的和 MAX, MIN 比較大小 return MIN <= num && num <= MAX; } /** * @Description: 計算兩個數之和,返回計算結果 * @param { String }: a 相加的第一個整數字符串 * @param { String }: b 相加的第一個整數字符串 * @return { string }: 返回計算結果 */ function IntAdd(a = "", b = "") { let result = "0"; const intA = Number(a), intB = Number(b); if (intA === 0) return intB; if (intB === 0) return intA; if ( isSafeNumber(intA) && isSafeNumber(intB) && isSafeNumber(intA + intB) ) { result = intA + intB; } else { result = IntCalc(a, b); } return result; } function IntCalc(a, b) { // TODO } function resClick() { const a = document.getElementById("ipt1").value; const b = document.getElementById("ipt2").value; const result = IntAdd(a, b); document.getElementById("res").innerText = result; }
如果不滿足上面條件的呢?
思路:
獲取數字轉成字符串拆分成多個部分(數組),每一個部分的長度為 Number.MAX_SAFE_INTEGER 轉成字符串后的長度減一(15),長度不足15的用字符‘0’填充首部,再計算每個部分的結果后拼接在一起
同時考慮到正負號的問題,拆分后的計算需要帶上符號
長度減一的原因是接下來每部分的所有計算都是安全的,不需要在考慮是數字計算結果為安全的整數
同時每部分計算后的結果存在問題以及解決方案
注意:下面會使用15這個數字,15上面說過了,是Number.MAX_SAFE_INTEGER的長度減一
1.計算結果為0
那么這個部分賦值15個字符‘0’組成的字符串,即‘000000000000000’
2.計算結果為負數
那么向上一級數組借10的15次方,同時高位(下一級數組)減一,低位用10的15次方再加上這個負數,做為這個部分的結果
3.計算結果為正數,判斷長度:
如果長度超過15,那么去掉結果的第一位字符(因為進位,第一個字符一定是‘1’),同時高位(下一級數組)加一
如果長度沒有超過15,向首部補充0直到長度足夠15
如果長度等于15,直接添加到結果中
改造上面的代碼:
const MAX = Number.MAX_SAFE_INTEGER; const MIN = Number.MIN_SAFE_INTEGER; const intLen = `${MAX}`.length - 1; /** * @Description: 判斷輸入的數字是否在javascript的安全系數范圍內 * @param { number } 需要檢查的數字 * @return { boolean }: 返回數字是否為安全的整數 */ function isSafeNumber(num) { // 即使 num 成了科學計數法也能正確的和 MAX, MIN 比較大小 return MIN <= num && num <= MAX; } /** * @Description: 計算兩個數之和,返回計算結果 * @param { String }: a 相加的第一個整數字符串 * @param { String }: b 相加的第一個整數字符串 * @return { string }: 返回計算結果 */ function IntAdd(a = "", b = "") { const statusObj = checkNumber(a, b); if (!statusObj.status) { return statusObj.data; } else { const tagA = Number(a) < 0, tagB = Number(b) < 0; const strA = `${a}`, strB = `${b}`; const lenA = tagA ? strA.length - 1 : strA.length; const lenB = tagB ? strB.length - 1 : strB.length; const maxLen = Math.max(lenA, lenB); const padLen = Math.ceil(maxLen / intLen) * intLen; // 即為會用到的整個數組長度 const newA = tagA ? `-${strA.slice(1).padStart(padLen, "0")}` : strA.padStart(padLen, "0"); const newB = tagB ? `-${strB.slice(1).padStart(padLen, "0")}` : strB.padStart(padLen, "0"); let result = IntCalc(newA, newB); // 去掉正負數前面無意義的字符 ‘0' const numberResult = Number(result); if (numberResult > 0) { while (result[0] === "0") { result = result.slice(1); } } else if (numberResult < 0) { while (result[1] === "0") { result = "-" + result.slice(2); } } else { result = "0"; } return result; } } function IntCalc(a, b) { let result = "0"; const intA = Number(a), intB = Number(b); // 判斷是否為安全數,不為安全數的操作進入復雜計算模式 if ( isSafeNumber(intA) && isSafeNumber(intB) && isSafeNumber(intA + intB) ) { result = `${intA + intB}`; } else { const sliceA = a.slice(1), sliceB = b.slice(1); if (a[0] === "-" && b[0] === "-") { // 兩個數都為負數,取反后計算,結果再取反 result = "-" + calc(sliceA, sliceB, true); } else if (a[0] === "-") { // 第一個數為負數,第二個數為正數的情況 const newV = compareNumber(sliceA, b); if (newV === 1) { // 由于 a 的絕對值比 b 大,為了確保返回結果為正數,a的絕對值作為第一個參數 result = "-" + calc(sliceA, b, false); } else if (newV === -1) { // 道理同上 result = calc(b, sliceA, false); } } else if (b[0] === "-") { // 第一個數為正數,第二個數為負數的情況 const newV = compareNumber(sliceB, a); if (newV === 1) { // 由于 b 的絕對值比 a 大,為了確保返回結果為正數,b的絕對值作為第一個參數 result = "-" + calc(sliceB, a, false); } else if (newV === -1) { // 道理同上 result = calc(a, sliceB, false); } } else { // 兩個數都為正數,直接計算 result = calc(a, b, true); } } return result; } /** * @Description: 比較兩個整數字符串是否正確 * @param { string }: 比較的第一個整數字符串 * @param { string }: 比較的第一個整數字符串 * @return { object }: 返回是否要退出函數的狀態和退出函數返回的數據 */ function checkNumber(a, b) { const obj = { status: true, data: null }; const typeA = typeof a, typeB = typeof b; const allowTypes = ["number", "string"]; if (!allowTypes.includes(typeA) || !allowTypes.includes(typeB)) { console.error("參數中存在非法的數據,數據類型只支持 number 和 string"); obj.status = false; obj.data = false; } if (Number.isNaN(a) || Number.isNaN(b)) { console.error("參數中不應該存在 NaN"); obj.status = false; obj.data = false; } const intA = Number(a), intB = Number(b); if (intA === 0) { obj.status = false; obj.data = b; } if (intB === 0) { obj.status = false; obj.data = a; } const inf = [Infinity, -Infinity]; if (inf.includes(intA) || inf.includes(intB)) { console.error("參數中存在Infinity或-Infinity"); obj.status = false; obj.data = false; } return obj; } /** * @Description: 比較兩個整數字符串正負 * @param { string } a 比較的第一個整數字符串 * @param { string } b 比較的第二個整數字符串 * @return { boolean } 返回第一個參數與第二個參數的比較 */ function compareNumber(a, b) { if (a === b) return 0; if (a.length > b.length) { return 1; } else if (a.length < b.length) { return -1; } else { for (let i = 0; i < a.length; i++) { if (a[i] > b[i]) { return 1; } else if (a[i] < b[i]) { return -1; } } } } /** * @Description: 相加的結果 * @param { string } a 相加的第一個整數字符串 * @param { string } b 相加的第二個整數字符串 * @param { string } type 兩個參數是 相加(true) 還是相減(false) * @return { string } 返回相加的結果 */ function calc(a, b, type = true) { const arr = []; // 保存每個部分計算結果的數組 for (let i = 0; i < a.length; i += intLen) { // 每部分長度 15 的裁取字符串 const strA = a.slice(i, i + intLen); const strB = b.slice(i, i + intLen); const newV = Number(strA) + Number(strB) * (type ? 1 : -1); // 每部分的計算結果,暫時不處理 arr.push(`${newV}`); } let num = ""; // 連接每個部分的字符串 for (let i = arr.length - 1; i >= 0; i--) { if (arr[i] > 0) { // 每部分結果大于 0 的處理方案 const str = `${arr[i]}`; if (str.length < intLen) { // 長度不足 15 的首部補充字符‘0' num = str.padStart(intLen, "0") + num; } else if (str.length > intLen) { // 長度超過 15 的扔掉第一位,下一部分進位加一 num = str.slice(1) + num; if (i >= 1 && str[0] !== "0") arr[i - 1]++; else num = "1" + num; } else { // 長度等于 15 的直接計算 num = str + num; } } else if (arr[i] < 0) { // 每部分結果小于 0 的處理方案,借位 10的15次方計算,結果恒為正數,首部填充字符‘0'到15位 const newV = `${10 ** intLen + Number(arr[i])}`; num = newV.padStart(intLen, "0") + num; if (i >= 1) arr[i - 1]--; } else { // 每部分結果等于 0 的處理方案,連續15個字符‘0' num = "0".padStart(intLen, "0") + num; } } return num; }
測試結果:
全部代碼請點擊 這里
console.log(MAX) // 9007199254740991 intAdd(MAX, '2') // '9007199254740993' intAdd(MAX, '10000000000000000') // '19007199254740991' // 下面測試10的二十一次方的數據 1000000000000000000000 intAdd(MAX, '1000000000000000000000') // '1000009007199254740991' intAdd(MAX, `-${10 ** 16}`) // '-992800745259009' // 仍然存在一個問題,就是不要使用計算中的字符串,如下 intAdd(MAX, `${10 ** 21}`) // '10.0000000071992548e+21' intAdd(MAX, `-${10 ** 21}`) // '0'
當然考慮到由于一般計算不會使用大數,書寫字符串相加確實感覺怪怪的,可以在函數內加入判斷,是科學計數法的提示并轉換為10進制數,進行代碼改進:
/** * @Description: 計算兩個數之和,返回計算結果 * @param { String }: a 相加的第一個整數字符串 * @param { String }: b 相加的第一個整數字符串 * @return { string }: 返回計算結果 */ function intAdd(a = "", b = "") { const statusObj = checkNumber(a, b); if (!statusObj.status) { return statusObj.data; } else { let newA, newB, maxLen; const tagA = Number(a) < 0, tagB = Number(b) < 0; let strA = `${a}`, strB = `${b}`; const reg = /^\-?(\d+)(\.\d+)?e\+(\d+)$/; if (reg.test(a) || reg.test(b)) { console.warn( "由于存在科學計數法,計算結果不一定準確,請轉化成字符串后計算" ); strA = strA.replace(reg, function(...rest) { const str = rest[2] ? rest[1] + rest[2].slice(1) : rest[1]; return str.padEnd(Number(rest[3]) + 1, "0"); }); strB = strB.replace(reg, function(...rest) { const str = rest[2] ? rest[1] + rest[2].slice(1) : rest[1]; return str.padEnd(Number(rest[3]) + 1, "0"); }); maxLen = Math.max(a.length, b.length); } else { const lenA = tagA ? strA.length - 1 : strA.length; const lenB = tagB ? strB.length - 1 : strB.length; maxLen = Math.max(lenA, lenB); } const padLen = Math.ceil(maxLen / intLen) * intLen; // 即為會用到的整個數組長度 newA = tagA ? `-${strA.slice(1).padStart(padLen, "0")}` : strA.padStart(padLen, "0"); newB = tagB ? `-${strB.slice(1).padStart(padLen, "0")}` : strB.padStart(padLen, "0"); let result = intCalc(newA, newB); // 去掉正負數前面無意義的字符 ‘0' const numberResult = Number(result); if (numberResult > 0) { while (result[0] === "0") { result = result.slice(1); } } else if (numberResult < 0) { while (result[1] === "0") { result = "-" + result.slice(2); } } else { result = "0"; } console.log(result); return result; } }
加法和減法同理,只需要把第二個參數取反后利用加法運算就可以了,由于之前已經提取了模板,可以直接定義減法函數
/** * @Description: 整數減法函數入口 * @param { String }: a 減法的第一個整數字符串 * @param { String }: b 減法的第一個整數字符串 * @return { string }: 返回計算結果 */ function intSub(a = "0", b = "0") { const newA = `${a}`; const newB = Number(b) > 0 ? `-${b}` : `${b}`.slice(1); const result = intAdd(newA, newB); return result; }
測試結果
全部代碼請點擊 這里
intSub('9037499254750994', '-9007299251310995') // 18044798506061989
文章開頭說了,小數加減,可以通過浮點進行計算,但是這里既然完成了整數的加減,那么能不能利用整數的加減原理來解決小數的加減計算呢?
整數加法代碼中經常出現 padStart
這個向前補齊的函數,因為在整數前加字符‘0’的對本身沒有影響。
小數也有這個原理,往尾部補‘0’同樣對小數沒有影響,然后再補齊后的數通過整數加減來計算。
首先來看下小數的加法計算實現
/** * @Description: 小數加法函數入口 * @param { String }: a 相加的第一個整數字符串 * @param { String }: b 相加的第一個整數字符串 * @return { string }: 返回計算結果 */ function floatAdd(a = "0", b = "0") { const statusObj = checkNumber(a, b); if (!statusObj.status) { return statusObj.data; } else { const strA = `${a}`.split("."), strB = `${b}`.split("."); let newA = strA[1], newB = strB[1]; const maxLen = Math.max(newA.length, newB.length); const floatLen = Math.ceil(maxLen / intLen) * intLen; newA = newA.padEnd(floatLen, "0"); newB = newB.padEnd(floatLen, "0"); newA = strA[0][0] === "-" ? `-${newA}` : newA; newB = strB[0][0] === "-" ? `-${newB}` : newB; let result = intCalc(newA, newB); let tag = true, numResult = Number(result); // 去掉正負數后面無意義的字符 ‘0' if (numResult !== 0) { if (numResult < 0) { result = result.slice(1); tag = false; } result = result.length === floatLen ? `0.${result}` : `1.${result.slice(1)}`; result = tag ? result : `-${result}`; let index = result.length - 1; while (result[index] === "0") { result = result.slice(0, -1); index--; } } else { result = "0"; } console.log(result); return result; } }
測試結果
floatAdd('0.9037499254750994', '-0.9007299251310995') // 0.0030200003439999
與整數減法的原理相同,可以直接定義減法函數
/** * @Description: 小數減法函數入口 * @param { String }: a 相減的第一個整數字符串 * @param { String }: b 相減的第一個整數字符串 * @return { string }: 返回計算結果 */ function floatSub(a = '0', b = '0') { const newA = `${a}` const newB = Number(b) > 0 ? `-${b}`: `${b.slice(1)}` const result = floatAdd(newA, newB) return result }
測試結果
全部代碼請點擊 這里
floatSub('0.9037499254750994', '-0.9007299251310995') // 1.8044798506061989
其實在實際開發過程中,并不是整數相加減,小數相加減,都有可能出現,所以還要考慮整數與小數之間的加減計算
這里的解決思路仍然是往前補0和往后補0
把整數和小數都補充完整后,合在一起進行整數相加
最后根據之前保存的整數的長度,插入小數點
剩下的就是把無意義的0排除掉,輸出結果
/** * @Description: 計算兩個數之差,返回計算結果 * @param { String }: a 相減的第一個整數字符串 * @param { String }: b 相減的第一個整數字符串 * @return { string }: 返回計算結果 */ function allSub(a = "0", b = "0") { const newA = `${a}`; const newB = Number(b) > 0 ? `-${b}` : `${b}`.slice(1); const result = allAdd(newA, newB); return result; } /** * @Description: 計算兩個數之和,返回計算結果 * @param { String }: a 相加的第一個整數字符串 * @param { String }: b 相加的第一個整數字符串 * @return { string }: 返回計算結果 */ function allAdd(a = "0", b = "0") { const statusObj = checkNumber(a, b); if (!statusObj.status) { return statusObj.data; } else { const strA = `${a}`.split("."), strB = `${b}`.split("."); let intAs = strA[0], floatA = strA.length === 1 ? "0" : strA[1]; let intBs = strB[0], floatB = strB.length === 1 ? "0" : strB[1]; // 可能存在純整數 或者純小數 0.xxxxxxx const tagA = intAs > 0 || !intAs[0] === '-' || intAs[0] === '0', tagB = intBs > 0 || !intBs[0] === '-' || intBs[0] === '0'; const maxIntLen = Math.max(intAs.length, intBs.length); const arrIntLen = Math.ceil(maxIntLen / intLen) * intLen; const maxFloatLen = Math.max(floatA.length, floatB.length); const arrFloatLen = Math.ceil(maxFloatLen / intLen) * intLen; intAs = tagA ? intAs.padStart(arrIntLen, "0") : intAs.slice(1).padStart(arrIntLen, "0"); intBs = tagB ? intBs.padStart(arrIntLen, "0") : intBs.slice(1).padStart(arrIntLen, "0"); let newA = floatA === "0" ? intAs + "0".padEnd(arrFloatLen, "0") : intAs + floatA.padEnd(arrFloatLen, "0"); let newB = floatB === "0" ? intBs + "0".padEnd(arrFloatLen, "0") : intBs + floatB.padEnd(arrFloatLen, "0"); newA = tagA ? newA : `-${newA}`; newB = tagB ? newB : `-${newB}`; let result = intCalc(newA, newB); const numResult = Number(result); if (result.length > arrIntLen) { result = result.slice(0, -arrFloatLen) + "." + result.slice(arrFloatLen); } // 去掉正負數前面后面無意義的字符 ‘0' if (numResult !== 0) { if (numResult > 0) { while (result[0] === "0") { result = result.slice(1); } } else if (numResult < 0) { while (result[1] === "0") { result = "-" + result.slice(2); } result = result.slice(1); tag = false; } let index = result.length - 1; while (result[index] === "0") { result = result.slice(0, -1); index--; } } else { result = "0"; } if (result[result.length - 1] === ".") { result = result.slice(0, -1); } if (result[0] === ".") { result = "0" + result; } console.log(result); return result; } }
測試結果
全部代碼請點擊 這里
allAdd("9037499254750994", "0.9007299251310995"); // 9037499254750994.9007299251310995 allSub("9037499254750994", "-0.9007299251310995"); // 9037499254750994.9007299251310995 allAdd('9037499254750994.9037499254750994', '-9007299251310995.9007299251310995'); // 30200003439999.0030200003439999 allSub('9037499254750994.9037499254750994', '9007299251310995.9007299251310995'); // 30200003439999.0030200003439999
Number.MAX_SAFE_INTEGER 和 Number.MIN_SAFE_INTEGER 之間的計算才是可信任的
小數加減的浮點精度問題轉移到整數來解決
超大的數加減的時候,分區計算(理由是第1點)
拆分成每部分15長度的字符串(理由是Number.MAX_SAFE_INTEGER的長度為16,無論如何加減都是滿足第一點的,這樣就不需要去注意加減的安全性問題了)
科學計數法的問題,匹配是否為科學計數法的數,然后轉換成十進制,同時提出警告,因為科學計數法的數存在誤差,計算會存在不準確性
感謝各位的閱讀!關于“JavaScript中任意兩數加減的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。