@lzwme/asmd-calc
Version:
支持浮点数精度的加减乘除四则运算 JS 库。
230 lines (229 loc) • 7.04 kB
JavaScript
/*
* @Author: renxia
* @Date: 2018-09-10 15:10:40
* @LastEditors: renxia
* @LastEditTime: 2025-04-02 16:47:52
* @Description: 支持浮点数精度的加减乘除四则运算
*/
import { getDecimalLen, isNull, toNonExponential } from './utils';
/**
* addition 加法计算。与普通加法运算不同的是,任意参数为 `null/NaN/undefined`,均会被视为 `0` 而忽略
*
* ### Example (es module)
* ```js
* import { add } from 'asmd-calc';
* console.log(add(0.1, 0.2, 3));
* // => 3.3
* ```
*
* @returns 永远返回有效的数值,不存在 NaN(视作0处理)
*/
export function add(...args) {
let total = Number(args[0]);
args.slice(1).forEach((value) => {
if (!value)
return;
if (!total)
return (total = value);
const tDLen = getDecimalLen(total);
const vDLen = getDecimalLen(value);
const decimalLen = tDLen + vDLen;
if (decimalLen) {
const e = Math.pow(10, Math.max(tDLen, vDLen));
total = (Math.round(total * e) + Math.round(value * e)) / e;
}
else {
total += typeof value === 'number' ? value : Number(value) || 0;
}
});
return total;
}
/**
* subtraction 减法计算。与普通减法运算不同的是,任意参数为 `null/NaN/undefined`,均会被视为 `0` 而忽略
*
* ### Example (es module)
* ```js
* import { sub } from 'asmd-calc';
* console.log(sub(0.3, 0.2, 0.1));
* // => 0
* ```
*
* @param args 第一个值为减数,后续值均为被减数
* @returns 永远返回有效的数值,不存在 NaN(视作0处理)
*/
export function sub(...args) {
if (!args.length)
return null;
args.forEach((value, idx) => {
if (idx && value)
args[idx] = -value;
});
return add(...args);
}
/**
* multiplication 乘法计算。
* - 注意小数位不宜过长(总长度不超过18位,结果不超过18位)
* - 任意参数为 `null/NaN/undefined`,均会被视为 `0`
*
* ### Example (es module)
* ```js
* import { mul } from 'asmd-calc';
* console.log(mul(3, 0.2, 0.1));
* // => 0.06
* ```
*
* @returns 永远返回有效的数值,不存在 NaN(视作0处理)
*/
export function mul(...args) {
let total = Number(args[0]);
if (!args.length)
return 0;
args.slice(1).forEach((value) => {
if (typeof value !== 'number')
value = +value || 0;
if (!value || !total)
return (total = 0);
if (1 === total)
return (total = value);
const tDLen = getDecimalLen(total);
const vDLen = getDecimalLen(value);
const decimalLen = tDLen + vDLen;
if (decimalLen) {
const e = Math.pow(10, decimalLen);
total = tDLen ? Math.round(total * Math.pow(10, tDLen)) : total; // Number(toNonExponential(total).replace('.', ''));
value = vDLen ? Math.round(value * Math.pow(10, vDLen)) : value; // Number(toNonExponential(value).replace('.', ''));
total = (total * value) / e;
}
else {
total *= value;
}
});
return total;
}
/**
* division 除法计算。
*
* 任意参数为 `null/NaN/undefined`,均会被视为 `0`。于是会有如下情况:
* ```
* NaN / 1 = 0 / 1 = 0
* 0 / NaN = 0 / 0 = NaN
* 1 / NaN = 1 / 0 = Infinity
* ```
*
* ### Example (es module)
* ```js
* import { div } from 'asmd-calc';
* console.log(div(0.6, 0.1, 0.2));
* // => 30
* ```
*
* @param args 第一个参数为除数,后续参数均为被除数
* @returns 永远返回有效的数值。
*/
export function div(...args) {
if (!args.length)
return null;
let total = args[0];
args.slice(1).forEach((value) => {
if (!value || !total) {
// null \ NaN \ undefined
total /= value;
return;
}
value = Number(value);
const tDLen = getDecimalLen(total);
const vDLen = getDecimalLen(value);
const decimalLen = vDLen - tDLen;
if (vDLen || tDLen) {
let e = Math.pow(10, decimalLen);
if (decimalLen < 0)
e = Number(e.toFixed(-decimalLen));
total = tDLen ? Math.round(total * Math.pow(10, tDLen)) : total; // Number(toNonExponential(total).replace('.', ''));
value = vDLen ? Math.round(value * Math.pow(10, vDLen)) : value; // Number(toNonExponential(value).replace('.', ''));
total = mul(total / value, e);
}
else {
total /= value;
}
});
return total;
}
/**
* 最多保留 N 位小数
*
* ### Example (es module)
* ```js
* import { keepDotDLength } from 'asmd-calc';
* console.log(keepDotDLength(0.66666, 2));
* // => 0.66
* console.log(keepDotDLength(0.66666, 2, false));
* // => 0.66
* console.log(keepDotDLength(0.66666, 2, true));
* // => 0.67
* ```
*
* @param value 数值
* @param precision 小数位数,应为 0-16 之间的整数
* @param isrounding 是否四舍五入取值。默认 false
*/
export function keepDotLength(value, precision, type = 'floor') {
if (isNull(value))
return null;
if (type === true)
type = 'round';
else if (!type)
type = 'floor';
return Number(toFixed(value, precision, type));
}
/**
* toFixed 方法重写 【解决 Number.toFixed 方法在不同浏览器表现不一致的问题】
*
* ### Example (es module)
* ```js
* import { toFixed } from 'asmd-calc';
* console.log(toFixed(0.66666, 2));
* // => 0.67
* console.log(toFixed(1.45, 1));
* // => 1.5
* console.log(toFixed(1.55, 1));
* // => 1.6
* console.log(toFixed(1.515, 2));
* // => 1.52
* ```
*
* @param value 数值
* @param precision 小数位数,应为 0-16 之间的整数
* @param type 进位处理方法:
* - round - 四舍五入
* - ceil 向上取整
* - floor 向下取整(截断)
*/
export function toFixed(value, precision, type = 'round') {
if (isNull(value))
return null;
if (!(type in Math))
throw new Error('type must be one of ["ceil", "round", "fround", "floor"]. current: ' + type);
value = Number(value);
precision = Math.max(Number(precision), 0);
if (!precision)
return String(Math[type](value));
const valList = toNonExponential(value).split('.');
if (!valList[1]) {
valList[1] = new Array(precision + 1).join('0');
return valList.join('.');
}
else {
const result = String(Math[type](Math.pow(10, precision) * Number('0.' + valList[1])) / Math.pow(10, precision)).split('.');
// result = 1,则没有小数部分
if (!result[1]) {
result[1] = '';
valList[0] = String(Number(valList[0]) + Number(result[0]));
}
// 小数部分末尾补 0
if (result[1].length < precision) {
result[1] += new Array(precision - result[1].length + 1).join('0');
}
valList[1] = result[1];
return valList.join('.');
}
}