monie-utils
Version:
A comprehensive TypeScript library for money-related utilities including currency formatting, conversion, validation, and financial calculations
1,629 lines (1,613 loc) • 59.8 kB
JavaScript
'use strict';
/* monie-utils - A comprehensive TypeScript library for money-related utilities */
// src/errors.ts
var MonieUtilsError = class _MonieUtilsError extends Error {
constructor(message) {
super(message);
this.code = "MONIE_UTILS_ERROR";
this.name = "MonieUtilsError";
if (Error.captureStackTrace) {
Error.captureStackTrace(this, _MonieUtilsError);
}
}
};
function createError(message) {
return new MonieUtilsError(message);
}
// src/formatCurrency/constants.ts
var DEFAULT_FORMAT_OPTIONS = {
locale: "en-US",
showSymbol: true,
showCode: false,
useGrouping: true,
symbolPosition: "start"
};
var CURRENCY_INFO = {
// Major World Currencies
USD: {
code: "USD",
symbol: "$",
name: "US Dollar",
decimalPlaces: 2,
usesGrouping: true
},
EUR: {
code: "EUR",
symbol: "\u20AC",
name: "Euro",
decimalPlaces: 2,
usesGrouping: true
},
GBP: {
code: "GBP",
symbol: "\xA3",
name: "British Pound",
decimalPlaces: 2,
usesGrouping: true
},
JPY: {
code: "JPY",
symbol: "\xA5",
name: "Japanese Yen",
decimalPlaces: 0,
usesGrouping: true
},
CHF: {
code: "CHF",
symbol: "CHF",
name: "Swiss Franc",
decimalPlaces: 2,
usesGrouping: true
},
CAD: {
code: "CAD",
symbol: "C$",
name: "Canadian Dollar",
decimalPlaces: 2,
usesGrouping: true
},
AUD: {
code: "AUD",
symbol: "A$",
name: "Australian Dollar",
decimalPlaces: 2,
usesGrouping: true
},
// African Currencies
NGN: {
code: "NGN",
symbol: "\u20A6",
name: "Nigerian Naira",
decimalPlaces: 2,
usesGrouping: true
},
ZAR: {
code: "ZAR",
symbol: "R",
name: "South African Rand",
decimalPlaces: 2,
usesGrouping: true
},
KES: {
code: "KES",
symbol: "KSh",
name: "Kenyan Shilling",
decimalPlaces: 2,
usesGrouping: true
},
GHS: {
code: "GHS",
symbol: "\u20B5",
name: "Ghanaian Cedi",
decimalPlaces: 2,
usesGrouping: true
},
// Asian Currencies
CNY: {
code: "CNY",
symbol: "\xA5",
name: "Chinese Yuan",
decimalPlaces: 2,
usesGrouping: true
},
INR: {
code: "INR",
symbol: "\u20B9",
name: "Indian Rupee",
decimalPlaces: 2,
usesGrouping: true
},
SGD: {
code: "SGD",
symbol: "S$",
name: "Singapore Dollar",
decimalPlaces: 2,
usesGrouping: true
},
// Cryptocurrencies
BTC: {
code: "BTC",
symbol: "\u20BF",
name: "Bitcoin",
decimalPlaces: 8,
usesGrouping: true
},
ETH: {
code: "ETH",
symbol: "\u039E",
name: "Ethereum",
decimalPlaces: 8,
usesGrouping: true
},
USDT: {
code: "USDT",
symbol: "\u20AE",
name: "Tether",
decimalPlaces: 2,
usesGrouping: true
}
};
var COMPACT_SUFFIXES = {
K: 1e3,
M: 1e6,
B: 1e9,
T: 1e12
};
var COMPACT_THRESHOLDS = {
THOUSAND: 1e3,
MILLION: 1e6,
BILLION: 1e9,
TRILLION: 1e12
};
// src/formatCurrency/formatCurrency.ts
function isValidAmount(amount) {
return typeof amount === "number" && Number.isFinite(amount);
}
function isValidCurrency(currency) {
return currency.toUpperCase() in CURRENCY_INFO;
}
function formatCompactNumber(amount, decimalPlaces = 1) {
const absAmount = Math.abs(amount);
if (absAmount >= COMPACT_THRESHOLDS.TRILLION) {
return `${(absAmount / COMPACT_THRESHOLDS.TRILLION).toFixed(decimalPlaces)}T`;
}
if (absAmount >= COMPACT_THRESHOLDS.BILLION) {
return `${(absAmount / COMPACT_THRESHOLDS.BILLION).toFixed(decimalPlaces)}B`;
}
if (absAmount >= COMPACT_THRESHOLDS.MILLION) {
return `${(absAmount / COMPACT_THRESHOLDS.MILLION).toFixed(decimalPlaces)}M`;
}
if (absAmount >= COMPACT_THRESHOLDS.THOUSAND) {
return `${(absAmount / COMPACT_THRESHOLDS.THOUSAND).toFixed(decimalPlaces)}K`;
}
return absAmount.toFixed(decimalPlaces);
}
function formatCurrency(amount, currency, options = {}) {
if (!isValidAmount(amount)) {
throw new MonieUtilsError(
`Invalid amount: ${amount}. Amount must be a finite number.`
);
}
const upperCurrency = currency.toUpperCase();
if (!isValidCurrency(upperCurrency)) {
throw new MonieUtilsError(
`Unsupported currency: ${currency}. Check the currency code.`
);
}
const opts = { ...DEFAULT_FORMAT_OPTIONS, ...options };
const currencyInfo = CURRENCY_INFO[upperCurrency];
const decimalPlaces = opts.decimalPlaces ?? currencyInfo.decimalPlaces;
if (opts.compact) {
const isNegative = amount < 0;
const absAmount = Math.abs(amount);
const compactNumber = formatCompactNumber(absAmount, 1);
const symbol = opts.customSymbol ?? currencyInfo.symbol;
let formatted;
if (opts.symbolPosition === "end") {
formatted = opts.showCode ? `${compactNumber} ${upperCurrency}` : `${compactNumber}${symbol}`;
} else {
formatted = opts.showCode ? `${upperCurrency} ${compactNumber}` : `${symbol}${compactNumber}`;
}
if (isNegative) {
formatted = `-${formatted}`;
}
return {
formatted,
amount,
currency: upperCurrency,
locale: opts.locale,
isCompact: true
};
}
try {
const isNegative = amount < 0;
const absAmount = Math.abs(amount);
const formatOptions = {
style: "decimal",
minimumFractionDigits: decimalPlaces,
maximumFractionDigits: decimalPlaces,
useGrouping: opts.useGrouping
};
const formatter = new Intl.NumberFormat(opts.locale, formatOptions);
let formatted = formatter.format(absAmount);
const symbol = opts.customSymbol ?? currencyInfo.symbol;
if (opts.showCode) {
if (opts.symbolPosition === "end") {
formatted = `${formatted} ${upperCurrency}`;
} else {
formatted = `${upperCurrency} ${formatted}`;
}
} else if (opts.showSymbol !== false) {
if (opts.symbolPosition === "end") {
formatted = `${formatted}${symbol}`;
} else {
formatted = `${symbol}${formatted}`;
}
}
if (isNegative) {
formatted = `-${formatted}`;
}
return {
formatted,
amount,
currency: upperCurrency,
locale: opts.locale,
isCompact: false
};
} catch (error) {
throw new MonieUtilsError(
`Failed to format currency: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
function formatMoney(amount, currency, locale) {
const options = locale ? { locale } : {};
const result = formatCurrency(amount, currency, options);
return result.formatted;
}
function formatCents(cents, currency, options = {}) {
if (!isValidAmount(cents)) {
throw new MonieUtilsError(
`Invalid cents amount: ${cents}. Amount must be a finite number.`
);
}
const upperCurrency = currency.toUpperCase();
if (!isValidCurrency(upperCurrency)) {
throw new MonieUtilsError(
`Unsupported currency: ${currency}. Check the currency code.`
);
}
const currencyInfo = CURRENCY_INFO[upperCurrency];
const divisor = Math.pow(10, currencyInfo.decimalPlaces);
const amount = cents / divisor;
return formatCurrency(amount, upperCurrency, options);
}
function formatCompactCurrency(amount, currency, options = {}) {
return formatCurrency(amount, currency, { ...options, compact: true });
}
// src/formatPercentage/constants.ts
var DEFAULT_PERCENTAGE_OPTIONS = {
precision: 2,
locale: "en-US",
useGrouping: true,
spaceBefore: false
};
// src/formatPercentage/formatPercentage.ts
function isValidDecimal(decimal) {
return typeof decimal === "number" && Number.isFinite(decimal);
}
function formatPercentage(decimal, options = {}) {
if (!isValidDecimal(decimal)) {
throw new MonieUtilsError(
`Invalid decimal: ${decimal}. Decimal must be a finite number.`
);
}
const opts = { ...DEFAULT_PERCENTAGE_OPTIONS, ...options };
const percentage = decimal * 100;
try {
const formatOptions = {
style: "decimal",
minimumFractionDigits: opts.precision,
maximumFractionDigits: opts.precision,
useGrouping: opts.useGrouping
};
const formatter = new Intl.NumberFormat(opts.locale, formatOptions);
const formattedNumber = formatter.format(percentage);
const suffix = opts.suffix ?? "%";
const formatted = opts.spaceBefore ? `${formattedNumber} ${suffix}` : `${formattedNumber}${suffix}`;
return {
formatted,
decimal,
percentage,
precision: opts.precision,
locale: opts.locale
};
} catch (error) {
throw new MonieUtilsError(
`Failed to format percentage: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
// src/localization/constants.ts
var LOCALE_CURRENCY_MAP = {
"en-US": {
currency: "USD",
symbol: "$",
name: "US Dollar",
decimalPlaces: 2,
locale: "en-US"
},
"en-GB": {
currency: "GBP",
symbol: "\xA3",
name: "British Pound",
decimalPlaces: 2,
locale: "en-GB"
},
"de-DE": {
currency: "EUR",
symbol: "\u20AC",
name: "Euro",
decimalPlaces: 2,
locale: "de-DE"
},
"fr-FR": {
currency: "EUR",
symbol: "\u20AC",
name: "Euro",
decimalPlaces: 2,
locale: "fr-FR"
},
"ja-JP": {
currency: "JPY",
symbol: "\xA5",
name: "Japanese Yen",
decimalPlaces: 0,
locale: "ja-JP"
},
"zh-CN": {
currency: "CNY",
symbol: "\xA5",
name: "Chinese Yuan",
decimalPlaces: 2,
locale: "zh-CN"
},
"es-ES": {
currency: "EUR",
symbol: "\u20AC",
name: "Euro",
decimalPlaces: 2,
locale: "es-ES"
},
"pt-BR": {
currency: "BRL",
symbol: "R$",
name: "Brazilian Real",
decimalPlaces: 2,
locale: "pt-BR"
},
"en-NG": {
currency: "NGN",
symbol: "\u20A6",
name: "Nigerian Naira",
decimalPlaces: 2,
locale: "en-NG"
}
};
// src/localization/localization.ts
function isValidLocale(locale) {
try {
new Intl.NumberFormat(locale);
return true;
} catch {
return false;
}
}
function formatCurrencyByLocale(amount, currency, locale, options = {}) {
if (typeof amount !== "number" || !Number.isFinite(amount)) {
throw new MonieUtilsError(
`Invalid amount: ${amount}. Amount must be a finite number.`
);
}
if (!currency || typeof currency !== "string") {
throw new MonieUtilsError(
`Invalid currency: ${currency}. Currency must be a valid string.`
);
}
if (!isValidLocale(locale)) {
throw new MonieUtilsError(
`Invalid locale: ${locale}. Locale must be a valid locale string.`
);
}
const formatOptions = {
locale
};
if (options.useGrouping !== void 0)
formatOptions.useGrouping = options.useGrouping;
if (options.customSymbol !== void 0)
formatOptions.customSymbol = options.customSymbol;
if (options.showCode !== void 0) formatOptions.showCode = options.showCode;
if (options.symbolPosition !== void 0)
formatOptions.symbolPosition = options.symbolPosition;
const result = formatCurrency(amount, currency, formatOptions);
return result.formatted;
}
function getLocaleCurrencyInfo(locale) {
if (!locale || typeof locale !== "string") {
throw new MonieUtilsError(
`Invalid locale: ${locale}. Locale must be a valid string.`
);
}
const currencyInfo = LOCALE_CURRENCY_MAP[locale];
if (!currencyInfo) {
throw new MonieUtilsError(
`Unsupported locale: ${locale}. Check the locale code.`
);
}
return currencyInfo;
}
function formatWithGrouping(amount, locale = "en-US") {
if (typeof amount !== "number" || !Number.isFinite(amount)) {
throw new MonieUtilsError(
`Invalid amount: ${amount}. Amount must be a finite number.`
);
}
if (!isValidLocale(locale)) {
throw new MonieUtilsError(
`Invalid locale: ${locale}. Locale must be a valid locale string.`
);
}
try {
const formatter = new Intl.NumberFormat(locale, {
style: "decimal",
useGrouping: true
});
const formatted = formatter.format(amount);
return {
formatted,
amount,
locale,
hasGrouping: true
};
} catch (error) {
throw new MonieUtilsError(
`Failed to format with grouping: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
function formatDecimalPlaces(amount, decimalPlaces) {
if (typeof amount !== "number" || !Number.isFinite(amount)) {
throw new MonieUtilsError(
`Invalid amount: ${amount}. Amount must be a finite number.`
);
}
if (typeof decimalPlaces !== "number" || decimalPlaces < 0 || !Number.isInteger(decimalPlaces)) {
throw new MonieUtilsError(
`Invalid decimal places: ${decimalPlaces}. Must be a non-negative integer.`
);
}
try {
const formatted = amount.toFixed(decimalPlaces);
return {
formatted,
amount,
decimalPlaces
};
} catch (error) {
throw new MonieUtilsError(
`Failed to format decimal places: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
// src/validation/validation.ts
function isValidAmount2(amount) {
return typeof amount === "number" && Number.isFinite(amount);
}
function isValidCurrency2(currencyCode) {
if (typeof currencyCode !== "string") return false;
return currencyCode.toUpperCase() in CURRENCY_INFO;
}
function validateMoneyObject(moneyObject) {
if (!moneyObject || typeof moneyObject !== "object") return false;
const obj = moneyObject;
return isValidAmount2(obj.amount) && isValidCurrency2(obj.currency);
}
function isPositiveAmount(amount) {
if (!isValidAmount2(amount)) return false;
return amount > 0;
}
function isWithinRange(amount, min, max, options = {}) {
if (!isValidAmount2(amount) || !isValidAmount2(min) || !isValidAmount2(max))
return false;
const { inclusive = true } = options;
if (inclusive) {
return amount >= min && amount <= max;
} else {
return amount > min && amount < max;
}
}
function parseAmount(amountString) {
if (typeof amountString !== "string") {
return {
amount: NaN,
isValid: false,
originalString: String(amountString)
};
}
const cleaned = amountString.replace(/[$£€¥₦₹]/g, "").replace(/[,\s]/g, "").replace(/[()]/g, "").trim();
const parsed = parseFloat(cleaned);
return {
amount: parsed,
isValid: isValidAmount2(parsed),
originalString: amountString
};
}
function parseCurrencyString(currencyString) {
if (typeof currencyString !== "string") {
return {
amount: NaN,
currency: "",
isValid: false,
originalString: String(currencyString)
};
}
const currencyMatch = currencyString.match(/\b([A-Z]{3})\b/);
const currency = currencyMatch ? currencyMatch[1] : "";
const amountResult = parseAmount(currencyString);
return {
amount: amountResult.amount,
currency,
isValid: amountResult.isValid && isValidCurrency2(currency),
originalString: currencyString
};
}
function normalizeAmount(amount, options = {}) {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError(
`Invalid amount: ${amount}. Amount must be a finite number.`
);
}
const { decimalPlaces = 2, roundingMode = "round" } = options;
if (typeof decimalPlaces !== "number" || decimalPlaces < 0 || !Number.isInteger(decimalPlaces)) {
throw new MonieUtilsError(
`Invalid decimal places: ${decimalPlaces}. Must be a non-negative integer.`
);
}
const multiplier = Math.pow(10, decimalPlaces);
switch (roundingMode) {
case "floor":
return Math.floor(amount * multiplier) / multiplier;
case "ceil":
return Math.ceil(amount * multiplier) / multiplier;
case "round":
default:
return Math.round(amount * multiplier) / multiplier;
}
}
function parseFormattedCurrency(formattedString, locale = "en-US") {
if (typeof formattedString !== "string") {
throw new MonieUtilsError(
`Invalid formatted string: ${formattedString}. Must be a string.`
);
}
try {
let cleaned = formattedString.replace(/[$£€¥₦₹]/g, "").replace(/[A-Z]{3}/g, "").trim();
if (locale.includes("de") || locale.includes("fr") || locale.includes("es")) {
const parts = cleaned.split(",");
if (parts.length === 2) {
const integerPart = parts[0].replace(/\./g, "");
const decimalPart = parts[1];
cleaned = `${integerPart}.${decimalPart}`;
}
} else {
cleaned = cleaned.replace(/,/g, "");
}
const parsed = parseFloat(cleaned);
if (!isValidAmount2(parsed)) {
throw new MonieUtilsError(
`Failed to parse formatted currency: ${formattedString}`
);
}
return parsed;
} catch (error) {
throw new MonieUtilsError(
`Failed to parse formatted currency: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
// src/conversion/conversion.ts
var DEFAULT_RATES = {
USD: { EUR: 0.85, GBP: 0.73, JPY: 110, NGN: 460 },
EUR: { USD: 1.18, GBP: 0.86, JPY: 129, NGN: 542 },
GBP: { USD: 1.37, EUR: 1.16, JPY: 150, NGN: 630 },
JPY: { USD: 91e-4, EUR: 77e-4, GBP: 67e-4, NGN: 4.18 },
NGN: { USD: 22e-4, EUR: 18e-4, GBP: 16e-4, JPY: 0.24 }
};
function getExchangeRate(from, to, customRate) {
if (customRate !== void 0) {
return customRate;
}
if (from === to) {
return 1;
}
const rate = DEFAULT_RATES[from]?.[to];
if (!rate) {
throw new MonieUtilsError(
`Exchange rate not available for ${from} to ${to}`
);
}
return rate;
}
function convertCurrency(amount, fromCurrency, toCurrency, rate) {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError(
`Invalid amount: ${amount}. Amount must be a finite number.`
);
}
if (!isValidCurrency2(fromCurrency)) {
throw new MonieUtilsError(`Invalid source currency: ${fromCurrency}`);
}
if (!isValidCurrency2(toCurrency)) {
throw new MonieUtilsError(`Invalid target currency: ${toCurrency}`);
}
const exchangeRate = getExchangeRate(
fromCurrency.toUpperCase(),
toCurrency.toUpperCase(),
rate
);
const convertedAmount = amount * exchangeRate;
return {
originalAmount: amount,
convertedAmount,
fromCurrency: fromCurrency.toUpperCase(),
toCurrency: toCurrency.toUpperCase(),
exchangeRate,
timestamp: /* @__PURE__ */ new Date()
};
}
function convertWithFee(amount, rate, feePercentage) {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError(
`Invalid amount: ${amount}. Amount must be a finite number.`
);
}
if (!isValidAmount2(rate) || rate <= 0) {
throw new MonieUtilsError(
`Invalid exchange rate: ${rate}. Rate must be a positive number.`
);
}
if (!isValidAmount2(feePercentage) || feePercentage < 0 || feePercentage > 100) {
throw new MonieUtilsError(
`Invalid fee percentage: ${feePercentage}. Must be between 0 and 100.`
);
}
const feeAmount = amount * feePercentage / 100;
const amountAfterFee = amount - feeAmount;
const convertedAmount = amountAfterFee * rate;
return {
originalAmount: amount,
convertedAmount,
fromCurrency: "",
// Not specified in this function
toCurrency: "",
// Not specified in this function
exchangeRate: rate,
timestamp: /* @__PURE__ */ new Date(),
feeAmount,
feePercentage,
amountAfterFee
};
}
function bulkConvert(amounts, fromCurrency, toCurrency, rate) {
if (!Array.isArray(amounts) || amounts.length === 0) {
throw new MonieUtilsError("Amounts must be a non-empty array");
}
if (!isValidCurrency2(fromCurrency)) {
throw new MonieUtilsError(`Invalid source currency: ${fromCurrency}`);
}
if (!isValidCurrency2(toCurrency)) {
throw new MonieUtilsError(`Invalid target currency: ${toCurrency}`);
}
for (const amount of amounts) {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError(`Invalid amount in array: ${amount}`);
}
}
const exchangeRate = getExchangeRate(
fromCurrency.toUpperCase(),
toCurrency.toUpperCase(),
rate
);
const conversions = amounts.map((amount) => ({
originalAmount: amount,
convertedAmount: amount * exchangeRate,
fromCurrency: fromCurrency.toUpperCase(),
toCurrency: toCurrency.toUpperCase(),
exchangeRate,
timestamp: /* @__PURE__ */ new Date()
}));
const totalOriginalAmount = amounts.reduce((sum, amount) => sum + amount, 0);
const totalConvertedAmount = totalOriginalAmount * exchangeRate;
return {
conversions,
totalOriginalAmount,
totalConvertedAmount,
exchangeRate,
fromCurrency: fromCurrency.toUpperCase(),
toCurrency: toCurrency.toUpperCase()
};
}
// src/arithmetic/arithmetic.ts
function roundMoney(amount, precision = 2, mode = "round") {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError(
`Invalid amount: ${amount}. Amount must be a finite number.`
);
}
if (typeof precision !== "number" || precision < 0 || !Number.isInteger(precision)) {
throw new MonieUtilsError(
`Invalid precision: ${precision}. Must be a non-negative integer.`
);
}
const multiplier = Math.pow(10, precision);
switch (mode) {
case "floor":
return Math.floor(amount * multiplier) / multiplier;
case "ceil":
return Math.ceil(amount * multiplier) / multiplier;
case "bankers":
const scaled = amount * multiplier;
const rounded = Math.round(scaled);
if (Math.abs(scaled - Math.floor(scaled) - 0.5) < Number.EPSILON) {
return (rounded % 2 === 0 ? rounded : rounded - Math.sign(rounded)) / multiplier;
}
return rounded / multiplier;
case "round":
default:
return Math.round(amount * multiplier) / multiplier;
}
}
function addMoney(amount1, amount2, currency) {
if (!isValidAmount2(amount1)) {
throw new MonieUtilsError(
`Invalid first amount: ${amount1}. Amount must be a finite number.`
);
}
if (!isValidAmount2(amount2)) {
throw new MonieUtilsError(
`Invalid second amount: ${amount2}. Amount must be a finite number.`
);
}
if (currency && !isValidCurrency2(currency)) {
throw new MonieUtilsError(`Invalid currency: ${currency}`);
}
return roundMoney(amount1 + amount2);
}
function subtractMoney(amount1, amount2, currency) {
if (!isValidAmount2(amount1)) {
throw new MonieUtilsError(
`Invalid first amount: ${amount1}. Amount must be a finite number.`
);
}
if (!isValidAmount2(amount2)) {
throw new MonieUtilsError(
`Invalid second amount: ${amount2}. Amount must be a finite number.`
);
}
if (currency && !isValidCurrency2(currency)) {
throw new MonieUtilsError(`Invalid currency: ${currency}`);
}
return roundMoney(amount1 - amount2);
}
function multiplyMoney(amount, multiplier) {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError(
`Invalid amount: ${amount}. Amount must be a finite number.`
);
}
if (!isValidAmount2(multiplier)) {
throw new MonieUtilsError(
`Invalid multiplier: ${multiplier}. Multiplier must be a finite number.`
);
}
return roundMoney(amount * multiplier);
}
function divideMoney(amount, divisor) {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError(
`Invalid amount: ${amount}. Amount must be a finite number.`
);
}
if (!isValidAmount2(divisor)) {
throw new MonieUtilsError(
`Invalid divisor: ${divisor}. Divisor must be a finite number.`
);
}
if (divisor === 0) {
throw new MonieUtilsError("Cannot divide by zero");
}
return roundMoney(amount / divisor);
}
function calculateTip(amount, percentage) {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError(
`Invalid amount: ${amount}. Amount must be a finite number.`
);
}
if (!isValidAmount2(percentage) || percentage < 0) {
throw new MonieUtilsError(
`Invalid percentage: ${percentage}. Percentage must be a non-negative number.`
);
}
return roundMoney(amount * percentage / 100);
}
function calculateTax(amount, taxRate) {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError(
`Invalid amount: ${amount}. Amount must be a finite number.`
);
}
if (!isValidAmount2(taxRate) || taxRate < 0) {
throw new MonieUtilsError(
`Invalid tax rate: ${taxRate}. Tax rate must be a non-negative number.`
);
}
return roundMoney(amount * taxRate / 100);
}
function calculateDiscount(amount, discountRate) {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError(
`Invalid amount: ${amount}. Amount must be a finite number.`
);
}
if (!isValidAmount2(discountRate) || discountRate < 0 || discountRate > 100) {
throw new MonieUtilsError(
`Invalid discount rate: ${discountRate}. Discount rate must be between 0 and 100.`
);
}
return roundMoney(amount * discountRate / 100);
}
function calculateSimpleInterest(principal, rate, time) {
if (!isValidAmount2(principal) || principal < 0) {
throw new MonieUtilsError(
`Invalid principal: ${principal}. Principal must be a non-negative number.`
);
}
if (!isValidAmount2(rate) || rate < 0) {
throw new MonieUtilsError(
`Invalid rate: ${rate}. Rate must be a non-negative number.`
);
}
if (!isValidAmount2(time) || time < 0) {
throw new MonieUtilsError(
`Invalid time: ${time}. Time must be a non-negative number.`
);
}
const interest = roundMoney(principal * rate * time / 100);
const finalAmount = roundMoney(principal + interest);
return {
principal,
rate,
time,
interest,
finalAmount,
type: "simple"
};
}
function calculateCompoundInterest(principal, rate, time, frequency = 1) {
if (!isValidAmount2(principal) || principal < 0) {
throw new MonieUtilsError(
`Invalid principal: ${principal}. Principal must be a non-negative number.`
);
}
if (!isValidAmount2(rate) || rate < 0) {
throw new MonieUtilsError(
`Invalid rate: ${rate}. Rate must be a non-negative number.`
);
}
if (!isValidAmount2(time) || time < 0) {
throw new MonieUtilsError(
`Invalid time: ${time}. Time must be a non-negative number.`
);
}
if (!isValidAmount2(frequency) || frequency <= 0 || !Number.isInteger(frequency)) {
throw new MonieUtilsError(
`Invalid frequency: ${frequency}. Frequency must be a positive integer.`
);
}
const finalAmount = principal * Math.pow(1 + rate / 100 / frequency, frequency * time);
const roundedFinal = roundMoney(finalAmount);
const interest = roundMoney(roundedFinal - principal);
return {
principal,
rate,
time,
interest,
finalAmount: roundedFinal,
type: "compound",
frequency
};
}
function splitAmount(totalAmount, numberOfParts) {
if (!isValidAmount2(totalAmount)) {
throw new MonieUtilsError(
`Invalid total amount: ${totalAmount}. Amount must be a finite number.`
);
}
if (!Number.isInteger(numberOfParts) || numberOfParts <= 0) {
throw new MonieUtilsError(
`Invalid number of parts: ${numberOfParts}. Must be a positive integer.`
);
}
const baseAmount = Math.floor(totalAmount * 100 / numberOfParts) / 100;
const remainder = roundMoney(totalAmount - baseAmount * numberOfParts);
const amounts = new Array(numberOfParts).fill(baseAmount);
let remainderCents = Math.round(remainder * 100);
for (let i = amounts.length - 1; i >= 0 && remainderCents > 0; i--) {
amounts[i] = roundMoney(amounts[i] + 0.01);
remainderCents--;
}
return {
amounts,
totalAmount,
numberOfParts,
remainder: remainderCents / 100
};
}
function distributeProportionally(totalAmount, ratios) {
if (!isValidAmount2(totalAmount)) {
throw new MonieUtilsError(
`Invalid total amount: ${totalAmount}. Amount must be a finite number.`
);
}
if (!Array.isArray(ratios) || ratios.length === 0) {
throw new MonieUtilsError("Ratios must be a non-empty array");
}
for (const ratio of ratios) {
if (!isValidAmount2(ratio) || ratio < 0) {
throw new MonieUtilsError(
`Invalid ratio: ${ratio}. All ratios must be non-negative numbers.`
);
}
}
const totalRatio = ratios.reduce((sum, ratio) => sum + ratio, 0);
if (totalRatio === 0) {
throw new MonieUtilsError("Sum of ratios cannot be zero");
}
const amounts = ratios.map(
(ratio) => roundMoney(totalAmount * ratio / totalRatio)
);
const distributedTotal = amounts.reduce((sum, amount) => sum + amount, 0);
const remainder = roundMoney(totalAmount - distributedTotal);
return {
amounts,
totalAmount,
ratios,
remainder
};
}
function calculatePercentageOfTotal(amount, total) {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError(
`Invalid amount: ${amount}. Amount must be a finite number.`
);
}
if (!isValidAmount2(total)) {
throw new MonieUtilsError(
`Invalid total: ${total}. Total must be a finite number.`
);
}
if (total === 0) {
throw new MonieUtilsError("Total cannot be zero");
}
const percentage = roundMoney(amount / total * 100);
return {
percentage,
amount,
total
};
}
// src/loans/loans.ts
function calculateMonthlyPayment(principal, rate, termMonths) {
if (!isValidAmount2(principal) || principal <= 0) {
throw new MonieUtilsError(
`Invalid principal: ${principal}. Principal must be a positive number.`
);
}
if (!isValidAmount2(rate) || rate < 0) {
throw new MonieUtilsError(
`Invalid rate: ${rate}. Rate must be a non-negative number.`
);
}
if (!Number.isInteger(termMonths) || termMonths <= 0) {
throw new MonieUtilsError(
`Invalid term: ${termMonths}. Term must be a positive integer.`
);
}
if (rate === 0) {
const monthlyPayment2 = roundMoney(principal / termMonths);
return {
monthlyPayment: monthlyPayment2,
principal,
rate,
termMonths,
totalAmount: roundMoney(monthlyPayment2 * termMonths),
totalInterest: 0
};
}
const monthlyRate = rate / 100 / 12;
const monthlyPayment = principal * (monthlyRate * Math.pow(1 + monthlyRate, termMonths)) / (Math.pow(1 + monthlyRate, termMonths) - 1);
const roundedPayment = roundMoney(monthlyPayment);
const totalAmount = roundMoney(roundedPayment * termMonths);
const totalInterest = roundMoney(totalAmount - principal);
return {
monthlyPayment: roundedPayment,
principal,
rate,
termMonths,
totalAmount,
totalInterest
};
}
function calculateLoanBalance(principal, rate, termMonths, paymentsMade) {
if (!isValidAmount2(principal) || principal <= 0) {
throw new MonieUtilsError(
`Invalid principal: ${principal}. Principal must be a positive number.`
);
}
if (!isValidAmount2(rate) || rate < 0) {
throw new MonieUtilsError(
`Invalid rate: ${rate}. Rate must be a non-negative number.`
);
}
if (!Number.isInteger(termMonths) || termMonths <= 0) {
throw new MonieUtilsError(
`Invalid term: ${termMonths}. Term must be a positive integer.`
);
}
if (!Number.isInteger(paymentsMade) || paymentsMade < 0 || paymentsMade > termMonths) {
throw new MonieUtilsError(
`Invalid payments made: ${paymentsMade}. Must be between 0 and ${termMonths}.`
);
}
if (paymentsMade === 0) {
return {
remainingBalance: principal,
principalPaid: 0,
interestPaid: 0,
paymentsMade: 0,
paymentsRemaining: termMonths
};
}
const { monthlyPayment } = calculateMonthlyPayment(
principal,
rate,
termMonths
);
if (rate === 0) {
const principalPaid2 = roundMoney(monthlyPayment * paymentsMade);
const remainingBalance2 = roundMoney(principal - principalPaid2);
return {
remainingBalance: Math.max(0, remainingBalance2),
principalPaid: principalPaid2,
interestPaid: 0,
paymentsMade,
paymentsRemaining: termMonths - paymentsMade
};
}
const monthlyRate = rate / 100 / 12;
const remainingBalance = principal * (Math.pow(1 + monthlyRate, termMonths) - Math.pow(1 + monthlyRate, paymentsMade)) / (Math.pow(1 + monthlyRate, termMonths) - 1);
const roundedBalance = roundMoney(Math.max(0, remainingBalance));
const principalPaid = roundMoney(principal - roundedBalance);
const totalPaid = roundMoney(monthlyPayment * paymentsMade);
const interestPaid = roundMoney(totalPaid - principalPaid);
return {
remainingBalance: roundedBalance,
principalPaid,
interestPaid,
paymentsMade,
paymentsRemaining: termMonths - paymentsMade
};
}
function calculateTotalInterest(principal, rate, termMonths) {
const loanResult = calculateMonthlyPayment(principal, rate, termMonths);
return loanResult.totalInterest;
}
function generateAmortizationSchedule(principal, rate, termMonths) {
const summary = calculateMonthlyPayment(principal, rate, termMonths);
const payments = [];
let remainingBalance = principal;
const monthlyRate = rate / 100 / 12;
for (let i = 1; i <= termMonths; i++) {
const interestAmount = rate === 0 ? 0 : roundMoney(remainingBalance * monthlyRate);
const principalAmount = roundMoney(summary.monthlyPayment - interestAmount);
const actualPrincipalAmount = i === termMonths ? remainingBalance : principalAmount;
const actualPaymentAmount = roundMoney(
actualPrincipalAmount + interestAmount
);
remainingBalance = roundMoney(
Math.max(0, remainingBalance - actualPrincipalAmount)
);
payments.push({
paymentNumber: i,
paymentAmount: actualPaymentAmount,
principalAmount: actualPrincipalAmount,
interestAmount,
remainingBalance
});
}
return {
payments,
summary
};
}
function calculateCreditUtilization(usedCredit, totalCredit) {
if (!isValidAmount2(usedCredit) || usedCredit < 0) {
throw new MonieUtilsError(
`Invalid used credit: ${usedCredit}. Must be a non-negative number.`
);
}
if (!isValidAmount2(totalCredit) || totalCredit <= 0) {
throw new MonieUtilsError(
`Invalid total credit: ${totalCredit}. Must be a positive number.`
);
}
if (usedCredit > totalCredit) {
throw new MonieUtilsError(
`Used credit (${usedCredit}) cannot exceed total credit (${totalCredit}).`
);
}
const utilizationPercentage = roundMoney(usedCredit / totalCredit * 100);
const availableCredit = roundMoney(totalCredit - usedCredit);
let riskLevel;
if (utilizationPercentage <= 10) {
riskLevel = "low";
} else if (utilizationPercentage <= 30) {
riskLevel = "medium";
} else {
riskLevel = "high";
}
return {
utilizationPercentage,
usedCredit,
totalCredit,
availableCredit,
riskLevel
};
}
function calculateMinimumPayment(balance, rate, minimumRate) {
if (!isValidAmount2(balance) || balance < 0) {
throw new MonieUtilsError(
`Invalid balance: ${balance}. Balance must be a non-negative number.`
);
}
if (!isValidAmount2(rate) || rate < 0) {
throw new MonieUtilsError(
`Invalid rate: ${rate}. Rate must be a non-negative number.`
);
}
if (!isValidAmount2(minimumRate) || minimumRate <= 0 || minimumRate > 100) {
throw new MonieUtilsError(
`Invalid minimum rate: ${minimumRate}. Must be between 0 and 100.`
);
}
const monthlyInterestRate = rate / 100 / 12;
const interestPortion = roundMoney(balance * monthlyInterestRate);
const minimumBasedOnRate = roundMoney(balance * minimumRate / 100);
const minimumPayment = Math.max(minimumBasedOnRate, interestPortion + 15);
const principalPortion = roundMoney(minimumPayment - interestPortion);
return {
minimumPayment: roundMoney(minimumPayment),
balance,
interestRate: rate,
minimumRate,
interestPortion,
principalPortion
};
}
function calculatePayoffTime(balance, payment, rate) {
if (!isValidAmount2(balance) || balance <= 0) {
throw new MonieUtilsError(
`Invalid balance: ${balance}. Balance must be a positive number.`
);
}
if (!isValidAmount2(payment) || payment <= 0) {
throw new MonieUtilsError(
`Invalid payment: ${payment}. Payment must be a positive number.`
);
}
if (!isValidAmount2(rate) || rate < 0) {
throw new MonieUtilsError(
`Invalid rate: ${rate}. Rate must be a non-negative number.`
);
}
const monthlyRate = rate / 100 / 12;
const monthlyInterest = balance * monthlyRate;
if (payment <= monthlyInterest) {
throw new MonieUtilsError(
`Payment (${payment}) must be greater than monthly interest (${roundMoney(monthlyInterest)}).`
);
}
if (rate === 0) {
const monthsToPayoff2 = Math.ceil(balance / payment);
const totalAmountPaid2 = roundMoney(payment * monthsToPayoff2);
return {
monthsToPayoff: monthsToPayoff2,
yearsToPayoff: roundMoney(monthsToPayoff2 / 12),
totalInterestPaid: 0,
totalAmountPaid: totalAmountPaid2,
monthlyPayment: payment
};
}
const monthsToPayoff = Math.ceil(
-Math.log(1 - balance * monthlyRate / payment) / Math.log(1 + monthlyRate)
);
const totalAmountPaid = roundMoney(payment * monthsToPayoff);
const totalInterestPaid = roundMoney(totalAmountPaid - balance);
return {
monthsToPayoff,
yearsToPayoff: roundMoney(monthsToPayoff / 12),
totalInterestPaid,
totalAmountPaid,
monthlyPayment: payment
};
}
// src/investment/investment.ts
function calculateROI(initialInvestment, finalValue) {
if (!isValidAmount2(initialInvestment) || !isValidAmount2(finalValue)) {
throw new MonieUtilsError(
"Initial investment and final value must be valid numbers"
);
}
if (initialInvestment <= 0) {
throw new MonieUtilsError("Initial investment must be greater than zero");
}
const gainLoss = finalValue - initialInvestment;
const roi = gainLoss / initialInvestment;
const roiPercentage = roi * 100;
return {
roi: roundMoney(roi, 6),
roiPercentage: roundMoney(roiPercentage, 2),
gainLoss: roundMoney(gainLoss, 2),
isGain: gainLoss >= 0
};
}
function calculateAnnualizedReturn(initialValue, finalValue, years) {
if (!isValidAmount2(initialValue) || !isValidAmount2(finalValue) || !isValidAmount2(years)) {
throw new MonieUtilsError(
"Initial value, final value, and years must be valid numbers"
);
}
if (initialValue <= 0) {
throw new MonieUtilsError("Initial value must be greater than zero");
}
if (years <= 0) {
throw new MonieUtilsError("Years must be greater than zero");
}
const totalReturn = (finalValue - initialValue) / initialValue;
const annualizedReturn = Math.pow(finalValue / initialValue, 1 / years) - 1;
return {
annualizedReturn: roundMoney(annualizedReturn, 6),
annualizedReturnPercentage: roundMoney(annualizedReturn * 100, 2),
totalReturn: roundMoney(totalReturn, 6),
totalReturnPercentage: roundMoney(totalReturn * 100, 2)
};
}
function calculateDividendYield(dividendPerShare, pricePerShare) {
if (!isValidAmount2(dividendPerShare) || !isValidAmount2(pricePerShare)) {
throw new MonieUtilsError(
"Dividend per share and price per share must be valid numbers"
);
}
if (pricePerShare <= 0) {
throw new MonieUtilsError("Price per share must be greater than zero");
}
if (dividendPerShare < 0) {
throw new MonieUtilsError("Dividend per share cannot be negative");
}
const yield_ = dividendPerShare / pricePerShare;
const yieldPercentage = yield_ * 100;
return {
yield: roundMoney(yield_, 6),
yieldPercentage: roundMoney(yieldPercentage, 2),
dividendPerShare: roundMoney(dividendPerShare, 2),
sharePrice: roundMoney(pricePerShare, 2)
};
}
function calculateFutureValue(presentValue, rate, periods) {
if (!isValidAmount2(presentValue) || !isValidAmount2(rate) || !isValidAmount2(periods)) {
throw new MonieUtilsError(
"Present value, rate, and periods must be valid numbers"
);
}
if (presentValue <= 0) {
throw new MonieUtilsError("Present value must be greater than zero");
}
if (rate < 0) {
throw new MonieUtilsError("Interest rate cannot be negative");
}
if (periods < 0 || !Number.isInteger(periods)) {
throw new MonieUtilsError("Periods must be a non-negative integer");
}
const futureValue = presentValue * Math.pow(1 + rate, periods);
const totalInterest = futureValue - presentValue;
return {
futureValue: roundMoney(futureValue, 2),
presentValue: roundMoney(presentValue, 2),
totalInterest: roundMoney(totalInterest, 2),
effectiveRate: roundMoney(rate, 6),
periods
};
}
// src/subscription/subscription.ts
function calculateSubscriptionValue(monthlyAmount, months, currency = "USD") {
if (!isValidAmount2(monthlyAmount) || !isValidAmount2(months)) {
throw new MonieUtilsError(
"Monthly amount and months must be valid numbers"
);
}
if (monthlyAmount < 0) {
throw new MonieUtilsError("Monthly amount cannot be negative");
}
if (months <= 0 || !Number.isInteger(months)) {
throw new MonieUtilsError("Months must be a positive integer");
}
const totalCost = monthlyAmount * months;
return {
totalCost: roundMoney(totalCost, 2),
monthlyAmount: roundMoney(monthlyAmount, 2),
months,
currency,
averageMonthlyCost: roundMoney(monthlyAmount, 2)
};
}
function compareSubscriptionPlans(plans) {
if (!Array.isArray(plans) || plans.length === 0) {
throw new MonieUtilsError("Plans must be a non-empty array");
}
if (plans.length === 1) {
throw new MonieUtilsError("At least two plans are required for comparison");
}
for (const plan of plans) {
if (!plan.id || !plan.name || !isValidAmount2(plan.monthlyAmount)) {
throw new MonieUtilsError(
"Each plan must have valid id, name, and monthlyAmount"
);
}
}
const planAnalyses = plans.map((plan) => {
const annualDiscount = plan.annualDiscount || 0;
const effectiveMonthlyRate = plan.monthlyAmount * (1 - annualDiscount);
const annualCost = effectiveMonthlyRate * 12;
const costPerUser = plan.maxUsers ? annualCost / plan.maxUsers : void 0;
const featureCount = plan.features?.length || 0;
const baseScore = Math.max(0, 100 - effectiveMonthlyRate * 2);
const featureBonus = Math.min(20, featureCount * 2);
const valueScore = Math.min(100, baseScore + featureBonus);
const analysis = {
plan,
effectiveMonthlyRate: roundMoney(effectiveMonthlyRate, 2),
annualCost: roundMoney(annualCost, 2),
valueScore: roundMoney(valueScore, 1)
};
if (costPerUser !== void 0) {
analysis.costPerUser = roundMoney(costPerUser, 2);
}
return analysis;
});
const recommendedPlan = planAnalyses.reduce(
(best, current) => current.valueScore > best.valueScore ? current : best
);
const highestCost = Math.max(...planAnalyses.map((p) => p.annualCost));
const lowestCost = Math.min(...planAnalyses.map((p) => p.annualCost));
const maxSavings = roundMoney(highestCost - lowestCost, 2);
return {
plans: planAnalyses,
recommendedPlan,
maxSavings
};
}
function calculateProrationAmount(amount, daysUsed, totalDays) {
if (!isValidAmount2(amount) || !isValidAmount2(daysUsed) || !isValidAmount2(totalDays)) {
throw new MonieUtilsError(
"Amount, days used, and total days must be valid numbers"
);
}
if (amount < 0) {
throw new MonieUtilsError("Amount cannot be negative");
}
if (daysUsed < 0 || totalDays <= 0) {
throw new MonieUtilsError(
"Days used cannot be negative and total days must be positive"
);
}
if (daysUsed > totalDays) {
throw new MonieUtilsError("Days used cannot exceed total days");
}
const usagePercentage = daysUsed / totalDays * 100;
const proratedAmount = amount * daysUsed / totalDays;
return {
proratedAmount: roundMoney(proratedAmount, 2),
fullAmount: roundMoney(amount, 2),
daysUsed: Math.round(daysUsed),
totalDays: Math.round(totalDays),
usagePercentage: roundMoney(usagePercentage, 2)
};
}
function calculateUpgradeCredit(oldPlan, newPlan, daysRemaining) {
if (!oldPlan || !newPlan) {
throw new MonieUtilsError("Both old and new plans must be provided");
}
if (!isValidAmount2(oldPlan.monthlyAmount) || !isValidAmount2(newPlan.monthlyAmount)) {
throw new MonieUtilsError("Both plans must have valid monthly amounts");
}
if (!isValidAmount2(daysRemaining) || daysRemaining < 0 || daysRemaining > 31) {
throw new MonieUtilsError("Days remaining must be between 0 and 31");
}
const assumedDaysInMonth = 30;
const effectiveDaysRemaining = Math.min(daysRemaining, assumedDaysInMonth);
const oldPlanRemainingValue = oldPlan.monthlyAmount * effectiveDaysRemaining / assumedDaysInMonth;
const newPlanProratedCost = newPlan.monthlyAmount * effectiveDaysRemaining / assumedDaysInMonth;
const creditAmount = oldPlanRemainingValue;
const netAmountDue = newPlanProratedCost - creditAmount;
return {
creditAmount: roundMoney(creditAmount, 2),
oldPlanRemainingValue: roundMoney(oldPlanRemainingValue, 2),
newPlanProratedCost: roundMoney(newPlanProratedCost, 2),
netAmountDue: roundMoney(netAmountDue, 2),
daysRemaining: Math.round(effectiveDaysRemaining)
};
}
function calculateAnnualEquivalent(amount, frequency) {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError("Amount must be a valid number");
}
if (amount < 0) {
throw new MonieUtilsError("Amount cannot be negative");
}
const frequencyMultipliers = {
daily: 365,
weekly: 52,
"bi-weekly": 26,
monthly: 12,
quarterly: 4,
"semi-annually": 2,
annually: 1
};
const paymentsPerYear = frequencyMultipliers[frequency];
if (!paymentsPerYear) {
throw new MonieUtilsError(`Invalid frequency: ${frequency}`);
}
const annualAmount = amount * paymentsPerYear;
return {
annualAmount: roundMoney(annualAmount, 2),
originalAmount: roundMoney(amount, 2),
frequency,
paymentsPerYear
};
}
function calculateNextPaymentDate(startDate, frequency) {
if (!(startDate instanceof Date) || isNaN(startDate.getTime())) {
throw new MonieUtilsError("Start date must be a valid Date object");
}
const nextDate = new Date(startDate);
switch (frequency) {
case "daily":
nextDate.setDate(nextDate.getDate() + 1);
break;
case "weekly":
nextDate.setDate(nextDate.getDate() + 7);
break;
case "bi-weekly":
nextDate.setDate(nextDate.getDate() + 14);
break;
case "monthly":
nextDate.setMonth(nextDate.getMonth() + 1);
break;
case "quarterly":
nextDate.setMonth(nextDate.getMonth() + 3);
break;
case "semi-annually":
nextDate.setMonth(nextDate.getMonth() + 6);
break;
case "annually":
nextDate.setFullYear(nextDate.getFullYear() + 1);
break;
default:
throw new MonieUtilsError(`Invalid frequency: ${frequency}`);
}
return nextDate;
}
function calculateTotalRecurringCost(amount, frequency, duration) {
if (!isValidAmount2(amount) || !isValidAmount2(duration)) {
throw new MonieUtilsError("Amount and duration must be valid numbers");
}
if (amount < 0) {
throw new MonieUtilsError("Amount cannot be negative");
}
if (duration <= 0) {
throw new MonieUtilsError("Duration must be positive");
}
const frequencyToMonthlyMultiplier = {
daily: 30.44,
// Average days per month
weekly: 4.33,
// Average weeks per month
"bi-weekly": 2.17,
// Average bi-weeks per month
monthly: 1,
quarterly: 1 / 3,
"semi-annually": 1 / 6,
annually: 1 / 12
};
const paymentsPerMonth = frequencyToMonthlyMultiplier[frequency];
if (!paymentsPerMonth) {
throw new MonieUtilsError(`Invalid frequency: ${frequency}`);
}
const numberOfPayments = Math.round(duration * paymentsPerMonth);
const totalCost = amount * numberOfPayments;
return {
totalCost: roundMoney(totalCost, 2),
numberOfPayments,
amountPerPeriod: roundMoney(amount, 2),
frequency,
duration: roundMoney(duration, 2),
durationUnit: "months"
};
}
// src/utils/utils.ts
function roundToNearestCent(amount) {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError("Amount must be a valid number");
}
return Math.round(amount * 100) / 100;
}
function roundToBankersRounding(amount, decimalPlaces = 2, mode = "half-even") {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError("Amount must be a valid number");
}
if (!Number.isInteger(decimalPlaces) || decimalPlaces < 0) {
throw new MonieUtilsError("Decimal places must be a non-negative integer");
}
const multiplier = Math.pow(10, decimalPlaces);
const shifted = amount * multiplier;
const rounded = Math.round(shifted * 10) / 10;
const floor = Math.floor(rounded);
const remainder = rounded - floor;
if (Math.abs(remainder - 0.5) < 1e-10) {
const isEven = floor % 2 === 0;
if (mode === "half-even") {
return isEven ? floor / multiplier : (floor + 1) / multiplier;
} else {
return isEven ? (floor + 1) / multiplier : floor / multiplier;
}
}
return Math.round(shifted) / multiplier;
}
function truncateToDecimalPlaces(amount, places) {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError("Amount must be a valid number");
}
if (!Number.isInteger(places) || places < 0) {
throw new MonieUtilsError("Decimal places must be a non-negative integer");
}
const multiplier = Math.pow(10, places);
return Math.trunc(amount * multiplier) / multiplier;
}
function ceilToNearestCent(amount) {
if (!isValidAmount2(amount)) {
throw new MonieUtilsError("Amount must be a valid number");
}
return Math.ceil(amount * 100) / 100;
}
function formatThousands(number, options = {}) {
if (!isValidAmount2(number)) {
throw new MonieUtilsError("Number must be a valid number");
}
const {
separator = ",",
locale = "en-US",
includeDecimals = true,
decimalPlaces
} = options;
try {