大数指number无法存储的数字,一般用字符串来表示。
以下分别讲述大数的加减乘除运算,运算之间可能有所交叉,例如正数减负数就变成两个正数相加,除法里也用到了减法,所以代码就放在了一起。
function checkIsNumber(numStr) {
const isNumber = /^-?\d+$/.test(numStr)
if (!isNumber) {
throw new Error('param must be a number');
}
return isNumber;
}
function isZero(numStr) {
return /^-?0$/.test(numStr);
}
function isNagative(numStr) {
return numStr[0] === '-';
}
function transformToNagative(numStr) {
if (isNagative(numStr)) {
return numStr;
}
return '-' + numStr;
}
function getPositivePart(numStr) {
if (isNagative(numStr)) {
return numStr.substr(1);
}
return numStr;
}
// 大数加法
function add(numStr1, numStr2) {
// 需要判断正负
const isNum1Nagative = isNagative(numStr1);
const isNum2Nagative = isNagative(numStr2);
numStr1 = getPositivePart(numStr1);
numStr2 = getPositivePart(numStr2);
// 一正一负责需要调减法, 否则还是加法
if (isNum1Nagative + isNum2Nagative === 1) {
return sub(isNum1Nagative ? numStr2 : numStr1, isNum1Nagative ? numStr1 : numStr2);
}
// 补0以避免后续长度判断
if (numStr1.length < numStr2.length) {
numStr1 = '0'.repeat(numStr2.length - numStr1.length) + numStr1
} else if (numStr2.length < numStr1.length) {
numStr2 = '0'.repeat(numStr1.length - numStr2.length) + numStr2
}
const length = numStr1.length
let carry = 0
let result = ''
// 逐位相加,记录进位
for (let i = length - 1; i >= 0; i--) {
const temp = +numStr1[i] + +numStr2[i] + carry;
result = (temp % 10) + result
carry = Math.floor(temp / 10);
}
// 最后进位要补上
if (carry) {
result = carry + result;
}
// 负数相加最后补负号
if (isNum1Nagative && isNum2Nagative) {
result = transformToNagative(result);
}
return result;
}
// 大数乘法
function multiBy(numStr1, numStr2) {
// 需要判断正负
const isNum1Nagative = isNagative(numStr1);
const isNum2Nagative = isNagative(numStr2);
numStr1 = getPositivePart(numStr1);
numStr2 = getPositivePart(numStr2);
let len1 = numStr1.length;
let len2 = numStr2.length;
// 结果不会超过len1+len2位
let result = [];
// 从右往左计算
for (let i = len1 - 1; i >= 0; i--) {
for (let j = len2 - 1; j >= 0; j--) {
// 每一位可能要经过几次相加
result[i + j] = (result[i + j] || 0) + +numStr1[i] * +numStr2[j];
}
}
// 要计算进位,不反转从右往左计算也是可以的,但是得处理result[-1]、result[-2]这类情况
result = result.reverse();
for (let i = 0; i < result.length; i++) {
if (result[i] > 10) {
result[i + 1] = (result[i + 1] || 0) + Math.floor(result[i] / 10);
result[i] %= 10;
}
}
// 前面反转过,需要再反转回来
const resultStr = result.reverse().join('');
if (isNum1Nagative + isNum2Nagative === 1) {
return transformToNagative(resultStr);
}
return resultStr;
}
// 两个正数比较大小
function bigger(numStr1, numStr2) {
const len1 = numStr1.length;
const len2 = numStr2.length;
return len1 > len2 || (len1 === len2 && numStr1 >= numStr2)
}
function sub(numStr1, numStr2) {
// 需要判断正负
const isNum1Nagative = isNagative(numStr1);
const isNum2Nagative = isNagative(numStr2);
numStr1 = getPositivePart(numStr1);
numStr2 = getPositivePart(numStr2);
if (isNum1Nagative && !isNum2Nagative) {
// 负数减正数,等于两个正数相加再取负
return transformToNagative(add(numStr1, numStr2));
} else if (isNum1Nagative && isNum2Nagative) {
// 负数减负数,等于2-1
return sub(numStr2, numStr1);
} else if (!isNum1Nagative && isNum2Nagative) {
// 正数减负数,等于两个正数相加
return add(numStr1, numStr2);
}
// 小减大直接用大数减小数,再加上负号
if (!bigger(numStr1, numStr2)) {
return transformToNagative(sub(numStr2, numStr1));
}
// 补0
if (numStr2.length < numStr1.length) {
numStr2 = '0'.repeat(numStr1.length - numStr2.length) + numStr2
}
let carry = 0;
let result = ''
for(i = numStr1.length - 1; i >= 0; i--) {
let current = numStr1[i] - carry;
if (current < +numStr2[i]) {
carry = 1;
// 借位
current += 10;
} else {
carry = 0;
}
result = (current - numStr2[i]) + result;
}
// 去掉前面的0
let zeroLen = 0;
for (let i = 0; i < result.length; i++) {
if (result[i] === '0') {
zeroLen++
} else {
break;
}
}
if (zeroLen > 0) {
result = result.substr(zeroLen);
}
return result;
}
function divide(numStr1, numStr2) {
if (isZero(numStr2)) {
throw new Error('dividend can not be zero');
}
// 需要判断正负
const isNum1Nagative = isNagative(numStr1);
const isNum2Nagative = isNagative(numStr2);
// 一正一负则结果是负
const resultIsNagative = isNum1Nagative + isNum2Nagative === 1;
numStr1 = getPositivePart(numStr1);
numStr2 = getPositivePart(numStr2);
const len1 = numStr1.length;
const len2 = numStr2.length;
// 除数比被除数小,直接返回
if (!bigger(numStr1, numStr2)) {
return {
integer: '0',
remainder: numStr1,
}
}
let remainder = '0';
let integer = ''
// 截一部分出来做除数
let divider = numStr1.substr(0, len2);
numStr1 = numStr1.substr(len2);
do {
// 当前位结果计数,和最终结果不一样,最终结果有可能溢出,只能用字符串表示,这个只会一位
let currentInteger = 0;
// 除数比较小,检查是否已到最后,如果到了最后,直接返回
if (!bigger(divider, numStr2)) {
if (!numStr1) {
remainder = divider;
break;
} else {
// 否则再取一位
divider += numStr1[0];
numStr1 = numStr1.substr(1);
}
}
while(bigger(divider, numStr2)) {
currentInteger++;
divider = sub(divider, numStr2);
}
remainder = divider;
integer += currentInteger;
} while(numStr1)
if (!integer) {
integer = '0'
}
if (resultIsNagative && integer !== '0') {
integer = transformToNagative(integer)
}
return {
integer,
remainder,
}
}