开篇
BigInt是JavaScript的类型之一,用于表示超过 2 53 − 1 2^{53} - 1 253−1的数,但是只支持小数,而有时常常需要使用到小数运算,这该如何实现呢?
正文
BigInt仅支持整数,但我们可以获取字符串形式的高精度小数(以下统称BigNumber)的小数点位置,然后转换为BigInt,再在计算结束后重新添加小数点以实现BigNumber的运算。
所有前置函数
function tenTimes (val) { //10的val次方
return 10n ** BigInt(val);
};
function times (val, num) { //重复字符串val num次
return val.repeat(num);
};
function toNum (val) { //把val转换为数字并把NaN转为0
return isNaN(Number(val)) ? 0 : Number(val);
};
function removeLeft (str, pattern) { //删除字符串str左侧的所有指定字符串pattern
const regex = new RegExp("^" + pattern + "+");
return str.replace(regex, "");
};
function removeRight (str, pattern) { //删除字符串str右侧的所有指定字符串pattern
const regex = new RegExp(pattern + "+$");
return str.replace(regex, "");
};
转换
num='123.456'
/*
index: 0 1 2 3 4 5 6
value: 1 2 3 . 4 5 6
*/
len=num.indexOf('.') //获取高精度小数的小数点位置
//3
int=num.slice(0,len) //正数部分
//'123'
dec=num.slice(len+1) //小数部分
//'456'
int+dec
//'123456'
num.length-len-1 //小数部分长度
// 3
整理上述代码,可以得到获取BigNumber的BigInt表示形式与小数部分长度。
function toBigNumber (num) {
let str = String(num),
P = str.indexOf("."); //获取小数点位置
P = P === -1 ? str.length - 1 : P; //处理无小数点情况
let noDecNumber=str.replace(".", ""); //删除小数点
let try_ = Number(noDecNumber);
let [str_, len_] = isNaN(try_)
? ["0", 0]
: noDecNumber.indexOf(".") === -1
? [noDecNumber, str.length - P - 1]
: ["0", 0]; //处理try_不是合法整数的情况
return {
num: BigInt(str_),
len: len_,
};
};
这样转换我们便得到了BigNumber。
统一位数
计算时,两个BigNumber的小数位数需要统一。
/*
num1:
index: 0 1 2 3 4 5 6
value: 1 2 3 . 4 5 6
num2:
index: 0 1 2 3 4 5 6
value: 1 2 3 4 . 5 6
↓↓
num1:
index: 0 1 2 3 4 5 6 7 \ 0 1 2 3 4 5 6
value: 0 1 2 3 . 4 5 6 → 0 1 2 3 4 5 6
num2:
index: 0 1 2 3 4 5 6 7 \ 0 1 2 3 4 5 6
value: 1 2 3 4 . 5 6 0 → 1 2 3 4 5 6 0
*/
可以发现,统一位数便是将小数部分长度较小的BigNumber(minBN)左移 a b s ( n u m 1. l e n − n u m 2. l e n ) abs(num1.len-num2.len) abs(num1.len−num2.len)( Δ l e n \Delta len Δlen )位,便是minBN转换为 m i n B N × ( 1 0 Δ l e n ) minBN \times (10^{\Delta len}) minBN×(10Δlen) 。
整理代码,得出:
function toSamePer (num, num2) {
let a = toBigNumber(num),
b = toBigNumber(num2);
let len = Math.max(a.len, b.len);
if (len === a.len)
b.num = b.num * tenTimes(len - b.len);
else
a.num = a.num * tenTimes(len - a.len);
a.len = b.len = len;
return {
num: a,
num2: b,
};
};
我们得到了统一两个BigNumber的小数位数的函数。
格式化
计算好后,我们还需要将BigNumber转回字符串形式的BigNumber,如:
num='-123456'
//num是计算后的BigNumber BigInt表现形式
len=3
//len是小数部分长度
//先判断是否是负数
flag=0
if (num.startsWith("-")) {
num = num.replace("-", "");
flag = 1;
}
//flag=1,num='123456'
//再在开头补0,测试时发现不补0会出现位数不够的情况
num = times("0", len * 2) + num
//000000123456
int = num.slice(0, num.length - len)
//000000123
dec = num.slice(num.length - len)
// 456
int = removeLeft(int, '0')
dec = removeRight(dec, '0')
//删除整数部分左侧的0与小数部分右侧的0
int = int === "" ? "0" : int
dec = dec === "" ? "" : "." + dec
//整理处理特殊情况
result = int + dec
//合并
result = (flag ? "-" : "") + result
//如果是负数加上负号
整理代码:
function formatNum (result, len) {
result = String(result);
let flag = 0;
if (result.startsWith("-")) {
result = result.replace("-", "");
flag = 1;
}
result = times("0", len * 2) + result;
let int = result.slice(0, result.length - len),
dec = result.slice(result.length - len);
int = removeLeft(int, '0');
dec = removeRight(dec, '0');
int = int === "" ? "0" : int;
dec = dec === "" ? "" : "." + dec;
let result_ = int + dec;
result_ = (flag ? "-" : "") + result_;
return result_;
};
这样,格式化BigNumber为字符串形式的函数就完成了。
计算
计算就很简单了。
function addFunc(num, num2) {
let { num: a, num2: b } = toSamePer(num, num2);
let result = a.num + b.num;
return formatNum(result, a.len);
};
function subFunc (num, num2) {
let { num: a, num2: b } = toSamePer(num, num2);
let result = a.num - b.num;
return formatNum(result, a.len);
};
function mulFunc (num, num2) {
let { num: a, num2: b } = toSamePer(num, num2);
let result = a.num * b.num;
return formatNum(result, a.len * 2);
//乘法是两个len,因为是两个小数部分长度为len的BigNumber相乘得出的
};
function divFunc (num, num2) {
let { num: a, num2: b } = toSamePer(num, num2);
if (b.num === 0n) return Infinity; //除数为0则Infinity
let result = (a.num * tenTimes(100)) / b.num;
return formatNum(result, 100);
};
function powFunc (num, num2) {
let a = toBigNumber(num),
b = Math.trunc(toNum(num2)), //处理指数非整数
flag = 0;
if (a.num === 0n) return '0'; //a^b,a为0则结果为0
else if (b.num === 0n) return '1'; //a非0时b为0则结果为1
if (b < 0) (flag = 1), (b = -b); //处理负指数
let result = a.num ** BigInt(b);
if (!flag) return formatNum(result, a.len * b);
else return divFunc("1", formatNum(result, a.len * b)); //处理负指数
};
function modFunc (num, num2) {
let { num: a, num2: b } = toSamePer(num, num2);
if (b.num === 0n) return '0'; //a%b,b为0则结果为0
let result = a.num % b.num;
return formatNum(result, a.len);
};
结语
差不多就这些了,新人第一次写文,如有疏漏请多包涵()