@vielzeug/coins 1.0.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -222
- package/dist/aggregate.cjs +2 -0
- package/dist/aggregate.cjs.map +1 -0
- package/dist/aggregate.d.ts +11 -0
- package/dist/aggregate.d.ts.map +1 -0
- package/dist/aggregate.js +44 -0
- package/dist/aggregate.js.map +1 -0
- package/dist/coins.cjs +1 -1
- package/dist/coins.cjs.map +1 -1
- package/dist/coins.iife.js +1 -1
- package/dist/coins.iife.js.map +1 -1
- package/dist/coins.js +1 -1
- package/dist/coins.js.map +1 -1
- package/dist/currency.cjs +2 -0
- package/dist/currency.cjs.map +1 -0
- package/dist/currency.d.ts +38 -0
- package/dist/currency.d.ts.map +1 -0
- package/dist/currency.js +55 -0
- package/dist/currency.js.map +1 -0
- package/dist/decimal.cjs +2 -0
- package/dist/decimal.cjs.map +1 -0
- package/dist/decimal.d.ts +7 -0
- package/dist/decimal.d.ts.map +1 -0
- package/dist/decimal.js +52 -0
- package/dist/decimal.js.map +1 -0
- package/dist/errors.cjs +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +6 -42
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -6
- package/dist/errors.js.map +1 -1
- package/dist/exchange.cjs +1 -1
- package/dist/exchange.cjs.map +1 -1
- package/dist/exchange.d.ts +9 -27
- package/dist/exchange.d.ts.map +1 -1
- package/dist/exchange.js +24 -12
- package/dist/exchange.js.map +1 -1
- package/dist/format.cjs +1 -1
- package/dist/format.cjs.map +1 -1
- package/dist/format.d.ts +2 -39
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +57 -94
- package/dist/format.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -5
- package/dist/money.cjs +1 -1
- package/dist/money.cjs.map +1 -1
- package/dist/money.d.ts +26 -245
- package/dist/money.d.ts.map +1 -1
- package/dist/money.js +71 -184
- package/dist/money.js.map +1 -1
- package/dist/serialization.cjs +2 -0
- package/dist/serialization.cjs.map +1 -0
- package/dist/serialization.d.ts +6 -0
- package/dist/serialization.d.ts.map +1 -0
- package/dist/serialization.js +45 -0
- package/dist/serialization.js.map +1 -0
- package/dist/types.d.ts +35 -52
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/_cache.cjs +0 -2
- package/dist/_cache.cjs.map +0 -1
- package/dist/_cache.d.ts +0 -6
- package/dist/_cache.d.ts.map +0 -1
- package/dist/_cache.js +0 -14
- package/dist/_cache.js.map +0 -1
- package/dist/_dev.cjs +0 -2
- package/dist/_dev.cjs.map +0 -1
- package/dist/_dev.d.ts +0 -2
- package/dist/_dev.d.ts.map +0 -1
- package/dist/_dev.js +0 -9
- package/dist/_dev.js.map +0 -1
- package/dist/utils.cjs +0 -2
- package/dist/utils.cjs.map +0 -1
- package/dist/utils.d.ts +0 -66
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -66
- package/dist/utils.js.map +0 -1
package/dist/_cache.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"_cache.js","names":[],"sources":["../src/_cache.ts"],"sourcesContent":["/** A FIFO bounded Map cache that evicts the oldest entry when full. */\nexport function boundedCache<K, V>(maxSize: number): { get(k: K): V | undefined; set(k: K, v: V): void } {\n const map = new Map<K, V>();\n\n return {\n get: (k) => map.get(k),\n set(k, v) {\n if (map.size >= maxSize) map.delete(map.keys().next().value as K);\n\n map.set(k, v);\n },\n };\n}\n"],"mappings":";AACA,SAAgB,EAAmB,GAAsE;CACvG,IAAM,oBAAM,IAAI,IAAU;CAE1B,OAAO;EACL,MAAM,MAAM,EAAI,IAAI,CAAC;EACrB,IAAI,GAAG,GAAG;GAGR,AAFI,EAAI,QAAQ,KAAS,EAAI,OAAO,EAAI,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAU,GAEhE,EAAI,IAAI,GAAG,CAAC;EACd;CACF;AACF"}
|
package/dist/_dev.cjs
DELETED
package/dist/_dev.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __COINS_PROD__?: boolean }).__COINS_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/coins] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":"AAAA,IAAM,EAAQ,CAAE,WAA4C,eAG5D,SAAgB,EAAK,EAAmB,CAClC,GAAO,QAAQ,KAAK,qBAAqB,GAAK,CACpD"}
|
package/dist/_dev.d.ts
DELETED
package/dist/_dev.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"_dev.d.ts","sourceRoot":"","sources":["../src/_dev.ts"],"names":[],"mappings":""}
|
package/dist/_dev.js
DELETED
package/dist/_dev.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __COINS_PROD__?: boolean }).__COINS_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/coins] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":";AAAA,IAAM,IAAQ,CAAE,WAA4C;AAG5D,SAAgB,EAAK,GAAmB;CACtC,AAAI,KAAO,QAAQ,KAAK,qBAAqB,GAAK;AACpD"}
|
package/dist/utils.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./errors.cjs");var t=require("./_cache.cjs").boundedCache(512);function n(n){let r=t.get(n);if(r!==void 0)return r;let i;try{i=new Intl.NumberFormat(`en`,{currency:n,style:`currency`}).resolvedOptions().maximumFractionDigits}catch{throw new e.InvalidCurrencyError(n)}if(i===void 0)throw new e.InvalidCurrencyError(n);return t.set(n,i),i}function r(e){return n(e),e}function i(e){return 10n**BigInt(e)}var a=/^-?\d+(\.\d+)?$/,o=/^(-?\d+\.?\d*)[eE]([+-]?\d+)$/,s=1e3;function c(t){let n=o.exec(t),r=n[1],i=parseInt(n[2],10);if(Math.abs(i)>s)throw new e.CoinsError(`Scientific notation exponent too large: "${t}" (max ±${s})`);let a=r.startsWith(`-`),c=a?r.slice(1):r,l=c.indexOf(`.`),u=l===-1?c:c.replace(`.`,``),d=i-(l===-1?0:c.length-l-1),f;if(d>=0)f=u+`0`.repeat(d);else{let e=u.length+d;f=e<=0?`0.`+`0`.repeat(-e)+u:u.slice(0,e)+`.`+u.slice(e)}return a?`-${f}`:f}function l(t){let n=o.test(t)?c(t):t;if(!a.test(n))throw new e.CoinsError(`Invalid decimal string: "${t}"`);let r=n.startsWith(`-`),s=r?n.slice(1):n,l=s.indexOf(`.`),u=l===-1?s:s.slice(0,l),d=l===-1?``:s.slice(l+1),f=i(d.length);return{denominator:f,negative:r,numerator:BigInt(u||`0`)*f+BigInt(d||`0`)}}function u(t,n,r,i,a=!1){if(n===0n)return t;switch(i){case`ceiling`:return a?t:t+1n;case`down`:return t;case`floor`:return a?t+1n:t;case`half-away-from-zero`:return n*2n>=r?t+1n:t;case`half-even`:{let e=n*2n;return e<r?t:e>r?t+1n:t%2n==0n?t:t+1n}case`up`:return t+1n;default:throw new e.CoinsError(`Unknown rounding mode: ${String(i)}`)}}exports.SCIENTIFIC_RE=o,exports.applyRounding=u,exports.expandScientific=c,exports.getCurrencyDecimals=n,exports.parseRational=l,exports.pow10=i,exports.validateCurrencyCode=r;
|
|
2
|
-
//# sourceMappingURL=utils.cjs.map
|
package/dist/utils.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.cjs","names":[],"sources":["../src/utils.ts"],"sourcesContent":["import type { CurrencyCode, RoundingMode } from './types';\n\nimport { boundedCache } from './_cache';\nimport { CoinsError, InvalidCurrencyError } from './errors';\n\nconst currencyDecimalsCache = boundedCache<string, number>(512);\n\n/**\n * Returns the number of minor-unit decimal places for a given ISO 4217 currency code.\n * Uses `Intl.NumberFormat` to resolve the canonical value (e.g. USD→2, JPY→0, KWD→3).\n * Throws `InvalidCurrencyError` for unrecognized codes or when the runtime cannot determine decimals.\n */\nexport function getCurrencyDecimals(currencyCode: CurrencyCode): number {\n const cached = currencyDecimalsCache.get(currencyCode);\n\n if (cached !== undefined) return cached;\n\n let resolved: number | undefined;\n\n try {\n resolved = new Intl.NumberFormat('en', { currency: currencyCode, style: 'currency' }).resolvedOptions()\n .maximumFractionDigits;\n } catch {\n throw new InvalidCurrencyError(currencyCode);\n }\n\n if (resolved === undefined) {\n throw new InvalidCurrencyError(currencyCode);\n }\n\n currencyDecimalsCache.set(currencyCode, resolved);\n\n return resolved;\n}\n\n/**\n * Validates an ISO 4217 currency code and returns it.\n * Throws `InvalidCurrencyError` if unrecognized. Uses `getCurrencyDecimals` internally so\n * the result is cached for subsequent decimal lookups.\n */\nexport function validateCurrencyCode(code: CurrencyCode): CurrencyCode {\n getCurrencyDecimals(code);\n\n return code;\n}\n\n/** Returns `10n ** BigInt(exponent)`. */\nexport function pow10(exponent: number): bigint {\n return 10n ** BigInt(exponent);\n}\n\n/** Regex for valid decimal strings accepted by `parseRational`. */\nconst DECIMAL_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/** Regex matching JavaScript scientific-notation strings, e.g. `'1e-7'`, `'-3.14E+5'`. */\nexport const SCIENTIFIC_RE = /^(-?\\d+\\.?\\d*)[eE]([+-]?\\d+)$/;\n\n/** Maximum absolute exponent allowed in scientific notation. Prevents '0'.repeat(N) allocation attacks. */\nconst MAX_SCIENTIFIC_EXP = 1000;\n\n/**\n * Expands a scientific-notation decimal string into a plain decimal string.\n * `'1e-7'` → `'0.0000001'`, `'1.23e+5'` → `'123000'`.\n * Only called when `SCIENTIFIC_RE` already matched — no extra validation.\n * @throws {CoinsError} If the absolute exponent exceeds `MAX_SCIENTIFIC_EXP` (1000).\n */\nexport function expandScientific(s: string): string {\n const match = SCIENTIFIC_RE.exec(s)!;\n const coeff = match[1]!;\n const exp = parseInt(match[2]!, 10);\n\n if (Math.abs(exp) > MAX_SCIENTIFIC_EXP) {\n throw new CoinsError(`Scientific notation exponent too large: \"${s}\" (max ±${MAX_SCIENTIFIC_EXP})`);\n }\n\n const isNeg = coeff.startsWith('-');\n const absCoeff = isNeg ? coeff.slice(1) : coeff;\n const dotIndex = absCoeff.indexOf('.');\n const digits = dotIndex === -1 ? absCoeff : absCoeff.replace('.', '');\n const fracLen = dotIndex === -1 ? 0 : absCoeff.length - dotIndex - 1;\n const newExp = exp - fracLen; // where the decimal point sits relative to end of digits\n\n let result: string;\n\n if (newExp >= 0) {\n // All digits are to the left of the decimal — append trailing zeros\n result = digits + '0'.repeat(newExp);\n } else {\n const intLen = digits.length + newExp; // digits before the decimal point\n\n if (intLen <= 0) {\n // Pure fraction: '0.' + leading zeros + digits\n result = '0.' + '0'.repeat(-intLen) + digits;\n } else {\n result = digits.slice(0, intLen) + '.' + digits.slice(intLen);\n }\n }\n\n return isNeg ? `-${result}` : result;\n}\n\n/**\n * Parses a decimal string into a rational `{ numerator, denominator, negative }`.\n * The denominator is always a power of 10 and the numerator is always non-negative.\n * @throws {CoinsError} For non-numeric input.\n *\n * Accepts standard decimal strings and JavaScript scientific notation\n * (e.g. `'1e-7'`, `'1.23E+5'`) — the latter is automatically expanded before parsing.\n *\n * @example\n * parseRational('1.5') → { numerator: 15n, denominator: 10n, negative: false }\n * parseRational('-0.5') → { numerator: 5n, denominator: 10n, negative: true }\n * parseRational('3') → { numerator: 3n, denominator: 1n, negative: false }\n * parseRational('1e-7') → { numerator: 1n, denominator: 10000000n, negative: false }\n *\n * Note: `'-0'` and `'-0.0'` produce `{ negative: true, numerator: 0n, ... }`.\n * Callers that compute `negative ? -result : result` on `0n` remain correct\n * because `-0n === 0n` in bigint arithmetic.\n */\nexport function parseRational(str: string): { denominator: bigint; negative: boolean; numerator: bigint } {\n // Expand scientific notation (e.g. from String(1e-7) = '1e-7') before validation.\n const normalized = SCIENTIFIC_RE.test(str) ? expandScientific(str) : str;\n\n if (!DECIMAL_RE.test(normalized)) {\n throw new CoinsError(`Invalid decimal string: \"${str}\"`);\n }\n\n const negative = normalized.startsWith('-');\n const absStr = negative ? normalized.slice(1) : normalized;\n const dotIndex = absStr.indexOf('.');\n const intStr = dotIndex === -1 ? absStr : absStr.slice(0, dotIndex);\n const fracStr = dotIndex === -1 ? '' : absStr.slice(dotIndex + 1);\n const denominator = pow10(fracStr.length);\n const numerator = BigInt(intStr || '0') * denominator + BigInt(fracStr || '0');\n\n return { denominator, negative, numerator };\n}\n\n/**\n * Applies a rounding mode to a truncated division result.\n *\n * **Contract**: `quotient` and `absRemainder` must be non-negative (absolute values).\n * The caller is responsible for:\n * 1. Computing everything in terms of absolute values.\n * 2. Passing `negative = true` when the true mathematical result is negative.\n * 3. Applying the sign after: `negative ? -result : result`.\n *\n * Returns the absolute value of the rounded result.\n *\n * @param quotient Non-negative integer part of the division (`|dividend| / divisor`).\n * @param absRemainder Non-negative remainder (`|dividend| % divisor`).\n * @param denominator The divisor used to produce quotient/remainder.\n * @param mode The desired rounding mode.\n * @param negative Whether the true mathematical result is negative. Defaults to `false`.\n */\nexport function applyRounding(\n quotient: bigint,\n absRemainder: bigint,\n denominator: bigint,\n mode: RoundingMode,\n negative = false,\n): bigint {\n if (absRemainder === 0n) return quotient;\n\n switch (mode) {\n case 'ceiling':\n // Toward +∞: round up for positive results, truncate for negative results.\n return negative ? quotient : quotient + 1n;\n\n case 'down':\n // Toward zero: always truncate.\n return quotient;\n\n case 'floor':\n // Toward −∞: truncate for positive results, one step more for negative results.\n return negative ? quotient + 1n : quotient;\n\n case 'half-away-from-zero':\n return absRemainder * 2n >= denominator ? quotient + 1n : quotient;\n\n case 'half-even': {\n const twice = absRemainder * 2n;\n\n if (twice < denominator) return quotient;\n\n if (twice > denominator) return quotient + 1n;\n\n // Exactly half — round to the nearest even integer.\n return quotient % 2n === 0n ? quotient : quotient + 1n;\n }\n\n case 'up':\n // Away from zero: always round away.\n return quotient + 1n;\n\n default:\n throw new CoinsError(`Unknown rounding mode: ${String(mode)}`);\n }\n}\n"],"mappings":"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"}
|
package/dist/utils.d.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { CurrencyCode, RoundingMode } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Returns the number of minor-unit decimal places for a given ISO 4217 currency code.
|
|
4
|
-
* Uses `Intl.NumberFormat` to resolve the canonical value (e.g. USD→2, JPY→0, KWD→3).
|
|
5
|
-
* Throws `InvalidCurrencyError` for unrecognized codes or when the runtime cannot determine decimals.
|
|
6
|
-
*/
|
|
7
|
-
export declare function getCurrencyDecimals(currencyCode: CurrencyCode): number;
|
|
8
|
-
/**
|
|
9
|
-
* Validates an ISO 4217 currency code and returns it.
|
|
10
|
-
* Throws `InvalidCurrencyError` if unrecognized. Uses `getCurrencyDecimals` internally so
|
|
11
|
-
* the result is cached for subsequent decimal lookups.
|
|
12
|
-
*/
|
|
13
|
-
export declare function validateCurrencyCode(code: CurrencyCode): CurrencyCode;
|
|
14
|
-
/** Returns `10n ** BigInt(exponent)`. */
|
|
15
|
-
export declare function pow10(exponent: number): bigint;
|
|
16
|
-
/** Regex matching JavaScript scientific-notation strings, e.g. `'1e-7'`, `'-3.14E+5'`. */
|
|
17
|
-
export declare const SCIENTIFIC_RE: RegExp;
|
|
18
|
-
/**
|
|
19
|
-
* Expands a scientific-notation decimal string into a plain decimal string.
|
|
20
|
-
* `'1e-7'` → `'0.0000001'`, `'1.23e+5'` → `'123000'`.
|
|
21
|
-
* Only called when `SCIENTIFIC_RE` already matched — no extra validation.
|
|
22
|
-
* @throws {CoinsError} If the absolute exponent exceeds `MAX_SCIENTIFIC_EXP` (1000).
|
|
23
|
-
*/
|
|
24
|
-
export declare function expandScientific(s: string): string;
|
|
25
|
-
/**
|
|
26
|
-
* Parses a decimal string into a rational `{ numerator, denominator, negative }`.
|
|
27
|
-
* The denominator is always a power of 10 and the numerator is always non-negative.
|
|
28
|
-
* @throws {CoinsError} For non-numeric input.
|
|
29
|
-
*
|
|
30
|
-
* Accepts standard decimal strings and JavaScript scientific notation
|
|
31
|
-
* (e.g. `'1e-7'`, `'1.23E+5'`) — the latter is automatically expanded before parsing.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* parseRational('1.5') → { numerator: 15n, denominator: 10n, negative: false }
|
|
35
|
-
* parseRational('-0.5') → { numerator: 5n, denominator: 10n, negative: true }
|
|
36
|
-
* parseRational('3') → { numerator: 3n, denominator: 1n, negative: false }
|
|
37
|
-
* parseRational('1e-7') → { numerator: 1n, denominator: 10000000n, negative: false }
|
|
38
|
-
*
|
|
39
|
-
* Note: `'-0'` and `'-0.0'` produce `{ negative: true, numerator: 0n, ... }`.
|
|
40
|
-
* Callers that compute `negative ? -result : result` on `0n` remain correct
|
|
41
|
-
* because `-0n === 0n` in bigint arithmetic.
|
|
42
|
-
*/
|
|
43
|
-
export declare function parseRational(str: string): {
|
|
44
|
-
denominator: bigint;
|
|
45
|
-
negative: boolean;
|
|
46
|
-
numerator: bigint;
|
|
47
|
-
};
|
|
48
|
-
/**
|
|
49
|
-
* Applies a rounding mode to a truncated division result.
|
|
50
|
-
*
|
|
51
|
-
* **Contract**: `quotient` and `absRemainder` must be non-negative (absolute values).
|
|
52
|
-
* The caller is responsible for:
|
|
53
|
-
* 1. Computing everything in terms of absolute values.
|
|
54
|
-
* 2. Passing `negative = true` when the true mathematical result is negative.
|
|
55
|
-
* 3. Applying the sign after: `negative ? -result : result`.
|
|
56
|
-
*
|
|
57
|
-
* Returns the absolute value of the rounded result.
|
|
58
|
-
*
|
|
59
|
-
* @param quotient Non-negative integer part of the division (`|dividend| / divisor`).
|
|
60
|
-
* @param absRemainder Non-negative remainder (`|dividend| % divisor`).
|
|
61
|
-
* @param denominator The divisor used to produce quotient/remainder.
|
|
62
|
-
* @param mode The desired rounding mode.
|
|
63
|
-
* @param negative Whether the true mathematical result is negative. Defaults to `false`.
|
|
64
|
-
*/
|
|
65
|
-
export declare function applyRounding(quotient: bigint, absRemainder: bigint, denominator: bigint, mode: RoundingMode, negative?: boolean): bigint;
|
|
66
|
-
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAO1D;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,YAAY,GAAG,MAAM,CAqBtE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,YAAY,CAIrE;AAED,yCAAyC;AACzC,wBAAgB,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE9C;AAKD,0FAA0F;AAC1F,eAAO,MAAM,aAAa,QAAkC,CAAC;AAK7D;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAiClD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAiBxG;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,YAAY,EAClB,QAAQ,UAAQ,GACf,MAAM,CAqCR"}
|
package/dist/utils.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { CoinsError as e, InvalidCurrencyError as t } from "./errors.js";
|
|
2
|
-
import { boundedCache as n } from "./_cache.js";
|
|
3
|
-
//#region src/utils.ts
|
|
4
|
-
var r = n(512);
|
|
5
|
-
function i(e) {
|
|
6
|
-
let n = r.get(e);
|
|
7
|
-
if (n !== void 0) return n;
|
|
8
|
-
let i;
|
|
9
|
-
try {
|
|
10
|
-
i = new Intl.NumberFormat("en", {
|
|
11
|
-
currency: e,
|
|
12
|
-
style: "currency"
|
|
13
|
-
}).resolvedOptions().maximumFractionDigits;
|
|
14
|
-
} catch {
|
|
15
|
-
throw new t(e);
|
|
16
|
-
}
|
|
17
|
-
if (i === void 0) throw new t(e);
|
|
18
|
-
return r.set(e, i), i;
|
|
19
|
-
}
|
|
20
|
-
function a(e) {
|
|
21
|
-
return i(e), e;
|
|
22
|
-
}
|
|
23
|
-
function o(e) {
|
|
24
|
-
return 10n ** BigInt(e);
|
|
25
|
-
}
|
|
26
|
-
var s = /^-?\d+(\.\d+)?$/, c = /^(-?\d+\.?\d*)[eE]([+-]?\d+)$/, l = 1e3;
|
|
27
|
-
function u(t) {
|
|
28
|
-
let n = c.exec(t), r = n[1], i = parseInt(n[2], 10);
|
|
29
|
-
if (Math.abs(i) > l) throw new e(`Scientific notation exponent too large: "${t}" (max ±${l})`);
|
|
30
|
-
let a = r.startsWith("-"), o = a ? r.slice(1) : r, s = o.indexOf("."), u = s === -1 ? o : o.replace(".", ""), d = i - (s === -1 ? 0 : o.length - s - 1), f;
|
|
31
|
-
if (d >= 0) f = u + "0".repeat(d);
|
|
32
|
-
else {
|
|
33
|
-
let e = u.length + d;
|
|
34
|
-
f = e <= 0 ? "0." + "0".repeat(-e) + u : u.slice(0, e) + "." + u.slice(e);
|
|
35
|
-
}
|
|
36
|
-
return a ? `-${f}` : f;
|
|
37
|
-
}
|
|
38
|
-
function d(t) {
|
|
39
|
-
let n = c.test(t) ? u(t) : t;
|
|
40
|
-
if (!s.test(n)) throw new e(`Invalid decimal string: "${t}"`);
|
|
41
|
-
let r = n.startsWith("-"), i = r ? n.slice(1) : n, a = i.indexOf("."), l = a === -1 ? i : i.slice(0, a), d = a === -1 ? "" : i.slice(a + 1), f = o(d.length);
|
|
42
|
-
return {
|
|
43
|
-
denominator: f,
|
|
44
|
-
negative: r,
|
|
45
|
-
numerator: BigInt(l || "0") * f + BigInt(d || "0")
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
function f(t, n, r, i, a = !1) {
|
|
49
|
-
if (n === 0n) return t;
|
|
50
|
-
switch (i) {
|
|
51
|
-
case "ceiling": return a ? t : t + 1n;
|
|
52
|
-
case "down": return t;
|
|
53
|
-
case "floor": return a ? t + 1n : t;
|
|
54
|
-
case "half-away-from-zero": return n * 2n >= r ? t + 1n : t;
|
|
55
|
-
case "half-even": {
|
|
56
|
-
let e = n * 2n;
|
|
57
|
-
return e < r ? t : e > r ? t + 1n : t % 2n == 0n ? t : t + 1n;
|
|
58
|
-
}
|
|
59
|
-
case "up": return t + 1n;
|
|
60
|
-
default: throw new e(`Unknown rounding mode: ${String(i)}`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
//#endregion
|
|
64
|
-
export { c as SCIENTIFIC_RE, f as applyRounding, u as expandScientific, i as getCurrencyDecimals, d as parseRational, o as pow10, a as validateCurrencyCode };
|
|
65
|
-
|
|
66
|
-
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","names":[],"sources":["../src/utils.ts"],"sourcesContent":["import type { CurrencyCode, RoundingMode } from './types';\n\nimport { boundedCache } from './_cache';\nimport { CoinsError, InvalidCurrencyError } from './errors';\n\nconst currencyDecimalsCache = boundedCache<string, number>(512);\n\n/**\n * Returns the number of minor-unit decimal places for a given ISO 4217 currency code.\n * Uses `Intl.NumberFormat` to resolve the canonical value (e.g. USD→2, JPY→0, KWD→3).\n * Throws `InvalidCurrencyError` for unrecognized codes or when the runtime cannot determine decimals.\n */\nexport function getCurrencyDecimals(currencyCode: CurrencyCode): number {\n const cached = currencyDecimalsCache.get(currencyCode);\n\n if (cached !== undefined) return cached;\n\n let resolved: number | undefined;\n\n try {\n resolved = new Intl.NumberFormat('en', { currency: currencyCode, style: 'currency' }).resolvedOptions()\n .maximumFractionDigits;\n } catch {\n throw new InvalidCurrencyError(currencyCode);\n }\n\n if (resolved === undefined) {\n throw new InvalidCurrencyError(currencyCode);\n }\n\n currencyDecimalsCache.set(currencyCode, resolved);\n\n return resolved;\n}\n\n/**\n * Validates an ISO 4217 currency code and returns it.\n * Throws `InvalidCurrencyError` if unrecognized. Uses `getCurrencyDecimals` internally so\n * the result is cached for subsequent decimal lookups.\n */\nexport function validateCurrencyCode(code: CurrencyCode): CurrencyCode {\n getCurrencyDecimals(code);\n\n return code;\n}\n\n/** Returns `10n ** BigInt(exponent)`. */\nexport function pow10(exponent: number): bigint {\n return 10n ** BigInt(exponent);\n}\n\n/** Regex for valid decimal strings accepted by `parseRational`. */\nconst DECIMAL_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/** Regex matching JavaScript scientific-notation strings, e.g. `'1e-7'`, `'-3.14E+5'`. */\nexport const SCIENTIFIC_RE = /^(-?\\d+\\.?\\d*)[eE]([+-]?\\d+)$/;\n\n/** Maximum absolute exponent allowed in scientific notation. Prevents '0'.repeat(N) allocation attacks. */\nconst MAX_SCIENTIFIC_EXP = 1000;\n\n/**\n * Expands a scientific-notation decimal string into a plain decimal string.\n * `'1e-7'` → `'0.0000001'`, `'1.23e+5'` → `'123000'`.\n * Only called when `SCIENTIFIC_RE` already matched — no extra validation.\n * @throws {CoinsError} If the absolute exponent exceeds `MAX_SCIENTIFIC_EXP` (1000).\n */\nexport function expandScientific(s: string): string {\n const match = SCIENTIFIC_RE.exec(s)!;\n const coeff = match[1]!;\n const exp = parseInt(match[2]!, 10);\n\n if (Math.abs(exp) > MAX_SCIENTIFIC_EXP) {\n throw new CoinsError(`Scientific notation exponent too large: \"${s}\" (max ±${MAX_SCIENTIFIC_EXP})`);\n }\n\n const isNeg = coeff.startsWith('-');\n const absCoeff = isNeg ? coeff.slice(1) : coeff;\n const dotIndex = absCoeff.indexOf('.');\n const digits = dotIndex === -1 ? absCoeff : absCoeff.replace('.', '');\n const fracLen = dotIndex === -1 ? 0 : absCoeff.length - dotIndex - 1;\n const newExp = exp - fracLen; // where the decimal point sits relative to end of digits\n\n let result: string;\n\n if (newExp >= 0) {\n // All digits are to the left of the decimal — append trailing zeros\n result = digits + '0'.repeat(newExp);\n } else {\n const intLen = digits.length + newExp; // digits before the decimal point\n\n if (intLen <= 0) {\n // Pure fraction: '0.' + leading zeros + digits\n result = '0.' + '0'.repeat(-intLen) + digits;\n } else {\n result = digits.slice(0, intLen) + '.' + digits.slice(intLen);\n }\n }\n\n return isNeg ? `-${result}` : result;\n}\n\n/**\n * Parses a decimal string into a rational `{ numerator, denominator, negative }`.\n * The denominator is always a power of 10 and the numerator is always non-negative.\n * @throws {CoinsError} For non-numeric input.\n *\n * Accepts standard decimal strings and JavaScript scientific notation\n * (e.g. `'1e-7'`, `'1.23E+5'`) — the latter is automatically expanded before parsing.\n *\n * @example\n * parseRational('1.5') → { numerator: 15n, denominator: 10n, negative: false }\n * parseRational('-0.5') → { numerator: 5n, denominator: 10n, negative: true }\n * parseRational('3') → { numerator: 3n, denominator: 1n, negative: false }\n * parseRational('1e-7') → { numerator: 1n, denominator: 10000000n, negative: false }\n *\n * Note: `'-0'` and `'-0.0'` produce `{ negative: true, numerator: 0n, ... }`.\n * Callers that compute `negative ? -result : result` on `0n` remain correct\n * because `-0n === 0n` in bigint arithmetic.\n */\nexport function parseRational(str: string): { denominator: bigint; negative: boolean; numerator: bigint } {\n // Expand scientific notation (e.g. from String(1e-7) = '1e-7') before validation.\n const normalized = SCIENTIFIC_RE.test(str) ? expandScientific(str) : str;\n\n if (!DECIMAL_RE.test(normalized)) {\n throw new CoinsError(`Invalid decimal string: \"${str}\"`);\n }\n\n const negative = normalized.startsWith('-');\n const absStr = negative ? normalized.slice(1) : normalized;\n const dotIndex = absStr.indexOf('.');\n const intStr = dotIndex === -1 ? absStr : absStr.slice(0, dotIndex);\n const fracStr = dotIndex === -1 ? '' : absStr.slice(dotIndex + 1);\n const denominator = pow10(fracStr.length);\n const numerator = BigInt(intStr || '0') * denominator + BigInt(fracStr || '0');\n\n return { denominator, negative, numerator };\n}\n\n/**\n * Applies a rounding mode to a truncated division result.\n *\n * **Contract**: `quotient` and `absRemainder` must be non-negative (absolute values).\n * The caller is responsible for:\n * 1. Computing everything in terms of absolute values.\n * 2. Passing `negative = true` when the true mathematical result is negative.\n * 3. Applying the sign after: `negative ? -result : result`.\n *\n * Returns the absolute value of the rounded result.\n *\n * @param quotient Non-negative integer part of the division (`|dividend| / divisor`).\n * @param absRemainder Non-negative remainder (`|dividend| % divisor`).\n * @param denominator The divisor used to produce quotient/remainder.\n * @param mode The desired rounding mode.\n * @param negative Whether the true mathematical result is negative. Defaults to `false`.\n */\nexport function applyRounding(\n quotient: bigint,\n absRemainder: bigint,\n denominator: bigint,\n mode: RoundingMode,\n negative = false,\n): bigint {\n if (absRemainder === 0n) return quotient;\n\n switch (mode) {\n case 'ceiling':\n // Toward +∞: round up for positive results, truncate for negative results.\n return negative ? quotient : quotient + 1n;\n\n case 'down':\n // Toward zero: always truncate.\n return quotient;\n\n case 'floor':\n // Toward −∞: truncate for positive results, one step more for negative results.\n return negative ? quotient + 1n : quotient;\n\n case 'half-away-from-zero':\n return absRemainder * 2n >= denominator ? quotient + 1n : quotient;\n\n case 'half-even': {\n const twice = absRemainder * 2n;\n\n if (twice < denominator) return quotient;\n\n if (twice > denominator) return quotient + 1n;\n\n // Exactly half — round to the nearest even integer.\n return quotient % 2n === 0n ? quotient : quotient + 1n;\n }\n\n case 'up':\n // Away from zero: always round away.\n return quotient + 1n;\n\n default:\n throw new CoinsError(`Unknown rounding mode: ${String(mode)}`);\n }\n}\n"],"mappings":";;;AAKA,IAAM,IAAwB,EAA6B,GAAG;AAO9D,SAAgB,EAAoB,GAAoC;CACtE,IAAM,IAAS,EAAsB,IAAI,CAAY;CAErD,IAAI,MAAW,KAAA,GAAW,OAAO;CAEjC,IAAI;CAEJ,IAAI;EACF,IAAW,IAAI,KAAK,aAAa,MAAM;GAAE,UAAU;GAAc,OAAO;EAAW,CAAC,CAAC,CAAC,gBAAgB,CAAC,CACpG;CACL,QAAQ;EACN,MAAM,IAAI,EAAqB,CAAY;CAC7C;CAEA,IAAI,MAAa,KAAA,GACf,MAAM,IAAI,EAAqB,CAAY;CAK7C,OAFA,EAAsB,IAAI,GAAc,CAAQ,GAEzC;AACT;AAOA,SAAgB,EAAqB,GAAkC;CAGrE,OAFA,EAAoB,CAAI,GAEjB;AACT;AAGA,SAAgB,EAAM,GAA0B;CAC9C,OAAO,OAAO,OAAO,CAAQ;AAC/B;AAGA,IAAM,IAAa,mBAGN,IAAgB,iCAGvB,IAAqB;AAQ3B,SAAgB,EAAiB,GAAmB;CAClD,IAAM,IAAQ,EAAc,KAAK,CAAC,GAC5B,IAAQ,EAAM,IACd,IAAM,SAAS,EAAM,IAAK,EAAE;CAElC,IAAI,KAAK,IAAI,CAAG,IAAI,GAClB,MAAM,IAAI,EAAW,4CAA4C,EAAE,UAAU,EAAmB,EAAE;CAGpG,IAAM,IAAQ,EAAM,WAAW,GAAG,GAC5B,IAAW,IAAQ,EAAM,MAAM,CAAC,IAAI,GACpC,IAAW,EAAS,QAAQ,GAAG,GAC/B,IAAS,MAAa,KAAK,IAAW,EAAS,QAAQ,KAAK,EAAE,GAE9D,IAAS,KADC,MAAa,KAAK,IAAI,EAAS,SAAS,IAAW,IAG/D;CAEJ,IAAI,KAAU,GAEZ,IAAS,IAAS,IAAI,OAAO,CAAM;MAC9B;EACL,IAAM,IAAS,EAAO,SAAS;EAE/B,AAIE,IAJE,KAAU,IAEH,OAAO,IAAI,OAAO,CAAC,CAAM,IAAI,IAE7B,EAAO,MAAM,GAAG,CAAM,IAAI,MAAM,EAAO,MAAM,CAAM;CAEhE;CAEA,OAAO,IAAQ,IAAI,MAAW;AAChC;AAoBA,SAAgB,EAAc,GAA4E;CAExG,IAAM,IAAa,EAAc,KAAK,CAAG,IAAI,EAAiB,CAAG,IAAI;CAErE,IAAI,CAAC,EAAW,KAAK,CAAU,GAC7B,MAAM,IAAI,EAAW,4BAA4B,EAAI,EAAE;CAGzD,IAAM,IAAW,EAAW,WAAW,GAAG,GACpC,IAAS,IAAW,EAAW,MAAM,CAAC,IAAI,GAC1C,IAAW,EAAO,QAAQ,GAAG,GAC7B,IAAS,MAAa,KAAK,IAAS,EAAO,MAAM,GAAG,CAAQ,GAC5D,IAAU,MAAa,KAAK,KAAK,EAAO,MAAM,IAAW,CAAC,GAC1D,IAAc,EAAM,EAAQ,MAAM;CAGxC,OAAO;EAAE;EAAa;EAAU,WAFd,OAAO,KAAU,GAAG,IAAI,IAAc,OAAO,KAAW,GAAG;CAEnC;AAC5C;AAmBA,SAAgB,EACd,GACA,GACA,GACA,GACA,IAAW,IACH;CACR,IAAI,MAAiB,IAAI,OAAO;CAEhC,QAAQ,GAAR;EACE,KAAK,WAEH,OAAO,IAAW,IAAW,IAAW;EAE1C,KAAK,QAEH,OAAO;EAET,KAAK,SAEH,OAAO,IAAW,IAAW,KAAK;EAEpC,KAAK,uBACH,OAAO,IAAe,MAAM,IAAc,IAAW,KAAK;EAE5D,KAAK,aAAa;GAChB,IAAM,IAAQ,IAAe;GAO7B,OALI,IAAQ,IAAoB,IAE5B,IAAQ,IAAoB,IAAW,KAGpC,IAAW,MAAO,KAAK,IAAW,IAAW;EACtD;EAEA,KAAK,MAEH,OAAO,IAAW;EAEpB,SACE,MAAM,IAAI,EAAW,0BAA0B,OAAO,CAAI,GAAG;CACjE;AACF"}
|