@ducatjs/money 0.1.0 → 0.2.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 +479 -62
- package/data/cash-rounding.json +123 -2
- package/data/cldr-cash.json +101 -0
- package/data/historic.json +78 -0
- package/data/iso4217-list-three.xml +1185 -0
- package/dist/adyen-fx.d.ts.map +1 -1
- package/dist/adyen-fx.js +22 -4
- package/dist/adyen-fx.js.map +1 -1
- package/dist/boc.d.ts +31 -0
- package/dist/boc.d.ts.map +1 -0
- package/dist/boc.js +114 -0
- package/dist/boc.js.map +1 -0
- package/dist/boe.d.ts +33 -0
- package/dist/boe.d.ts.map +1 -0
- package/dist/boe.js +114 -0
- package/dist/boe.js.map +1 -0
- package/dist/cash.d.ts +30 -1
- package/dist/cash.d.ts.map +1 -1
- package/dist/cash.js +50 -2
- package/dist/cash.js.map +1 -1
- package/dist/convert.d.ts +10 -9
- package/dist/convert.d.ts.map +1 -1
- package/dist/convert.js +13 -3
- package/dist/convert.js.map +1 -1
- package/dist/drizzle/mysql.d.ts +105 -0
- package/dist/drizzle/mysql.d.ts.map +1 -0
- package/dist/drizzle/mysql.js +60 -0
- package/dist/drizzle/mysql.js.map +1 -0
- package/dist/drizzle/pg.d.ts +103 -0
- package/dist/drizzle/pg.d.ts.map +1 -0
- package/dist/drizzle/pg.js +60 -0
- package/dist/drizzle/pg.js.map +1 -0
- package/dist/drizzle/sqlite.d.ts +102 -0
- package/dist/drizzle/sqlite.d.ts.map +1 -0
- package/dist/drizzle/sqlite.js +59 -0
- package/dist/drizzle/sqlite.js.map +1 -0
- package/dist/ecb.d.ts +37 -2
- package/dist/ecb.d.ts.map +1 -1
- package/dist/ecb.js +148 -10
- package/dist/ecb.js.map +1 -1
- package/dist/fed.d.ts +35 -0
- package/dist/fed.d.ts.map +1 -0
- package/dist/fed.js +134 -0
- package/dist/fed.js.map +1 -0
- package/dist/fetch.d.ts +45 -0
- package/dist/fetch.d.ts.map +1 -1
- package/dist/fetch.js +167 -24
- package/dist/fetch.js.map +1 -1
- package/dist/fn.d.ts +8 -1
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +8 -1
- package/dist/fn.js.map +1 -1
- package/dist/generated/cash.d.ts +2 -0
- package/dist/generated/cash.d.ts.map +1 -1
- package/dist/generated/cash.js +101 -0
- package/dist/generated/cash.js.map +1 -1
- package/dist/generated/historic.d.ts +337 -0
- package/dist/generated/historic.d.ts.map +1 -0
- package/dist/generated/historic.js +66 -0
- package/dist/generated/historic.js.map +1 -0
- package/dist/historic.d.ts +40 -0
- package/dist/historic.d.ts.map +1 -0
- package/dist/historic.js +43 -0
- package/dist/historic.js.map +1 -0
- package/dist/history.d.ts +96 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +213 -0
- package/dist/history.js.map +1 -0
- package/dist/hmrc.d.ts +28 -0
- package/dist/hmrc.d.ts.map +1 -0
- package/dist/hmrc.js +108 -0
- package/dist/hmrc.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/kysely.d.ts +120 -0
- package/dist/kysely.d.ts.map +1 -0
- package/dist/kysely.js +309 -0
- package/dist/kysely.js.map +1 -0
- package/dist/money.d.ts +93 -9
- package/dist/money.d.ts.map +1 -1
- package/dist/money.js +135 -9
- package/dist/money.js.map +1 -1
- package/dist/parse.d.ts +45 -1
- package/dist/parse.d.ts.map +1 -1
- package/dist/parse.js +290 -3
- package/dist/parse.js.map +1 -1
- package/dist/pg.d.ts +100 -0
- package/dist/pg.d.ts.map +1 -0
- package/dist/pg.js +343 -0
- package/dist/pg.js.map +1 -0
- package/dist/plugins/all.d.ts +8 -1
- package/dist/plugins/all.d.ts.map +1 -1
- package/dist/plugins/all.js +8 -1
- package/dist/plugins/all.js.map +1 -1
- package/dist/plugins/convert.d.ts +3 -4
- package/dist/plugins/convert.d.ts.map +1 -1
- package/dist/plugins/convert.js.map +1 -1
- package/dist/plugins/tax.d.ts +4 -5
- package/dist/plugins/tax.d.ts.map +1 -1
- package/dist/plugins/tax.js.map +1 -1
- package/dist/policy.d.ts +34 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +90 -0
- package/dist/policy.js.map +1 -0
- package/dist/prisma.d.ts +25 -14
- package/dist/prisma.d.ts.map +1 -1
- package/dist/prisma.js +29 -19
- package/dist/prisma.js.map +1 -1
- package/dist/provider.d.ts +21 -7
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +51 -11
- package/dist/provider.js.map +1 -1
- package/dist/rate-text.d.ts +38 -0
- package/dist/rate-text.d.ts.map +1 -0
- package/dist/rate-text.js +143 -0
- package/dist/rate-text.js.map +1 -0
- package/dist/rate.d.ts +61 -5
- package/dist/rate.d.ts.map +1 -1
- package/dist/rate.js +133 -33
- package/dist/rate.js.map +1 -1
- package/dist/rounding.js +1 -1
- package/dist/rounding.js.map +1 -1
- package/dist/schema.d.ts +43 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +77 -0
- package/dist/schema.js.map +1 -0
- package/dist/sql.d.ts +54 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/sql.js +131 -0
- package/dist/sql.js.map +1 -0
- package/dist/tax.d.ts +13 -9
- package/dist/tax.d.ts.map +1 -1
- package/dist/tax.js +20 -4
- package/dist/tax.js.map +1 -1
- package/dist/testing/arbitraries.d.ts +43 -0
- package/dist/testing/arbitraries.d.ts.map +1 -0
- package/dist/testing/arbitraries.js +98 -0
- package/dist/testing/arbitraries.js.map +1 -0
- package/dist/testing/index.d.ts +67 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +159 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/jest.d.ts +2 -0
- package/dist/testing/jest.d.ts.map +1 -0
- package/dist/testing/jest.js +15 -0
- package/dist/testing/jest.js.map +1 -0
- package/dist/testing/vitest.d.ts +7 -0
- package/dist/testing/vitest.d.ts.map +1 -0
- package/dist/testing/vitest.js +12 -0
- package/dist/testing/vitest.js.map +1 -0
- package/dist/valibot.d.ts +33 -0
- package/dist/valibot.d.ts.map +1 -0
- package/dist/valibot.js +61 -0
- package/dist/valibot.js.map +1 -0
- package/dist/zod.d.ts +36 -0
- package/dist/zod.d.ts.map +1 -0
- package/dist/zod.js +58 -0
- package/dist/zod.js.map +1 -0
- package/package.json +116 -5
- package/src/adyen-fx.ts +25 -4
- package/src/boc.ts +136 -0
- package/src/boe.ts +131 -0
- package/src/cash.ts +70 -2
- package/src/convert.ts +33 -15
- package/src/drizzle/mysql.ts +78 -0
- package/src/drizzle/pg.ts +78 -0
- package/src/drizzle/sqlite.ts +70 -0
- package/src/ecb.ts +179 -13
- package/src/fed.ts +146 -0
- package/src/fetch.ts +207 -28
- package/src/fn.ts +14 -0
- package/src/generated/cash.ts +103 -0
- package/src/generated/historic.ts +67 -0
- package/src/historic.ts +77 -0
- package/src/history.ts +333 -0
- package/src/hmrc.ts +125 -0
- package/src/index.ts +4 -0
- package/src/kysely.ts +588 -0
- package/src/money.ts +210 -12
- package/src/parse.ts +371 -4
- package/src/pg.ts +410 -0
- package/src/plugins/all.ts +10 -0
- package/src/plugins/convert.ts +4 -5
- package/src/plugins/tax.ts +9 -10
- package/src/policy.ts +124 -0
- package/src/prisma.ts +91 -10
- package/src/provider.ts +81 -16
- package/src/rate-text.ts +151 -0
- package/src/rate.ts +205 -36
- package/src/rounding.ts +1 -1
- package/src/schema.ts +119 -0
- package/src/sql.ts +178 -0
- package/src/tax.ts +40 -9
- package/src/testing/arbitraries.ts +134 -0
- package/src/testing/index.ts +247 -0
- package/src/testing/jest.ts +18 -0
- package/src/testing/vitest.ts +18 -0
- package/src/valibot.ts +87 -0
- package/src/zod.ts +78 -0
package/src/money.ts
CHANGED
|
@@ -32,6 +32,56 @@ export interface MoneyJSON<C extends string = string> {
|
|
|
32
32
|
readonly currency: C;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* A reusable rounding rule: a scale, an increment at that scale and a rounding mode, such as "CHF
|
|
37
|
+
* cash" (scale 2, increment 5, half up) or "4 decimals, half even". Build one with `roundingPolicy()`
|
|
38
|
+
* from `@ducatjs/money/policy` (or `cashPolicy()` from `@ducatjs/money/cash`) and pass it wherever
|
|
39
|
+
* a rounding mode is accepted: `m.multiply('1.075', policy)`, `m.round(policy)`. A policy is also
|
|
40
|
+
* a function that rounds an amount, so `m.with(policy)` and `amounts.map(policy)` work too.
|
|
41
|
+
*
|
|
42
|
+
* Policies are plain values passed explicitly; they are never stored on amounts or set globally.
|
|
43
|
+
*/
|
|
44
|
+
export interface RoundingPolicy {
|
|
45
|
+
/** Rounds `money` to this policy (exactly like `round(money, policy)`). */
|
|
46
|
+
<C extends string>(money: Money<C>): Money<C>;
|
|
47
|
+
/** Decimal places of the result; `undefined` means the currency's ISO exponent. */
|
|
48
|
+
readonly scale: number | undefined;
|
|
49
|
+
/** The result is a multiple of this many 10^-scale units (1 for plain rounding, 5 for 0.05). */
|
|
50
|
+
readonly increment: number;
|
|
51
|
+
/** How to round to the increment. */
|
|
52
|
+
readonly rounding: RoundingMode;
|
|
53
|
+
/** The one currency the policy is for, if any; other currencies throw `CurrencyMismatchError`. */
|
|
54
|
+
readonly currency: string | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* @internal money × factor, rounded once to this policy, under a key only this copy of the
|
|
57
|
+
* package can build. The core calls it for every operation given a policy, so the policy code
|
|
58
|
+
* stays out of bundles that never build one.
|
|
59
|
+
*/
|
|
60
|
+
readonly [ROUND_PRODUCT]: <C extends string>(money: Money<C>, factor: Ratio) => Money<C>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @internal The key a policy's rounding function is stored under. Module-private, so only
|
|
65
|
+
* `roundingPolicy` from this copy of the package makes values the core accepts as policies: a
|
|
66
|
+
* lookalike function, or a policy from a second installed copy (whose results would be that copy's
|
|
67
|
+
* Money), is rejected with a TypeError, as `isMoney` rejects the other copy's Money.
|
|
68
|
+
*/
|
|
69
|
+
export const ROUND_PRODUCT: unique symbol = Symbol();
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @internal money × factor rounded by a policy. Anything not built by `roundingPolicy` lacks the
|
|
73
|
+
* private key, so calling it throws a TypeError; that is the whole check, kept this small because
|
|
74
|
+
* it is in every bundle.
|
|
75
|
+
*/
|
|
76
|
+
export const roundWithPolicy = <C extends string>(
|
|
77
|
+
money: Money<C>,
|
|
78
|
+
factor: Ratio,
|
|
79
|
+
policy: RoundingPolicy,
|
|
80
|
+
): Money<C> => policy[ROUND_PRODUCT](money, factor);
|
|
81
|
+
|
|
82
|
+
/** What rounding parameters accept: a mode such as `'halfEven'`, or a `RoundingPolicy`. */
|
|
83
|
+
export type RoundingInput = RoundingMode | RoundingPolicy;
|
|
84
|
+
|
|
35
85
|
/** Thrown when two amounts in different currencies are combined. */
|
|
36
86
|
export class CurrencyMismatchError extends TypeError {
|
|
37
87
|
override name = 'CurrencyMismatchError';
|
|
@@ -103,11 +153,11 @@ class Money<C extends string = string> {
|
|
|
103
153
|
* this * factor, rounded to this scale. `factor` is exact: integer, decimal string ('1.075'),
|
|
104
154
|
* bigint or ratio().
|
|
105
155
|
*/
|
|
106
|
-
multiply(factor: Factor, rounding?:
|
|
156
|
+
multiply(factor: Factor, rounding?: RoundingInput): Money<C> {
|
|
107
157
|
return multiply(this, factor, rounding);
|
|
108
158
|
}
|
|
109
|
-
/** this / divisor, rounded to this scale */
|
|
110
|
-
divide(divisor: Factor, rounding?:
|
|
159
|
+
/** this / divisor, rounded to this scale (or to a policy's scale and increment) */
|
|
160
|
+
divide(divisor: Factor, rounding?: RoundingInput): Money<C> {
|
|
111
161
|
return divide(this, divisor, rounding);
|
|
112
162
|
}
|
|
113
163
|
negate(): Money<C> {
|
|
@@ -116,9 +166,12 @@ class Money<C extends string = string> {
|
|
|
116
166
|
abs(): Money<C> {
|
|
117
167
|
return abs(this);
|
|
118
168
|
}
|
|
119
|
-
/** Rounds to the currency's exponent (or `scale`). */
|
|
120
|
-
round(rounding: RoundingMode, scale?: number): Money<C
|
|
121
|
-
|
|
169
|
+
/** Rounds to the currency's exponent (or `scale`), or to a rounding policy. */
|
|
170
|
+
round(rounding: RoundingMode, scale?: number): Money<C>;
|
|
171
|
+
round(policy: RoundingPolicy): Money<C>;
|
|
172
|
+
round(rounding: RoundingInput, scale?: number): Money<C> {
|
|
173
|
+
// the free function handles both; the cast only picks its implementation signature
|
|
174
|
+
return round(this, rounding as RoundingMode, scale);
|
|
122
175
|
}
|
|
123
176
|
/** Changes the scale; decreasing it rounds (default 'halfExpand'). */
|
|
124
177
|
toScale(scale: number, rounding?: RoundingMode): Money<C> {
|
|
@@ -167,7 +220,27 @@ class Money<C extends string = string> {
|
|
|
167
220
|
return clamp(this, lo, hi);
|
|
168
221
|
}
|
|
169
222
|
|
|
223
|
+
/** Same currency, value and scale: 1.0 EUR is not identical to 1.00 EUR. */
|
|
224
|
+
identical(other: Money): boolean {
|
|
225
|
+
return identical(this, other);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Applies `operation` to this amount and returns its result, so a rounding policy or a
|
|
229
|
+
* conversion reads as one step in a chain: `price.multiply('1.075').with(toCash)`.
|
|
230
|
+
*/
|
|
231
|
+
with<R>(operation: (money: Money<C>) => R): R {
|
|
232
|
+
return operation(this);
|
|
233
|
+
}
|
|
234
|
+
|
|
170
235
|
// output
|
|
236
|
+
/** The whole major units, truncated toward zero: 12 for 12.34 EUR, -12 for -12.34 EUR. */
|
|
237
|
+
majorPart(): number | bigint {
|
|
238
|
+
return majorPart(this);
|
|
239
|
+
}
|
|
240
|
+
/** The rest in 10^-scale units, with the amount's sign: 34 for 12.34 EUR, -34 for -12.34. */
|
|
241
|
+
minorPart(): number | bigint {
|
|
242
|
+
return minorPart(this);
|
|
243
|
+
}
|
|
171
244
|
/** Exact decimal string in major units: "12.34" */
|
|
172
245
|
toDecimal(): string {
|
|
173
246
|
return formatScaled(this.minor, this.scale);
|
|
@@ -188,6 +261,33 @@ export function isMoney(value: unknown): value is Money {
|
|
|
188
261
|
return value instanceof Money;
|
|
189
262
|
}
|
|
190
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Whether a value is a Money in `currency`. It narrows a currency known only at run time
|
|
266
|
+
* (`Money<string>`, or `unknown`) to the literal type:
|
|
267
|
+
*
|
|
268
|
+
* if (isMoneyIn(price, 'EUR')) { price.add(money('1', 'EUR')) }
|
|
269
|
+
*/
|
|
270
|
+
export function isMoneyIn<C extends CurrencyInput>(value: unknown, currency: C): value is Money<C> {
|
|
271
|
+
return value instanceof Money && value.currency === currency;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Narrows `money` to `Money<C>`, or throws a `CurrencyMismatchError` if it is in another
|
|
276
|
+
* currency. Use it where a different currency is a bug rather than a branch.
|
|
277
|
+
*/
|
|
278
|
+
export function assertCurrency<C extends CurrencyInput>(
|
|
279
|
+
money: Money,
|
|
280
|
+
currency: C,
|
|
281
|
+
): asserts money is Money<C> {
|
|
282
|
+
// A non-Money would otherwise pass when it happens to have a matching `currency` field.
|
|
283
|
+
if (!(money instanceof Money)) {
|
|
284
|
+
throw new TypeError(`Expected Money, got ${typeof money}`);
|
|
285
|
+
}
|
|
286
|
+
if (money.currency !== currency) {
|
|
287
|
+
throw new CurrencyMismatchError(currency, money.currency);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
191
291
|
/**
|
|
192
292
|
* Which module installed each added method, so a second copy of the same module can replace its
|
|
193
293
|
* own.
|
|
@@ -546,10 +646,16 @@ export function abs<C extends string>(money: Money<C>): Money<C> {
|
|
|
546
646
|
export function multiply<C extends string>(
|
|
547
647
|
money: Money<C>,
|
|
548
648
|
factor: Factor,
|
|
549
|
-
rounding:
|
|
649
|
+
rounding: RoundingInput = DEFAULT_ROUNDING,
|
|
550
650
|
): Money<C> {
|
|
551
651
|
const minor = money.minor;
|
|
552
|
-
|
|
652
|
+
// An integer product is exact at this scale, but a policy may ask for another scale or increment.
|
|
653
|
+
if (
|
|
654
|
+
typeof factor === 'number' &&
|
|
655
|
+
Number.isSafeInteger(factor) &&
|
|
656
|
+
typeof minor === 'number' &&
|
|
657
|
+
typeof rounding === 'string'
|
|
658
|
+
) {
|
|
553
659
|
const product = minor * factor;
|
|
554
660
|
if (product <= MAX_SAFE_MINOR && product >= -MAX_SAFE_MINOR) {
|
|
555
661
|
return new Money(money.currency, product + 0, money.scale);
|
|
@@ -558,13 +664,16 @@ export function multiply<C extends string>(
|
|
|
558
664
|
return multiplyByRatio(money, ratio(factor), rounding);
|
|
559
665
|
}
|
|
560
666
|
|
|
561
|
-
/** @internal m * r, rounded */
|
|
667
|
+
/** @internal m * r, rounded with a mode (at `scale`) or with a policy (at the policy's scale) */
|
|
562
668
|
export function multiplyByRatio<C extends string>(
|
|
563
669
|
money: Money<C>,
|
|
564
670
|
factor: Ratio,
|
|
565
|
-
rounding:
|
|
671
|
+
rounding: RoundingInput,
|
|
566
672
|
scale = money.scale,
|
|
567
673
|
): Money<C> {
|
|
674
|
+
if (typeof rounding === 'function') {
|
|
675
|
+
return roundWithPolicy(money, factor, rounding);
|
|
676
|
+
}
|
|
568
677
|
const minor = money.minor;
|
|
569
678
|
if (
|
|
570
679
|
typeof minor === 'number' &&
|
|
@@ -588,16 +697,55 @@ export function multiplyByRatio<C extends string>(
|
|
|
588
697
|
return make(money.currency, divideAndRoundBigint(numerator, denominator, rounding), scale);
|
|
589
698
|
}
|
|
590
699
|
|
|
591
|
-
/** m / divisor, rounded to m's scale (default 'halfExpand'). */
|
|
700
|
+
/** m / divisor, rounded to m's scale (default 'halfExpand'), or with a rounding policy. */
|
|
592
701
|
export function divide<C extends string>(
|
|
593
702
|
money: Money<C>,
|
|
594
703
|
divisor: Factor,
|
|
595
|
-
rounding:
|
|
704
|
+
rounding: RoundingInput = DEFAULT_ROUNDING,
|
|
596
705
|
): Money<C> {
|
|
597
706
|
const divisorRatio = ratio(divisor);
|
|
598
707
|
if (divisorRatio.num === 0n) {
|
|
599
708
|
throw new RangeError('Division by zero');
|
|
600
709
|
}
|
|
710
|
+
|
|
711
|
+
// Fast path: m / (n/d) = m·d / n, rounded once by the same kernel the ratio path uses. Building
|
|
712
|
+
// the inverse ratio d/n would cost bigint arithmetic and a gcd on every call; the divisor's own
|
|
713
|
+
// ratio is cached for small integers and strings. Why it is exact:
|
|
714
|
+
// - makeRatio guarantees d > 0 and n/d in lowest terms, so m·d / n is the same fraction the
|
|
715
|
+
// inverse ratio d/n would give, and only n's sign needs handling;
|
|
716
|
+
// - `x === x` is false only for NaN, which is how a Ratio marks a part that is not a safe
|
|
717
|
+
// integer, so both parts are safe integers here;
|
|
718
|
+
// - m and d are safe integers, so a float product beyond 2^53 - 1 cannot round back down into
|
|
719
|
+
// the safe range: the bound check below rejects every inexact product;
|
|
720
|
+
// - divideAndRound corrects its float quotient exactly (and takes the bigint kernel itself when
|
|
721
|
+
// dividend + divisor exceeds the safe range).
|
|
722
|
+
// A rounding policy (a function, not a mode string) must not take this path: it rounds to its own
|
|
723
|
+
// scale and increment.
|
|
724
|
+
const minor = money.minor;
|
|
725
|
+
const numerator = divisorRatio.n;
|
|
726
|
+
const denominator = divisorRatio.d;
|
|
727
|
+
if (
|
|
728
|
+
typeof minor === 'number' &&
|
|
729
|
+
typeof rounding === 'string' &&
|
|
730
|
+
numerator === numerator &&
|
|
731
|
+
denominator === denominator
|
|
732
|
+
) {
|
|
733
|
+
let dividend = minor * denominator;
|
|
734
|
+
if (dividend <= MAX_SAFE_MINOR && dividend >= -MAX_SAFE_MINOR) {
|
|
735
|
+
// divideAndRound needs a positive divisor: (−p)/(−n) is the same quotient
|
|
736
|
+
let positiveDivisor = numerator;
|
|
737
|
+
if (numerator < 0) {
|
|
738
|
+
dividend = 0 - dividend;
|
|
739
|
+
positiveDivisor = 0 - numerator;
|
|
740
|
+
}
|
|
741
|
+
return new Money(
|
|
742
|
+
money.currency,
|
|
743
|
+
divideAndRound(dividend, positiveDivisor, rounding),
|
|
744
|
+
money.scale,
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
601
749
|
return multiplyByRatio(money, ratio(divisorRatio.den, divisorRatio.num), rounding);
|
|
602
750
|
}
|
|
603
751
|
|
|
@@ -609,7 +757,17 @@ export function round<C extends string>(
|
|
|
609
757
|
money: Money<C>,
|
|
610
758
|
rounding: RoundingMode,
|
|
611
759
|
scale?: number,
|
|
760
|
+
): Money<C>;
|
|
761
|
+
/** Rounds to a policy's scale and increment: `round(money('10.03', 'CHF'), cashPolicy('CHF'))`. */
|
|
762
|
+
export function round<C extends string>(money: Money<C>, policy: RoundingPolicy): Money<C>;
|
|
763
|
+
export function round<C extends string>(
|
|
764
|
+
money: Money<C>,
|
|
765
|
+
rounding: RoundingInput,
|
|
766
|
+
scale?: number,
|
|
612
767
|
): Money<C> {
|
|
768
|
+
if (typeof rounding === 'function') {
|
|
769
|
+
return roundWithPolicy(money, ratio(1), rounding);
|
|
770
|
+
}
|
|
613
771
|
return toScale(money, scale ?? currencyExponent(money.currency), rounding);
|
|
614
772
|
}
|
|
615
773
|
|
|
@@ -670,6 +828,17 @@ export function equals(money: Money, other: Money): boolean {
|
|
|
670
828
|
return compare(money, other) === 0;
|
|
671
829
|
}
|
|
672
830
|
|
|
831
|
+
/**
|
|
832
|
+
* Same currency, same value *and* same scale, with number and bigint minor units counted as equal.
|
|
833
|
+
* `equals` treats 1.0 EUR (scale 1) and 1.00 EUR as equal; `identical` does not. For round-trip
|
|
834
|
+
* tests, where the scale must survive too.
|
|
835
|
+
*/
|
|
836
|
+
export function identical(money: Money, other: Money): boolean {
|
|
837
|
+
return (
|
|
838
|
+
money.currency === other.currency && money.scale === other.scale && money.minor == other.minor
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
|
|
673
842
|
/** money < other (same currency; any scale). */
|
|
674
843
|
export const lessThan = <C extends string>(money: Money<C>, other: Money<NoInfer<C>>): boolean =>
|
|
675
844
|
compare(money, other) < 0;
|
|
@@ -747,6 +916,35 @@ export function toDecimal(money: Money): string {
|
|
|
747
916
|
return formatScaled(money.minor, money.scale);
|
|
748
917
|
}
|
|
749
918
|
|
|
919
|
+
/**
|
|
920
|
+
* The whole major units, truncated toward zero: 12 for 12.34 EUR and -12 for -12.34 EUR. A number
|
|
921
|
+
* while it is a safe integer, a bigint beyond, like `minor`.
|
|
922
|
+
*/
|
|
923
|
+
export function majorPart(money: Money): number | bigint {
|
|
924
|
+
const minor = money.minor;
|
|
925
|
+
if (typeof minor === 'number' && money.scale <= 15) {
|
|
926
|
+
// Taking the remainder out first leaves an exact multiple, so the division is exact. `+ 0`
|
|
927
|
+
// turns -0 into 0.
|
|
928
|
+
const unit = POW10[money.scale]!;
|
|
929
|
+
return (minor - (minor % unit)) / unit + 0;
|
|
930
|
+
}
|
|
931
|
+
// bigint division truncates toward zero, like the fast path
|
|
932
|
+
return normalizeMinor(toBigInt(minor) / pow10n(money.scale));
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* What is left after `majorPart`, in 10^-scale units and with the amount's sign: 34 for 12.34
|
|
937
|
+
* EUR, -34 for -12.34 EUR, 5 for 1.005 EUR at scale 3.
|
|
938
|
+
*/
|
|
939
|
+
export function minorPart(money: Money): number | bigint {
|
|
940
|
+
const minor = money.minor;
|
|
941
|
+
if (typeof minor === 'number' && money.scale <= 15) {
|
|
942
|
+
// % on safe integers is exact and keeps the dividend's sign; `+ 0` turns -0 into 0.
|
|
943
|
+
return (minor % POW10[money.scale]!) + 0;
|
|
944
|
+
}
|
|
945
|
+
return normalizeMinor(toBigInt(minor) % pow10n(money.scale));
|
|
946
|
+
}
|
|
947
|
+
|
|
750
948
|
/** Minor units as a bigint (always safe; use for BIGINT columns). */
|
|
751
949
|
export function toMinor(money: Money): bigint {
|
|
752
950
|
return toBigInt(money.minor);
|