@ivujs/i-utils
Version:
前端模块化 JavaScript 工具库
322 lines (318 loc) • 10.1 kB
JavaScript
;
var index = require('../validate/index.cjs');
var math = require('../constants/math.cjs');
/**
* @module 数学
*/
/* 数字计算 */
/**
* 两个数字相加
* @param {string|number} arg1 第一个数字
* @param {string|number} arg2 第二个数字
* @returns {number} 返回计算后的数字
*/
function add(arg1, arg2) {
let r1, r2;
try {
r1 = arg1.toString().split(".")[1].length;
}
catch (e) {
r1 = 0;
}
try {
r2 = arg2.toString().split(".")[1].length;
}
catch (e) {
r2 = 0;
}
const m = Math.pow(10, Math.max(r1, r2));
return (Number(arg1) * m + Number(arg2) * m) / m;
}
/**
* 两个数字相减
* @param {string|number} arg1 第一个数字
* @param {string|number} arg2 第二个数字
* @returns {number} 返回计算后的数字
*/
function subtract(arg1, arg2) {
let r1, r2;
try {
r1 = arg1.toString().split(".")[1].length;
}
catch (e) {
r1 = 0;
}
try {
r2 = arg2.toString().split(".")[1].length;
}
catch (e) {
r2 = 0;
}
const m = Math.pow(10, Math.max(r1, r2));
const n = r1 >= r2 ? r1 : r2;
return Number(((Number(arg1) * m - Number(arg2) * m) / m).toFixed(n));
}
/**
* 两个数字相乘
* @param {string|number} arg1 第一个数字
* @param {string|number} arg2 第二个数字
* @returns {number} 返回计算后的数字
*/
function multiply(arg1, arg2) {
let m = 0;
const s1 = arg1.toString();
const s2 = arg2.toString();
try {
m += s1.split(".")[1].length;
}
catch (e) { }
try {
m += s2.split(".")[1].length;
}
catch (e) { }
return (Number(s1.replace(".", "")) * Number(s2.replace(".", ""))) / Math.pow(10, m);
}
/**
* 两个数字相除
* @param {string|number} arg1 第一个数字
* @param {string|number} arg2 第二个数字
* @returns {number} 返回计算后的数字
*/
function divide(arg1, arg2) {
let t1 = 0, t2 = 0;
try {
t1 = arg1.toString().split(".")[1].length;
}
catch (e) { }
try {
t2 = arg2.toString().split(".")[1].length;
}
catch (e) { }
const r1 = Number(arg1.toString().replace(".", ""));
const r2 = Number(arg2.toString().replace(".", ""));
return (r1 / r2) * Math.pow(10, t2 - t1);
}
/**
* 两个数字取模
* @param {string|number} arg1 第一个数字
* @param {string|number} arg2 第二个数字
* @returns {number} 返回计算后的数字
*/
function modulo(arg1, arg2) {
let t1 = 0, t2 = 0, d = 0;
try {
t1 = arg1.toString().split(".")[1].length;
}
catch (e) { }
try {
t2 = arg2.toString().split(".")[1].length;
}
catch (e) { }
d = Math.pow(10, Math.max(t1, t2));
return (Math.round(Number(arg1) * d) % Math.round(Number(arg2) * d)) / d;
}
/**
* 最大公约数
* @param {string|number} arg1 第一个数字
* @param {string|number} arg2 第二个数字
* @returns {number} 返回计算后的数字
*/
function gcd(arg1, arg2) {
let a = Math.abs(Number(arg1));
let b = Math.abs(Number(arg2));
while (b !== 0) {
const temp = b;
b = a % b;
a = temp;
}
return a;
}
/**
* 最小公倍数
* @param {string|number} arg1 第一个数字
* @param {string|number} arg2 第二个数字
* @returns {number} 返回计算后的数字
*/
function scm(arg1, arg2) {
const a = Number(arg1);
const b = Number(arg2);
const absA = Math.abs(a);
const absB = Math.abs(b);
if (absA === 0 || absB === 0) {
return 0;
}
return (absA * absB) / gcd(absA, absB);
}
/* 数字精度 */
/**
* 强制保留小数位数
* @description 默认保留两位小数,解决原生的toFixed()会五舍六入的问题
* @param {string|number} num 数字
* @param {number} decimals 保留小数的位数,默认2位
* @param {number} mode 保留小数模式
* @returns {string} 返回保留后的数字字符串
*/
function toFixed(num, decimals = 2, mode = math.MATH.ROUND) {
// 四舍五入
if (mode === math.MATH.ROUND) {
return _toFixedRound(num, decimals);
}
// 向下舍出
else if (mode === math.MATH.ROUND_FLOOR) {
return _toFixedFloor(num, decimals);
}
else {
throw new TypeError("toFixed: mode is MATH.ROUND 0 or MATH.ROUND_FLOOR");
}
}
/**
* 尽可能保留小数位数
* @param {string|number} num 数字
* @param {number} decimals 保留小数的位数,默认2位
* @param {number} mode 保留小数模式
* @returns {number} 返回保留后的数字
*/
function toDecimal(num, decimals = 2, mode = math.MATH.ROUND) {
// 四舍五入
if (mode === math.MATH.ROUND) {
return Number(_toDecimalRound(num, decimals));
}
// 向下舍出
else if (mode === math.MATH.ROUND_FLOOR) {
return Number(_toDecimalFloor(num, decimals));
}
// 错误保留格式
else {
throw new TypeError("toDecimal: mode is MATH.ROUND 0 or MATH.ROUND_FLOOR");
}
}
/* 内部函数 */
/**
* 四舍五入,强制保留小数位数
* @description 默认保留两位小数,此方法解决原生的toFixed()会五舍六入的问题
* @param {string|number} num 数字
* @param {number} decimals 保留小数的位数,默认2位
* @returns {string} 返回字符串的数字
*/
function _toFixedRound(num, decimals = 2) {
if (index.isNaN(Number(num))) {
// num可能是字符串,先转数字再判断NaN
return "--";
}
let s = String(num);
if (!decimals)
decimals = 0;
if (s.indexOf(".") === -1)
s += ".";
s += new Array(decimals + 1).join("0");
// 正则模板字符串拼接,避免手动拼接字符串的转义问题
const reg = new RegExp(`^(-|\\+)?(\\d+(\\.\\d{0,${decimals + 1}})?)\\d*$`);
if (reg.test(s)) {
let s1 = "0" + RegExp.$2, b = true;
// 1. 把外层的数字变量 a 改名,避免和内层数组冲突
const decimalLength = RegExp.$3.length;
const pm = RegExp.$1;
if (decimalLength === decimals + 2) {
// 2. 内层数组用新变量名 digitArr,明确类型为 string[]
const digitArr = s1.match(/\d/g);
if (parseInt(digitArr[digitArr.length - 1]) > 4) {
for (let i = digitArr.length - 2; i >= 0; i--) {
// 3. 确保赋值为字符串类型
digitArr[i] = (parseInt(digitArr[i]) + 1).toString();
if (digitArr[i] === "10") {
digitArr[i] = "0";
b = i !== 1;
}
else
break;
}
}
s1 = digitArr.join("").replace(new RegExp(`(\\d+)(\\d{${decimals}})\\d$`), "$1.$2");
}
if (b)
s1 = s1.substr(1);
return (pm + s1).replace(/\.$/, "");
}
return String(num);
}
/**
* 向下舍出,强制保留小数位数
* @description 默认保留两位小数,此方法相当于强制截取小数位数
* @param {string|number} num 数字
* @param {number} decimals 保留小数的位数,默认2位
* @returns {string} 返回字符串的数字
*/
function _toFixedFloor(num, decimals = 2) {
// num可能是字符串,先转数字再判断NaN
const numVal = Number(num);
if (index.isNaN(numVal)) {
throw new TypeError(`${num} is NaN`);
}
// 校验小数位数范围
if (decimals < 0 || decimals > 20) {
throw new TypeError("_toFixedFloor: decimals places must be between 0 and 20");
}
// 默认为保留的小数点后两位
const dec = Math.max(0, Math.floor(decimals)); // 确保小数位数为非负整数
const tempNumStr = String(numVal); // 提前转字符串,避免重复转换
const pointIndex = tempNumStr.indexOf("."); // :去掉+1,直接获取小数点位置
// 获取小数点后的个数(需要保证有小数位)
const pointCount = pointIndex > -1 ? tempNumStr.length - pointIndex - 1 : 0;
// 源数据为整数或者小数点后面小于decimals位的作补零处理
if (pointIndex === -1 || pointCount <= dec) {
// pointIndex === -1 表示无小数点
let tempNumA = tempNumStr; // 统一为字符串类型,避免类型混淆
if (pointIndex === -1) {
tempNumA = `${tempNumA}.`;
// 补零到指定小数位
for (let index = 0; index < dec; index++) {
tempNumA = `${tempNumA}0`;
}
}
else {
// 补零到指定小数位
for (let index = 0; index < dec - pointCount; index++) {
tempNumA = `${tempNumA}0`;
}
}
return tempNumA;
}
// 截取当前数据到小数点后decimals位
const [integerPart, decimalPart] = tempNumStr.split(".");
return `${integerPart}.${decimalPart.substring(0, dec)}`;
}
/**
* 四舍五入,尽可能保留小数
* @param {string|number} num 数字
* @param {number} decimals 保留小数的位数,默认2位
* @returns {string} 返回保留后的数字
*/
function _toDecimalRound(num, decimals = 2) {
if (index.isNaN(Number(num))) {
throw new TypeError(`_toDecimalRound: ${num} is not a number`);
}
const n = Math.pow(10, decimals);
return Math.round(Number(num) * n) / n;
}
/**
* 向下舍入,尽可能保留小数
* @param {string|number} num 数字
* @param {number} decimals 保留小数的位数,默认2位
* @returns {number} 返回保留后的数字
*/
function _toDecimalFloor(num, decimals = 2) {
if (index.isNaN(Number(num))) {
throw new TypeError(`_toDecimalFloor: ${num} is not a number`);
}
const n = Math.pow(10, decimals);
return Math.floor(Number(num) * n) / n;
}
exports.add = add;
exports.divide = divide;
exports.gcd = gcd;
exports.modulo = modulo;
exports.multiply = multiply;
exports.scm = scm;
exports.subtract = subtract;
exports.toDecimal = toDecimal;
exports.toFixed = toFixed;