Last active
May 20, 2018 08:03
-
-
Save Jancat/eaee5b0b95b96a1a5c0dbfc5b981bad2 to your computer and use it in GitHub Desktop.
JavaScript 大数运算
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 大整数乘法 | |
* 功能:大整数乘法精确运算,同时解决浮点数误差问题 | |
* 问题:超过 Number.MAX_SAGE_INTEGER 的数不能准确表示,同时运算结果也不准确 | |
* 原理:通过字符串表示大数,并且从字符串末尾开始把每个字符转成数值进行运算,然后再处理进位和借位,最后结果也是用字符串表示 | |
* 说明: | |
* 支持正负整数; | |
* 不支持科学计数法表示的字符串,如 1.2e+10; | |
* 进行运算的字符串假设数值格式尽量标准 | |
*/ | |
// 大整数相乘 | |
function largeNumMulti(num1, num2) { | |
// 从末尾开始计算乘积 | |
const num1Arr = num1.split('').reverse() | |
const num2Arr = num2.split('').reverse() | |
const result = [] | |
// 填充0,为后面的累加作准备 | |
result.length = num1Arr.length + num2Arr.length - 1 | |
result.fill(0) | |
// result[i + j] 上不断累加对应位的乘积 | |
num1Arr.forEach((a, i) => { | |
num2Arr.forEach((b, j) => { | |
result[i + j] += parseInt(a) * parseInt(b) | |
}) | |
}) | |
// 处理 result 进位 | |
const lastCarry = result.reduce((carry, v, k) => { | |
result[k] += carry | |
if (result[k] >= 10) { | |
const _carry = Math.floor(result[k] / 10) | |
result[k] %= 10 | |
return _carry | |
} | |
return 0 | |
}, 0) | |
lastCarry && result.push(lastCarry) | |
return result.reverse().join('') | |
} | |
// 处理异常数值和符号 | |
function largeNumCalculation(num1, num2, ...others) { | |
if (others.length > 0) { | |
return largeNumCalculation(largeNumCalculation(num1, num2), others[0], ...others.slice(1)) | |
} | |
if ( | |
Number.isNaN(parseFloat(num1)) || | |
Number.isNaN(parseFloat(num2)) || | |
!/^[0-9-]*$/.test(num1) || | |
!/^[0-9-]*$/.test(num2) | |
) { | |
throw new Error('Not a valid number') | |
} | |
const num1Trimmed = num1.trim().replace(/^0+/g, '') | |
const num2Trimmed = num2.trim().replace(/^0+/g, '') | |
const isNum1Positive = !num1[0].startsWith('-') | |
const isNum2Positive = !num2[0].startsWith('-') | |
// 处理符号 | |
if (num1Trimmed && num2Trimmed) { | |
if (isNum1Positive && isNum2Positive) { | |
return largeNumMulti(num1Trimmed, num2Trimmed) | |
} else if (!isNum1Positive && !isNum2Positive) { | |
return largeNumMulti(num1Trimmed.slice(1), num2Trimmed.slice(1)) | |
} else { | |
return ( | |
'-' + | |
largeNumMulti( | |
isNum1Positive ? num1Trimmed : num1Trimmed.slice(1), | |
isNum2Positive ? num2Trimmed : num2Trimmed.slice(1), | |
) | |
) | |
} | |
} | |
return num1Trimmed + num2Trimmed | |
} | |
const a = '-3455' | |
const b = '-2200' | |
console.log(+a * +b) | |
console.log(largeNumCalculation(a, b)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 大数相加、相减 | |
* 功能:大数加减精确运算,同时解决浮点数误差问题 | |
* 问题:超过 Number.MAX_SAGE_INTEGER 的数不能准确表示,同时运算结果也不准确 | |
* 原理:通过字符串表示大数,并且从字符串末尾开始把每个字符转成数值进行运算,然后再处理进位和借位,最后结果也是用字符串表示 | |
* 说明: | |
* 支持正负数、浮点数、整数; | |
* 不支持科学计数法表示的字符串,如 1.2e+10; | |
* 进行运算的字符串假设数值格式尽量标准 | |
*/ | |
// 删除字符串中多余的空白字符和 0 | |
function trimZero(str) { | |
const newStr = str.trim() | |
return newStr.replace( | |
/(?:(-)(?![0.]+(?![\d.]))|-)?\d*?([1-9]\d*|0)(?:(?:(\.\d*[1-9])|\.)\d*)?(?![\d.])/gm, | |
'$1$2$3', | |
) | |
} | |
// 分割整数部分和小数部分 | |
function divide(num) { | |
const numPointIndex = num.indexOf('.') | |
const numInteger = num.slice(0, numPointIndex > -1 ? numPointIndex : undefined) | |
const numFraction = numPointIndex > -1 ? num.slice(numPointIndex + 1) : '' | |
return [numInteger, numFraction] | |
} | |
// 两数对齐,用 '0' 填充,并且移除小数点 | |
// 返回对齐后的数和小数点位置 | |
function alignNumber(num1, num2) { | |
function align(_num1, _num2, padFn) { | |
const maxLength = Math.max(_num1.length, _num2.length) | |
return [padFn.call(_num1, maxLength, '0'), padFn.call(_num2, maxLength, '0')] | |
} | |
// 分割整数和小数 | |
let [num1Integer, num1Fraction] = divide(num1) | |
let [num2Integer, num2Fraction] = divide(num2) | |
// 整数部分和小数部分对齐 | |
;[num1Integer, num2Integer] = align(num1Integer, num2Integer, String.prototype.padStart) | |
;[num1Fraction, num2Fraction] = align(num1Fraction, num2Fraction, String.prototype.padEnd) | |
return [num1Integer + num1Fraction, num2Integer + num2Fraction, num1Integer.length] | |
} | |
// 大数相加 | |
// numTrimmed 一定是正数 | |
function largeNumAddition(num1Trimmed, num2Trimmed) { | |
const [alignedNum1, alignedNum2, pointIndex] = alignNumber(num1Trimmed, num2Trimmed) | |
const result = [] | |
let borrow = 0 | |
// 倒序一个个字符相加,把相加结果保存在 result[] 中 | |
for (let i = alignedNum1.length - 1; i >= 0; i--) { | |
const a = parseInt(alignedNum1[i]) | |
const b = parseInt(alignedNum2[i]) | |
if (a + b + borrow >= 10) { | |
result.unshift(a + b + borrow - 10) | |
borrow = 1 | |
} else { | |
result.unshift(a + b + borrow) | |
borrow = 0 | |
} | |
} | |
// 如果是小数则把小数点插入回原来的位置 | |
if (pointIndex < alignedNum1.length) { | |
result.splice(pointIndex, 0, '.') | |
} | |
if (borrow === 1) { | |
result.unshift(1) | |
} | |
return trimZero(result.join('')) | |
} | |
// 大数相减 | |
// numTrimmed 一定是正数 | |
function largeNumSubtraction(num1Trimmed, num2Trimmed) { | |
// 如果 num1Trimmed 的整数部分长度小于 num2Trimmed 的整数部分长度,说明 num1Trimmed 小于 num2Trimmed | |
// 减数交换并对结果转负 | |
if (divide(num1Trimmed)[0].length < divide(num2Trimmed)[0].length) { | |
return '-' + largeNumSubtraction(num2Trimmed, num1Trimmed) | |
} | |
const [alignedNum1, alignedNum2, pointIndex] = alignNumber(num1Trimmed, num2Trimmed) | |
const result = [] | |
let borrow = 0 | |
// 倒序一个个字符相加,把相加结果保存在 result[] 中 | |
for (let i = alignedNum1.length - 1; i >= 0; i--) { | |
const a = parseInt(alignedNum1[i]) | |
const b = parseInt(alignedNum2[i]) | |
if (a - b - borrow < 0) { | |
result.unshift(a - b - borrow + 10) | |
borrow = 1 | |
} else { | |
result.unshift(a - b - borrow) | |
borrow = 0 | |
} | |
} | |
if (pointIndex < alignedNum1.length) { | |
result.splice(pointIndex, 0, '.') | |
} | |
// 和函数最开始的判断逻辑一致,这里根据最后的借位判断是否要交换减数 | |
if (borrow) { | |
return '-' + largeNumSubtraction(num2Trimmed, num1Trimmed) | |
} | |
return trimZero(result.join('')) | |
} | |
function largeNumCalculation(num1, num2, ...others) { | |
if (others.length > 0) { | |
return largeNumCalculation(largeNumCalculation(num1, num2), others[0], ...others.slice(1)) | |
} | |
if ( | |
Number.isNaN(parseFloat(num1)) || | |
Number.isNaN(parseFloat(num2)) || | |
!/^[0-9.-]*$/.test(num1) || | |
!/^[0-9.-]*$/.test(num2) | |
) { | |
throw new Error('Not a valid number') | |
} | |
const num1Trimmed = trimZero(num1) | |
const num2Trimmed = trimZero(num2) | |
// 根据正负数判断选择相加还是相减 | |
const isNum1Positive = !num1[0].startsWith('-') | |
const isNum2Positive = !num2[0].startsWith('-') | |
// 处理符号 | |
if (num1Trimmed && num2Trimmed) { | |
if (isNum1Positive && isNum2Positive) { | |
return largeNumAddition(num1Trimmed, num2Trimmed) | |
} else if (isNum1Positive && !isNum2Positive) { | |
return largeNumSubtraction(num1Trimmed, num2Trimmed.slice(1)) | |
} else if (isNum2Positive && !isNum1Positive) { | |
return largeNumSubtraction(num2Trimmed, num1Trimmed.slice(1)) | |
} else { | |
return '-' + largeNumAddition(num1Trimmed.slice(1), num2Trimmed.slice(1)) | |
} | |
} | |
return num1Trimmed + num2Trimmed | |
} | |
const a = '-8888888888888.888888888888888' | |
const b = '000099999999.0000944444444444444444444440' | |
console.log(parseFloat(a) + parseFloat(b)) | |
console.log(largeNumCalculation(a, b)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment