dnum
Version:
Small library for big decimal numbers.
350 lines (343 loc) • 12.7 kB
JavaScript
var import_node_module = require("node:module");
var __create = Object.create;
var __getProtoOf = Object.getPrototypeOf;
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __toESM = (mod, isNodeMode, target) => {
target = mod != null ? __create(__getProtoOf(mod)) : {};
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
for (let key of __getOwnPropNames(mod))
if (!__hasOwnProp.call(to, key))
__defProp(to, key, {
get: () => mod[key],
enumerable: true
});
return to;
};
var __moduleCache = /* @__PURE__ */ new WeakMap;
var __toCommonJS = (from) => {
var entry = __moduleCache.get(from), desc;
if (entry)
return entry;
entry = __defProp({}, "__esModule", { value: true });
if (from && typeof from === "object" || typeof from === "function")
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
get: () => from[key],
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
}));
__moduleCache.set(from, entry);
return entry;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {
get: all[name],
enumerable: true,
configurable: true,
set: (newValue) => all[name] = () => newValue
});
};
// src/index.ts
var exports_src = {};
__export(exports_src, {
toString: () => toString,
toParts: () => toParts,
toNumber: () => toNumber,
toJSON: () => toJSON,
subtract: () => subtract,
sub: () => subtract,
setDecimals: () => setDecimals,
round: () => round,
remainder: () => remainder,
rem: () => remainder,
multiply: () => multiply,
mul: () => multiply,
lte: () => lessThanOrEqual,
lt: () => lessThan,
lessThanOrEqual: () => lessThanOrEqual,
lessThan: () => lessThan,
isDnum: () => isDnum,
gte: () => greaterThanOrEqual,
gt: () => greaterThan,
greaterThanOrEqual: () => greaterThanOrEqual,
greaterThan: () => greaterThan,
fromJSON: () => fromJSON,
from: () => from,
format: () => format,
floor: () => floor,
equalizeDecimals: () => equalizeDecimals,
equal: () => equal,
eq: () => equal,
divide: () => divide,
div: () => divide,
compare: () => compare,
cmp: () => compare,
ceil: () => ceil,
add: () => add,
abs: () => abs2
});
module.exports = __toCommonJS(exports_src);
// src/dnum.ts
var import_from_exponential = __toESM(require("from-exponential"));
// src/utils.ts
function divideAndRoundUp(dividend, divisor) {
const num = divisor > 0n ? dividend : -dividend;
const den = divisor > 0n ? divisor : -divisor;
const remainder = num % den;
const roundUp = remainder > 0n ? 1n : 0n;
return num / den + roundUp;
}
function divideAndRoundDown(dividend, divisor) {
const num = divisor > 0n ? dividend : -dividend;
const den = divisor > 0n ? divisor : -divisor;
const remainder = num % den;
const roundDown = remainder < 0n ? -1n : 0n;
return num / den + roundDown;
}
function divideAndRoundHalf(dividend, divisor) {
const num = divisor > 0n ? dividend : -dividend;
const den = divisor > 0n ? divisor : -divisor;
const invertSign = num < 0n ? -1n : 1n;
return (num * invertSign + den / 2n) / den * invertSign;
}
function divideAndRound(dividend, divisor, rounding = "ROUND_HALF") {
return rounding === "ROUND_UP" ? divideAndRoundUp(dividend, divisor) : rounding === "ROUND_DOWN" ? divideAndRoundDown(dividend, divisor) : divideAndRoundHalf(dividend, divisor);
}
function splitNumber(number) {
let [whole, fraction = "0"] = number.split(".");
if (whole === "") {
whole = "0";
}
fraction = fraction.replace(/(?!^)0*$/, "");
return [whole, fraction];
}
function powerOfTen(zeroes) {
return BigInt("1" + "0".repeat(zeroes));
}
function roundToPower(value, power) {
const a = value / power * power;
const b = a + power;
return value - a >= b - value ? b : a;
}
function ceilToPower(value, power) {
const remainder = value % power;
return remainder === 0n ? value : value / power * power + power;
}
function floorToPower(value, power) {
return value / power * power;
}
function abs(value) {
return value < 0n ? -value : value;
}
// src/dnum.ts
function isDnum(value) {
return Array.isArray(value) && value.length === 2 && typeof value[0] === "bigint" && typeof value[1] === "number";
}
var NUM_RE = /^-?(?:[0-9]+|(?:[0-9]*(?:\.[0-9]+)))$/;
function from(value, decimals = true) {
if (isDnum(value)) {
return setDecimals(value, decimals === true ? value[1] : decimals);
}
value = String(value);
if (value.includes("e")) {
value = import_from_exponential.default(value);
}
if (!value.match(NUM_RE)) {
throw new Error(`dnum: incorrect number (${value})`);
}
const negative = value.startsWith("-");
if (negative) {
value = value.slice(1);
}
const parts = splitNumber(value);
const whole = parts[0];
let fraction = parts[1];
if (decimals === true) {
decimals = fraction === "0" ? 0 : fraction.length;
}
fraction = fraction.slice(0, decimals);
fraction = fraction + "0".repeat(decimals - fraction.length);
const result = (BigInt(whole) * powerOfTen(decimals) + BigInt(fraction)) * (negative ? -1n : 1n);
return [result, decimals];
}
function setValueDecimals(value, decimalsDiff, options = {}) {
options.rounding ??= "ROUND_HALF";
if (decimalsDiff > 0) {
return value * powerOfTen(decimalsDiff);
}
if (decimalsDiff < 0) {
return divideAndRound(value, powerOfTen(-decimalsDiff), options.rounding);
}
return value;
}
function setDecimals(value, decimals, options = {}) {
options.rounding ??= "ROUND_HALF";
if (value[1] === decimals) {
return value;
}
if (value[1] < 0 || decimals < 0) {
throw new Error("dnum: decimals cannot be negative");
}
const decimalsDiff = decimals - value[1];
return [
setValueDecimals(value[0], decimalsDiff, options),
decimals
];
}
function equalizeDecimals(nums, decimals) {
const decimals_ = decimals ?? Math.max(...nums.map(([, decimals2]) => decimals2), 0);
return nums.map((num) => setDecimals(num, decimals_));
}
function toJSON([value, decimals]) {
return JSON.stringify([String(value), decimals]);
}
function fromJSON(jsonValue) {
const [value, decimals] = JSON.parse(jsonValue);
return [BigInt(value), decimals];
}
function toParts(dnum, optionsOrDigits = {}) {
const [value, decimals] = dnum;
const options = typeof optionsOrDigits === "number" ? { digits: optionsOrDigits } : optionsOrDigits;
const {
digits = decimals,
trailingZeros,
decimalsRounding
} = options;
const decimalsDivisor = powerOfTen(decimals);
let whole = value / decimalsDivisor;
const fractionValue = abs(value % decimalsDivisor);
const roundFn = decimalsRounding === "ROUND_UP" ? ceilToPower : decimalsRounding === "ROUND_DOWN" ? floorToPower : roundToPower;
let fraction = String(roundFn(BigInt("1" + "0".repeat(Math.max(0, String(decimalsDivisor).length - String(fractionValue).length - 1)) + String(fractionValue)), powerOfTen(Math.max(0, decimals - digits))));
if (fraction.startsWith("2")) {
whole += 1n;
}
fraction = fraction.slice(1, digits + 1);
fraction = trailingZeros ? fraction.padEnd(digits, "0") : fraction.replace(/0+$/, "");
return [
abs(whole),
fraction === "" || BigInt(fraction) === 0n && !trailingZeros ? null : fraction
];
}
function toNumber(value, optionsOrDigits) {
return Number(toString(value, optionsOrDigits));
}
function toString(value, optionsOrDigits) {
const [whole, fraction] = toParts(value, optionsOrDigits);
return (value[0] >= 0n ? "" : "-") + whole + (fraction ? `.${fraction}` : "");
}
// src/formatting.ts
function format(dnum, optionsOrDigits = {}) {
const options = typeof optionsOrDigits === "number" ? { digits: optionsOrDigits } : optionsOrDigits;
const {
compact,
locale = Intl.NumberFormat().resolvedOptions().locale,
signDisplay = "auto",
...toPartsOptions
} = options;
const [whole, fraction] = toParts(dnum, toPartsOptions);
const decimalsSeparator = new Intl.NumberFormat(locale).formatToParts(0.1).find((v) => v.type === "decimal")?.value ?? ".";
const roundsToZero = whole === 0n && (fraction === null || /^0+$/.test(fraction));
const wholeString = formatSign(dnum, roundsToZero, signDisplay) + BigInt(whole).toLocaleString(locale, {
notation: compact ? "compact" : "standard"
});
return fraction === null || !/\d/.test(wholeString.at(-1)) ? wholeString : `${wholeString}${decimalsSeparator}${fraction}`;
}
function formatSign(dnum, roundsToZero, signDisplay) {
if (signDisplay === "auto") {
return dnum[0] >= 0n ? "" : "-";
}
if (signDisplay === "always") {
return dnum[0] >= 0n ? "+" : "-";
}
if (signDisplay === "exceptZero") {
return roundsToZero ? "" : dnum[0] >= 0n ? "+" : "-";
}
if (signDisplay === "negative") {
return dnum[0] >= 0n || roundsToZero ? "" : "-";
}
return "";
}
// src/operations.ts
function add(num1, num2, decimals) {
const [num1_, num2_] = normalizePairAndDecimals(num1, num2, decimals);
return setDecimals([num1_[0] + num2_[0], num1_[1]], decimals ?? (isDnum(num1) ? num1[1] : num1_[1]));
}
function subtract(num1, num2, decimals) {
const [num1_, num2_] = normalizePairAndDecimals(num1, num2, decimals);
return setDecimals([num1_[0] - num2_[0], num1_[1]], decimals ?? (isDnum(num1) ? num1[1] : num1_[1]));
}
function multiply(num1, num2, optionsOrDecimals = {}) {
const options = typeof optionsOrDecimals === "number" ? { decimals: optionsOrDecimals } : optionsOrDecimals;
options.rounding ??= "ROUND_HALF";
const [num1_, num2_] = normalizePairAndDecimals(num1, num2, options.decimals);
return setDecimals([num1_[0] * num2_[0], num1_[1] * 2], options.decimals ?? (isDnum(num1) ? num1[1] : num1_[1]), { rounding: options.rounding });
}
function divide(num1, num2, optionsOrDecimals = {}) {
const options = typeof optionsOrDecimals === "number" ? { decimals: optionsOrDecimals } : optionsOrDecimals;
options.rounding ??= "ROUND_HALF";
const [num1_, num2_] = normalizePairAndDecimals(num1, num2, options.decimals);
if (num2_[0] === 0n) {
throw new Error("dnum: division by zero");
}
const value1 = setValueDecimals(num1_[0], Math.max(num1_[1], options.decimals ?? 0));
const value2 = setValueDecimals(num2_[0], 0);
return setDecimals([divideAndRound(value1, value2, options.rounding), num1_[1]], options.decimals ?? (isDnum(num1) ? num1[1] : num1_[1]), { rounding: options.rounding });
}
function remainder(num1, num2, decimals) {
const [num1_, num2_] = normalizePairAndDecimals(num1, num2);
return setDecimals([num1_[0] % num2_[0], num1_[1]], decimals ?? (isDnum(num1) ? num1[1] : num1_[1]));
}
function compare(num1, num2) {
const [num1_, num2_] = normalizePairAndDecimals(num1, num2);
return num1_[0] > num2_[0] ? 1 : num1_[0] < num2_[0] ? -1 : 0;
}
function equal(num1, num2) {
const [num1_, num2_] = normalizePairAndDecimals(num1, num2);
return num1_[0] === num2_[0];
}
function greaterThan(num1, num2) {
const [num1_, num2_] = normalizePairAndDecimals(num1, num2);
return num1_[0] > num2_[0];
}
function greaterThanOrEqual(num1, num2) {
return !lessThan(num1, num2);
}
function lessThan(num1, num2) {
const [num1_, num2_] = normalizePairAndDecimals(num1, num2);
return num1_[0] < num2_[0];
}
function lessThanOrEqual(num1, num2) {
return !greaterThan(num1, num2);
}
function abs2(num, decimals) {
const [valueIn, decimalsIn] = from(num);
if (decimals === undefined)
decimals = decimalsIn;
let valueAbs = valueIn;
if (valueAbs < 0n) {
valueAbs = -valueAbs;
}
return setDecimals([valueAbs, decimalsIn], decimals);
}
function floor(num, decimals) {
return round(num, { decimals, rounding: "ROUND_DOWN" });
}
function ceil(num, decimals) {
return round(num, { decimals, rounding: "ROUND_UP" });
}
function round(num, optionsOrDecimals = {}) {
const options = typeof optionsOrDecimals === "number" ? { decimals: optionsOrDecimals } : optionsOrDecimals;
options.rounding ??= "ROUND_HALF";
const numIn = from(num);
return setDecimals(setDecimals(numIn, 0, { rounding: options.rounding }), options.decimals === undefined ? numIn[1] : options.decimals);
}
function normalizePairAndDecimals(num1, num2, decimals) {
const num1_ = from(num1);
const num2_ = from(num2);
if (num1_[1] < 0 || num2_[1] < 0) {
throw new Error("dnum: decimals cannot be negative");
}
return equalizeDecimals([num1_, num2_], Math.max(num1_[1], num2_[1], decimals ?? 0));
}