@agoric/ertp 0.16.3-other-dev-8f8782b.0 → 0.16.3-other-dev-fbe72e7.0.fbe72e7

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.
Files changed (63) hide show
  1. package/exported.d.ts +37 -0
  2. package/exported.js +2 -1
  3. package/package.json +37 -37
  4. package/src/amountMath.d.ts +46 -38
  5. package/src/amountMath.d.ts.map +1 -1
  6. package/src/amountMath.js +136 -128
  7. package/src/amountStore.d.ts +9 -0
  8. package/src/amountStore.d.ts.map +1 -0
  9. package/src/amountStore.js +34 -0
  10. package/src/displayInfo.d.ts +3 -0
  11. package/src/displayInfo.d.ts.map +1 -1
  12. package/src/displayInfo.js +3 -1
  13. package/src/index.d.ts +1 -0
  14. package/src/index.js +4 -0
  15. package/src/issuerKit.d.ts +29 -9
  16. package/src/issuerKit.d.ts.map +1 -1
  17. package/src/issuerKit.js +230 -75
  18. package/src/legacy-payment-helpers.d.ts +9 -2
  19. package/src/legacy-payment-helpers.d.ts.map +1 -1
  20. package/src/legacy-payment-helpers.js +43 -37
  21. package/src/mathHelpers/copyBagMathHelpers.d.ts +3 -4
  22. package/src/mathHelpers/copyBagMathHelpers.d.ts.map +1 -1
  23. package/src/mathHelpers/copyBagMathHelpers.js +4 -5
  24. package/src/mathHelpers/copySetMathHelpers.d.ts +3 -3
  25. package/src/mathHelpers/copySetMathHelpers.d.ts.map +1 -1
  26. package/src/mathHelpers/copySetMathHelpers.js +6 -4
  27. package/src/mathHelpers/natMathHelpers.d.ts +8 -7
  28. package/src/mathHelpers/natMathHelpers.d.ts.map +1 -1
  29. package/src/mathHelpers/natMathHelpers.js +8 -9
  30. package/src/mathHelpers/setMathHelpers.d.ts +2 -0
  31. package/src/mathHelpers/setMathHelpers.d.ts.map +1 -1
  32. package/src/mathHelpers/setMathHelpers.js +2 -1
  33. package/src/payment.d.ts +4 -2
  34. package/src/payment.d.ts.map +1 -1
  35. package/src/payment.js +8 -8
  36. package/src/paymentLedger.d.ts +8 -2
  37. package/src/paymentLedger.d.ts.map +1 -1
  38. package/src/paymentLedger.js +80 -97
  39. package/src/purse.d.ts +19 -9
  40. package/src/purse.d.ts.map +1 -1
  41. package/src/purse.js +86 -26
  42. package/src/ratio.d.ts +48 -0
  43. package/src/ratio.d.ts.map +1 -0
  44. package/src/ratio.js +441 -0
  45. package/src/safeMath.d.ts +11 -0
  46. package/src/safeMath.d.ts.map +1 -0
  47. package/src/safeMath.js +50 -0
  48. package/src/transientNotifier.d.ts +1 -1
  49. package/src/transientNotifier.d.ts.map +1 -1
  50. package/src/transientNotifier.js +5 -0
  51. package/src/typeGuards.d.ts +64 -13
  52. package/src/typeGuards.d.ts.map +1 -1
  53. package/src/typeGuards.js +70 -57
  54. package/src/types-index.d.ts +2 -0
  55. package/src/types-index.js +2 -0
  56. package/src/types.d.ts +254 -220
  57. package/src/types.d.ts.map +1 -1
  58. package/src/types.ts +474 -0
  59. package/CHANGELOG.md +0 -743
  60. package/src/types-ambient.d.ts +0 -376
  61. package/src/types-ambient.d.ts.map +0 -1
  62. package/src/types-ambient.js +0 -440
  63. package/src/types.js +0 -441
package/src/purse.js CHANGED
@@ -1,27 +1,73 @@
1
- import { M } from '@agoric/store';
2
- import { prepareExoClassKit, makeScalarBigSetStore } from '@agoric/vat-data';
1
+ import { Fail } from '@endo/errors';
2
+ import { M, makeCopySet } from '@agoric/store';
3
3
  import { AmountMath } from './amountMath.js';
4
4
  import { makeTransientNotifierKit } from './transientNotifier.js';
5
+ import { makeAmountStore } from './amountStore.js';
5
6
 
6
- const { Fail } = assert;
7
+ /** @import {AssetKind, RecoverySetsOption, Brand, Payment} from './types.js' */
7
8
 
9
+ const EMPTY_COPY_SET = makeCopySet([]);
10
+
11
+ // TODO Type InterfaceGuard better than InterfaceGuard<any>
12
+ /**
13
+ * @param {import('@agoric/zone').Zone} issuerZone
14
+ * @param {string} name
15
+ * @param {AssetKind} assetKind
16
+ * @param {Brand} brand
17
+ * @param {{
18
+ * purse: import('@endo/patterns').InterfaceGuard<any>;
19
+ * depositFacet: import('@endo/patterns').InterfaceGuard<any>;
20
+ * }} PurseIKit
21
+ * @param {{
22
+ * depositInternal: any;
23
+ * withdrawInternal: any;
24
+ * }} purseMethods
25
+ * @param {RecoverySetsOption} recoverySetsState
26
+ * @param {WeakMapStore<Payment, SetStore<Payment>>} paymentRecoverySets
27
+ */
8
28
  export const preparePurseKind = (
9
- issuerBaggage,
29
+ issuerZone,
10
30
  name,
11
31
  assetKind,
12
32
  brand,
13
33
  PurseIKit,
14
34
  purseMethods,
35
+ recoverySetsState,
36
+ paymentRecoverySets,
15
37
  ) => {
16
38
  const amountShape = brand.getAmountShape();
17
39
 
18
40
  // Note: Virtual for high cardinality, but *not* durable, and so
19
41
  // broken across an upgrade.
42
+ // TODO propagate zonifying to notifiers, maybe?
20
43
  const { provideNotifier, update: updateBalance } = makeTransientNotifierKit();
21
44
 
22
- const updatePurseBalance = (state, newPurseBalance, purse) => {
23
- state.currentBalance = newPurseBalance;
24
- updateBalance(purse, purse.getCurrentAmount());
45
+ /**
46
+ * If `recoverySetsState === 'hasRecoverySets'` (the normal state), then just
47
+ * return `state.recoverySet`.
48
+ *
49
+ * If `recoverySetsState === 'noRecoverySets'`, return `undefined`. Callers
50
+ * must be aware that the `undefined` return happens iff `recoverySetsState
51
+ * === 'noRecoverySets'`, and to avoid storing or retrieving anything from the
52
+ * actual recovery set.
53
+ *
54
+ * @param {{ recoverySet: SetStore<Payment> }} state
55
+ * @returns {SetStore<Payment> | undefined}
56
+ */
57
+ const maybeRecoverySet = state => {
58
+ const { recoverySet } = state;
59
+ if (recoverySetsState === 'hasRecoverySets') {
60
+ return recoverySet;
61
+ } else {
62
+ recoverySetsState === 'noRecoverySets' ||
63
+ Fail`recoverSetsState must be noRecoverySets if it isn't hasRecoverSets`;
64
+ paymentRecoverySets !== undefined ||
65
+ Fail`paymentRecoverySets must always be defined`;
66
+ recoverySet.getSize() === 0 ||
67
+ Fail`With noRecoverySets, recoverySet must be empty`;
68
+
69
+ return undefined;
70
+ }
25
71
  };
26
72
 
27
73
  // - This kind is a pair of purse and depositFacet that have a 1:1
@@ -31,17 +77,14 @@ export const preparePurseKind = (
31
77
  // that created depositFacet as needed. But this approach ensures a constant
32
78
  // identity for the facet and exercises the multi-faceted object style.
33
79
  const { depositInternal, withdrawInternal } = purseMethods;
34
- const makePurseKit = prepareExoClassKit(
35
- issuerBaggage,
80
+ const makePurseKit = issuerZone.exoClassKit(
36
81
  `${name} Purse`,
37
82
  PurseIKit,
38
83
  () => {
39
84
  const currentBalance = AmountMath.makeEmpty(brand, assetKind);
40
85
 
41
86
  /** @type {SetStore<Payment>} */
42
- const recoverySet = makeScalarBigSetStore('recovery set', {
43
- durable: true,
44
- });
87
+ const recoverySet = issuerZone.detached().setStore('recovery set');
45
88
 
46
89
  return {
47
90
  currentBalance,
@@ -54,28 +97,36 @@ export const preparePurseKind = (
54
97
  // PurseI does *not* delay `deposit` until `srcPayment` is fulfulled.
55
98
  // See the comments on PurseI.deposit in typeGuards.js
56
99
  const { state } = this;
100
+ const { purse } = this.facets;
101
+ const balanceStore = makeAmountStore(state, 'currentBalance');
57
102
  // Note COMMIT POINT within deposit.
58
- return depositInternal(
59
- state.currentBalance,
60
- newPurseBalance =>
61
- updatePurseBalance(state, newPurseBalance, this.facets.purse),
103
+ const srcPaymentBalance = depositInternal(
104
+ balanceStore,
62
105
  srcPayment,
63
106
  optAmountShape,
64
107
  );
108
+ updateBalance(purse, balanceStore.getAmount());
109
+ return srcPaymentBalance;
65
110
  },
66
111
  withdraw(amount) {
67
112
  const { state } = this;
113
+ const { purse } = this.facets;
114
+
115
+ const optRecoverySet = maybeRecoverySet(state);
116
+ const balanceStore = makeAmountStore(state, 'currentBalance');
68
117
  // Note COMMIT POINT within withdraw.
69
- return withdrawInternal(
70
- state.currentBalance,
71
- newPurseBalance =>
72
- updatePurseBalance(state, newPurseBalance, this.facets.purse),
118
+ const payment = withdrawInternal(
119
+ balanceStore,
73
120
  amount,
74
- state.recoverySet,
121
+ optRecoverySet,
75
122
  );
123
+ updateBalance(purse, balanceStore.getAmount());
124
+ return payment;
76
125
  },
77
126
  getCurrentAmount() {
78
- return this.state.currentBalance;
127
+ const { state } = this;
128
+ const balanceStore = makeAmountStore(state, 'currentBalance');
129
+ return balanceStore.getAmount();
79
130
  },
80
131
  getCurrentAmountNotifier() {
81
132
  return provideNotifier(this.facets.purse);
@@ -83,24 +134,33 @@ export const preparePurseKind = (
83
134
  getAllegedBrand() {
84
135
  return brand;
85
136
  },
86
- // eslint-disable-next-line no-use-before-define
137
+
87
138
  getDepositFacet() {
88
139
  return this.facets.depositFacet;
89
140
  },
90
141
 
91
142
  getRecoverySet() {
92
- return this.state.recoverySet.snapshot();
143
+ const { state } = this;
144
+ const optRecoverySet = maybeRecoverySet(state);
145
+ if (optRecoverySet === undefined) {
146
+ return EMPTY_COPY_SET;
147
+ }
148
+ return optRecoverySet.snapshot();
93
149
  },
94
150
  recoverAll() {
95
151
  const { state, facets } = this;
96
152
  let amount = AmountMath.makeEmpty(brand, assetKind);
97
- for (const payment of state.recoverySet.keys()) {
153
+ const optRecoverySet = maybeRecoverySet(state);
154
+ if (optRecoverySet === undefined) {
155
+ return amount; // empty at this time
156
+ }
157
+ for (const payment of optRecoverySet.keys()) {
98
158
  // This does cause deletions from the set while iterating,
99
159
  // but this special case is allowed.
100
160
  const delta = facets.purse.deposit(payment);
101
161
  amount = AmountMath.add(amount, delta, brand);
102
162
  }
103
- state.recoverySet.getSize() === 0 ||
163
+ optRecoverySet.getSize() === 0 ||
104
164
  Fail`internal: Remaining unrecovered payments: ${facets.purse.getRecoverySet()}`;
105
165
  return amount;
106
166
  },
package/src/ratio.d.ts ADDED
@@ -0,0 +1,48 @@
1
+ export function assertIsRatio(ratio: any): void;
2
+ export function makeRatio(numerator: bigint, numeratorBrand: Brand, denominator?: bigint, denominatorBrand?: Brand): Ratio;
3
+ export function makeRatioFromAmounts(numeratorAmount: Amount, denominatorAmount: Amount): Ratio;
4
+ /** @type {ScaleAmount} */
5
+ export const floorMultiplyBy: ScaleAmount;
6
+ /** @type {ScaleAmount} */
7
+ export const ceilMultiplyBy: ScaleAmount;
8
+ /** @type {ScaleAmount} */
9
+ export const multiplyBy: ScaleAmount;
10
+ /**
11
+ * Divide the amount by the ratio, truncating the remainder.
12
+ *
13
+ * @type {ScaleAmount}
14
+ */
15
+ export const floorDivideBy: ScaleAmount;
16
+ /**
17
+ * Divide the amount by the ratio, rounding up the remainder.
18
+ *
19
+ * @type {ScaleAmount}
20
+ */
21
+ export const ceilDivideBy: ScaleAmount;
22
+ /**
23
+ * Divide the amount by the ratio, rounding to nearest with ties to even (aka
24
+ * Banker's Rounding) as in IEEE 754 default rounding.
25
+ *
26
+ * @type {ScaleAmount}
27
+ */
28
+ export const divideBy: ScaleAmount;
29
+ export function invertRatio(ratio: Ratio): Ratio;
30
+ export function addRatios(left: Ratio, right: Ratio): Ratio;
31
+ export function subtractRatios(left: Ratio, right: Ratio): Ratio;
32
+ export function multiplyRatios(left: Ratio, right: Ratio): Ratio;
33
+ export function oneMinus(ratio: Ratio): Ratio;
34
+ export function ratioGTE(left: Ratio, right: Ratio): boolean;
35
+ export function ratiosSame(left: Ratio, right: Ratio): boolean;
36
+ export function quantize(ratio: Ratio, newDen: bigint): Ratio;
37
+ export function parseRatio(numeric: ParsableNumber, numeratorBrand: Brand<"nat">, denominatorBrand?: Brand<"nat">): Ratio;
38
+ export function assertParsableNumber(specimen: unknown): asserts specimen is ParsableNumber;
39
+ export function ratioToNumber(ratio: Ratio): number;
40
+ export type Ratio = {
41
+ numerator: Amount<"nat">;
42
+ denominator: Amount<"nat">;
43
+ };
44
+ export type ScaleAmount = (amount: Amount<"nat">, ratio: Ratio) => Amount<"nat">;
45
+ export type ParsableNumber = bigint | number | string;
46
+ import type { Brand } from '@agoric/ertp';
47
+ import type { Amount } from '@agoric/ertp';
48
+ //# sourceMappingURL=ratio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ratio.d.ts","sourceRoot":"","sources":["ratio.js"],"names":[],"mappings":"AAwDO,gDAcN;AASM,qCANI,MAAM,kBACN,KAAK,gBACL,MAAM,qBACN,KAAK,GACH,KAAK,CAejB;AAOM,sDAJI,MAAM,qBACN,MAAM,GACJ,KAAK,CAYjB;AA2BD,0BAA0B;AAC1B,8BADW,WAAW,CAGpB;AAEF,0BAA0B;AAC1B,6BADW,WAAW,CAGpB;AAEF,0BAA0B;AAC1B,yBADW,WAAW,CAGpB;AA2BF;;;;GAIG;AACH,4BAFU,WAAW,CAInB;AAEF;;;;GAIG;AACH,2BAFU,WAAW,CAInB;AAEF;;;;;GAKG;AACH,uBAFU,WAAW,CAInB;AAMK,mCAHI,KAAK,GACH,KAAK,CAWjB;AAOM,gCAJI,KAAK,SACL,KAAK,GACH,KAAK,CAwBjB;AAOM,qCAJI,KAAK,SACL,KAAK,GACH,KAAK,CAmBjB;AAOM,qCAJI,KAAK,SACL,KAAK,GACH,KAAK,CA8BjB;AAQM,gCAHI,KAAK,GACH,KAAK,CAcjB;AAOM,+BAJI,KAAK,SACL,KAAK,GACH,OAAO,CAkBnB;AAUM,iCAJI,KAAK,SACL,KAAK,GACH,OAAO,CAOnB;AAWM,gCAJI,KAAK,UACL,MAAM,GACJ,KAAK,CAiBjB;AAaM,oCALI,cAAc,kBACd,MAAM,KAAK,CAAC,qBACZ,MAAM,KAAK,CAAC,GACV,KAAK,CAmBjB;AAMM,+CAHI,OAAO,GACL,QAAQ,QAAQ,IAAI,cAAc,CAK9C;AAQM,qCAHI,KAAK,GACH,MAAM,CAMlB;;eAxaa,OAAO,KAAK,CAAC;iBACb,OAAO,KAAK,CAAC;;mCAKhB,OAAO,KAAK,CAAC,SACb,KAAK,KACH,OAAO,KAAK,CAAC;6BAgXZ,MAAM,GAAG,MAAM,GAAG,MAAM;2BAhYkB,cAAc;4BAAd,cAAc"}
package/src/ratio.js ADDED
@@ -0,0 +1,441 @@
1
+ import { q, Fail } from '@endo/errors';
2
+ import { assertRecord } from '@endo/marshal';
3
+ import { isNat } from '@endo/nat';
4
+
5
+ import { AmountMath } from './amountMath.js';
6
+ import { natSafeMath } from './safeMath.js';
7
+
8
+ /**
9
+ * @import {Amount, Brand, Issuer, Payment, Purse} from '@agoric/ertp';
10
+ */
11
+
12
+ const { multiply, floorDivide, ceilDivide, bankersDivide, add, subtract } =
13
+ natSafeMath;
14
+
15
+ /**
16
+ * @typedef {object} Ratio
17
+ * @property {Amount<'nat'>} numerator
18
+ * @property {Amount<'nat'>} denominator
19
+ */
20
+
21
+ /**
22
+ * @callback ScaleAmount
23
+ * @param {Amount<'nat'>} amount
24
+ * @param {Ratio} ratio
25
+ * @returns {Amount<'nat'>}
26
+ */
27
+
28
+ // make a Ratio, which represents a fraction. It is a pass-by-copy record.
29
+ //
30
+ // The natural syntax for the most common operations we want to support
31
+ // are Amount * Ratio and Amount / Ratio. Since the operations want to adhere to
32
+ // the ratio rather than the amount, we settled on a calling convention of
33
+ // [ceil|floor]MultiplyBy(Amount, Ratio) and [ceil|floor]DivideBy(Amount, Ratio)
34
+ //
35
+ // The most common kind of Ratio can be applied to Amounts of a particular
36
+ // brand, and produces results of the same brand. This represents a multiplier
37
+ // that is only applicable to that brand. The less common kind of Ratio can be
38
+ // applied to one particular brand of amounts, and produces results of another
39
+ // particular brand. This represents some kind of exchange rate. The
40
+ // brand-checking helps us ensure that normal Ratios aren't applied to amounts
41
+ // of the wrong brand, and that exchange rates are only used in the appropriate
42
+ // direction.
43
+ //
44
+ // Since the ratios are represented by a numerator and a denominator, every
45
+ // multiplication or division operation that produces an amount ends with a
46
+ // division of the underlying bigints, and integer division requires a mode
47
+ // of [rounding to integer](https://en.wikipedia.org/wiki/Rounding#Rounding_to_integer).
48
+ // Because `Ratio` only work with Natural numbers, just three modes suffice:
49
+ // - floor rounds down
50
+ // - ceil rounds up
51
+ // - default (without prefix) minimizes bias by rounding half to even
52
+
53
+ const PERCENT = 100n;
54
+
55
+ const ratioPropertyNames = ['numerator', 'denominator'];
56
+
57
+ export const assertIsRatio = ratio => {
58
+ assertRecord(ratio, 'ratio');
59
+ const keys = Object.keys(ratio);
60
+ keys.length === 2 || Fail`Ratio ${ratio} must be a record with 2 fields.`;
61
+ for (const name of keys) {
62
+ ratioPropertyNames.includes(name) ||
63
+ Fail`Parameter must be a Ratio record, but ${ratio} has ${q(name)}`;
64
+ }
65
+ const numeratorValue = ratio.numerator.value;
66
+ const denominatorValue = ratio.denominator.value;
67
+ isNat(numeratorValue) ||
68
+ Fail`The numerator value must be a NatValue, not ${numeratorValue}`;
69
+ isNat(denominatorValue) ||
70
+ Fail`The denominator value must be a NatValue, not ${denominatorValue}`;
71
+ };
72
+
73
+ /**
74
+ * @param {bigint} numerator
75
+ * @param {Brand} numeratorBrand
76
+ * @param {bigint} [denominator] The default denominator is 100
77
+ * @param {Brand} [denominatorBrand] The default is to reuse the numeratorBrand
78
+ * @returns {Ratio}
79
+ */
80
+ export const makeRatio = (
81
+ numerator,
82
+ numeratorBrand,
83
+ denominator = PERCENT,
84
+ denominatorBrand = numeratorBrand,
85
+ ) => {
86
+ denominator > 0n ||
87
+ Fail`No infinite ratios! Denominator was 0 ${q(denominatorBrand)}`;
88
+
89
+ return harden({
90
+ numerator: AmountMath.make(numeratorBrand, numerator),
91
+ denominator: AmountMath.make(denominatorBrand, denominator),
92
+ });
93
+ };
94
+
95
+ /**
96
+ * @param {Amount} numeratorAmount
97
+ * @param {Amount} denominatorAmount
98
+ * @returns {Ratio}
99
+ */
100
+ export const makeRatioFromAmounts = (numeratorAmount, denominatorAmount) => {
101
+ AmountMath.coerce(numeratorAmount.brand, numeratorAmount);
102
+ AmountMath.coerce(denominatorAmount.brand, denominatorAmount);
103
+ return makeRatio(
104
+ // @ts-expect-error value can be any AmountValue but makeRatio() supports only bigint
105
+ numeratorAmount.value,
106
+ numeratorAmount.brand,
107
+ denominatorAmount.value,
108
+ denominatorAmount.brand,
109
+ );
110
+ };
111
+
112
+ /**
113
+ * @param {Amount<'nat'>} amount
114
+ * @param {Ratio} ratio
115
+ * @param {any} divideOp
116
+ * @returns {Amount<'nat'>}
117
+ */
118
+ const multiplyHelper = (amount, ratio, divideOp) => {
119
+ AmountMath.coerce(amount.brand, amount);
120
+ assertIsRatio(ratio);
121
+ amount.brand === ratio.denominator.brand ||
122
+ Fail`amount's brand ${q(amount.brand)} must match ratio's denominator ${q(
123
+ ratio.denominator.brand,
124
+ )}`;
125
+
126
+ return /** @type {Amount<'nat'>} */ (
127
+ AmountMath.make(
128
+ ratio.numerator.brand,
129
+ divideOp(
130
+ multiply(amount.value, ratio.numerator.value),
131
+ ratio.denominator.value,
132
+ ),
133
+ )
134
+ );
135
+ };
136
+
137
+ /** @type {ScaleAmount} */
138
+ export const floorMultiplyBy = (amount, ratio) => {
139
+ return multiplyHelper(amount, ratio, floorDivide);
140
+ };
141
+
142
+ /** @type {ScaleAmount} */
143
+ export const ceilMultiplyBy = (amount, ratio) => {
144
+ return multiplyHelper(amount, ratio, ceilDivide);
145
+ };
146
+
147
+ /** @type {ScaleAmount} */
148
+ export const multiplyBy = (amount, ratio) => {
149
+ return multiplyHelper(amount, ratio, bankersDivide);
150
+ };
151
+
152
+ /**
153
+ * @param {Amount<'nat'>} amount
154
+ * @param {Ratio} ratio
155
+ * @param {any} divideOp
156
+ * @returns {Amount<'nat'>}
157
+ */
158
+ const divideHelper = (amount, ratio, divideOp) => {
159
+ AmountMath.coerce(amount.brand, amount);
160
+ assertIsRatio(ratio);
161
+ amount.brand === ratio.numerator.brand ||
162
+ Fail`amount's brand ${q(amount.brand)} must match ratio's numerator ${q(
163
+ ratio.numerator.brand,
164
+ )}`;
165
+
166
+ return /** @type {Amount<'nat'>} */ (
167
+ AmountMath.make(
168
+ ratio.denominator.brand,
169
+ divideOp(
170
+ multiply(amount.value, ratio.denominator.value),
171
+ ratio.numerator.value,
172
+ ),
173
+ )
174
+ );
175
+ };
176
+
177
+ /**
178
+ * Divide the amount by the ratio, truncating the remainder.
179
+ *
180
+ * @type {ScaleAmount}
181
+ */
182
+ export const floorDivideBy = (amount, ratio) => {
183
+ return divideHelper(amount, ratio, floorDivide);
184
+ };
185
+
186
+ /**
187
+ * Divide the amount by the ratio, rounding up the remainder.
188
+ *
189
+ * @type {ScaleAmount}
190
+ */
191
+ export const ceilDivideBy = (amount, ratio) => {
192
+ return divideHelper(amount, ratio, ceilDivide);
193
+ };
194
+
195
+ /**
196
+ * Divide the amount by the ratio, rounding to nearest with ties to even (aka
197
+ * Banker's Rounding) as in IEEE 754 default rounding.
198
+ *
199
+ * @type {ScaleAmount}
200
+ */
201
+ export const divideBy = (amount, ratio) => {
202
+ return divideHelper(amount, ratio, bankersDivide);
203
+ };
204
+
205
+ /**
206
+ * @param {Ratio} ratio
207
+ * @returns {Ratio}
208
+ */
209
+ export const invertRatio = ratio => {
210
+ assertIsRatio(ratio);
211
+
212
+ return makeRatio(
213
+ ratio.denominator.value,
214
+ ratio.denominator.brand,
215
+ ratio.numerator.value,
216
+ ratio.numerator.brand,
217
+ );
218
+ };
219
+
220
+ /**
221
+ * @param {Ratio} left
222
+ * @param {Ratio} right
223
+ * @returns {Ratio}
224
+ */
225
+ export const addRatios = (left, right) => {
226
+ assertIsRatio(right);
227
+ assertIsRatio(left);
228
+ left.numerator.brand === right.numerator.brand ||
229
+ Fail`numerator brands must match: ${q(left)} ${q(right)}`;
230
+ left.denominator.brand === right.denominator.brand ||
231
+ Fail`denominator brands must match: ${q(left)} ${q(right)}`;
232
+
233
+ // Simplifying the expression:
234
+ // (and + bnd) / y d**2
235
+ // (a + b) nd / y d**2
236
+ // ((a + b) n / y d) * (d / d)
237
+ // (a + b) n / yd
238
+ return makeRatio(
239
+ add(
240
+ multiply(left.numerator.value, right.denominator.value), // a nd
241
+ multiply(left.denominator.value, right.numerator.value), // b nd
242
+ ), // (a + b) nd
243
+ left.numerator.brand,
244
+ multiply(left.denominator.value, right.denominator.value), // y d**2
245
+ left.denominator.brand,
246
+ );
247
+ };
248
+
249
+ /**
250
+ * @param {Ratio} left
251
+ * @param {Ratio} right
252
+ * @returns {Ratio}
253
+ */
254
+ export const subtractRatios = (left, right) => {
255
+ assertIsRatio(right);
256
+ assertIsRatio(left);
257
+ left.numerator.brand === right.numerator.brand ||
258
+ Fail`numerator brands must match: ${q(left)} ${q(right)}`;
259
+ left.denominator.brand === right.denominator.brand ||
260
+ Fail`denominator brands must match: ${q(left)} ${q(right)}`;
261
+
262
+ return makeRatio(
263
+ subtract(
264
+ multiply(left.numerator.value, right.denominator.value), // a nd
265
+ multiply(left.denominator.value, right.numerator.value), // b nd
266
+ ), // (a - b) nd
267
+ left.numerator.brand,
268
+ multiply(left.denominator.value, right.denominator.value), // y d**2
269
+ left.denominator.brand,
270
+ );
271
+ };
272
+
273
+ /**
274
+ * @param {Ratio} left
275
+ * @param {Ratio} right
276
+ * @returns {Ratio}
277
+ */
278
+ export const multiplyRatios = (left, right) => {
279
+ assertIsRatio(right);
280
+ assertIsRatio(left);
281
+
282
+ const getRemainingBrands = () => {
283
+ // Prefer results that have the same brand as the left operand.
284
+ if (right.numerator.brand === right.denominator.brand) {
285
+ return [left.numerator.brand, left.denominator.brand];
286
+ }
287
+ if (right.numerator.brand === left.denominator.brand) {
288
+ return [left.numerator.brand, right.denominator.brand];
289
+ }
290
+ if (left.numerator.brand === right.denominator.brand) {
291
+ return [right.numerator.brand, left.denominator.brand];
292
+ }
293
+ if (left.numerator.brand === left.denominator.brand) {
294
+ return [right.numerator.brand, right.denominator.brand];
295
+ }
296
+ throw Fail`at least one brand must cancel out: ${q(left)} ${q(right)}`;
297
+ };
298
+
299
+ const [numeratorBrand, denominatorBrand] = getRemainingBrands();
300
+ return makeRatio(
301
+ multiply(left.numerator.value, right.numerator.value),
302
+ numeratorBrand,
303
+ multiply(left.denominator.value, right.denominator.value),
304
+ denominatorBrand,
305
+ );
306
+ };
307
+
308
+ /**
309
+ * If ratio is between 0 and 1, subtract from 1.
310
+ *
311
+ * @param {Ratio} ratio
312
+ * @returns {Ratio}
313
+ */
314
+ export const oneMinus = ratio => {
315
+ assertIsRatio(ratio);
316
+ ratio.numerator.brand === ratio.denominator.brand ||
317
+ Fail`oneMinus only supports ratios with a single brand, but ${ratio.numerator.brand} doesn't match ${ratio.denominator.brand}`;
318
+ ratio.numerator.value <= ratio.denominator.value ||
319
+ Fail`Parameter must be less than or equal to 1: ${ratio.numerator.value}/${ratio.denominator.value}`;
320
+ return makeRatio(
321
+ subtract(ratio.denominator.value, ratio.numerator.value),
322
+ ratio.numerator.brand,
323
+ ratio.denominator.value,
324
+ ratio.numerator.brand,
325
+ );
326
+ };
327
+
328
+ /**
329
+ * @param {Ratio} left
330
+ * @param {Ratio} right
331
+ * @returns {boolean}
332
+ */
333
+ export const ratioGTE = (left, right) => {
334
+ if (left.numerator.brand === right.numerator.brand) {
335
+ left.denominator.brand === right.denominator.brand ||
336
+ Fail`numerator brands match, but denominator brands don't: ${q(left)} ${q(
337
+ right,
338
+ )}`;
339
+ } else if (left.numerator.brand === left.denominator.brand) {
340
+ right.numerator.brand === right.denominator.brand ||
341
+ Fail`lefthand brands match, but righthand brands don't: ${q(left)} ${q(
342
+ right,
343
+ )}`;
344
+ }
345
+ return natSafeMath.isGTE(
346
+ multiply(left.numerator.value, right.denominator.value),
347
+ multiply(right.numerator.value, left.denominator.value),
348
+ );
349
+ };
350
+
351
+ /**
352
+ * True iff the ratios are the same values (equal or equivalant may return
353
+ * false)
354
+ *
355
+ * @param {Ratio} left
356
+ * @param {Ratio} right
357
+ * @returns {boolean}
358
+ */
359
+ export const ratiosSame = (left, right) => {
360
+ return (
361
+ AmountMath.isEqual(left.numerator, right.numerator) &&
362
+ AmountMath.isEqual(left.denominator, right.denominator)
363
+ );
364
+ };
365
+
366
+ /**
367
+ * Make a new ratio with a smaller denominator that approximates the ratio. If
368
+ * the proposed denominator is larger than the current one, return the
369
+ * original.
370
+ *
371
+ * @param {Ratio} ratio
372
+ * @param {bigint} newDen
373
+ * @returns {Ratio}
374
+ */
375
+ export const quantize = (ratio, newDen) => {
376
+ const oldDen = ratio.denominator.value;
377
+ const oldNum = ratio.numerator.value;
378
+ if (newDen > oldDen) {
379
+ return ratio;
380
+ }
381
+
382
+ const newNum =
383
+ newDen === oldDen ? oldNum : bankersDivide(oldNum * newDen, oldDen);
384
+ return makeRatio(
385
+ newNum,
386
+ ratio.numerator.brand,
387
+ newDen,
388
+ ratio.denominator.brand,
389
+ );
390
+ };
391
+
392
+ const NUMERIC_RE = /^(\d\d*)(?:\.(\d*))?$/;
393
+ /** @typedef {bigint | number | string} ParsableNumber */
394
+
395
+ /**
396
+ * Create a ratio from a given numeric value.
397
+ *
398
+ * @param {ParsableNumber} numeric
399
+ * @param {Brand<'nat'>} numeratorBrand
400
+ * @param {Brand<'nat'>} [denominatorBrand]
401
+ * @returns {Ratio}
402
+ */
403
+ export const parseRatio = (
404
+ numeric,
405
+ numeratorBrand,
406
+ denominatorBrand = numeratorBrand,
407
+ ) => {
408
+ const match = `${numeric}`.match(NUMERIC_RE);
409
+ if (!match) {
410
+ throw Fail`Invalid numeric data: ${numeric}`;
411
+ }
412
+
413
+ const [_, whole, part = ''] = match;
414
+ return makeRatio(
415
+ BigInt(`${whole}${part}`),
416
+ numeratorBrand,
417
+ 10n ** BigInt(part.length),
418
+ denominatorBrand,
419
+ );
420
+ };
421
+
422
+ /**
423
+ * @param {unknown} specimen
424
+ * @returns {asserts specimen is ParsableNumber}
425
+ */
426
+ export const assertParsableNumber = specimen => {
427
+ const match = `${specimen}`.match(NUMERIC_RE);
428
+ match || Fail`Invalid numeric data: ${specimen}`;
429
+ };
430
+
431
+ /**
432
+ * Ratios might be greater or less than one.
433
+ *
434
+ * @param {Ratio} ratio
435
+ * @returns {number}
436
+ */
437
+ export const ratioToNumber = ratio => {
438
+ const n = Number(ratio.numerator.value);
439
+ const d = Number(ratio.denominator.value);
440
+ return n / d;
441
+ };
@@ -0,0 +1,11 @@
1
+ export namespace natSafeMath {
2
+ let add: NatOp;
3
+ let subtract: NatOp;
4
+ let multiply: NatOp;
5
+ let floorDivide: NatOp;
6
+ let ceilDivide: NatOp;
7
+ let bankersDivide: NatOp;
8
+ let isGTE: (x: number | bigint, y: number | bigint) => boolean;
9
+ }
10
+ export type NatOp = (x: number | bigint, y: number | bigint) => bigint;
11
+ //# sourceMappingURL=safeMath.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safeMath.d.ts","sourceRoot":"","sources":["safeMath.js"],"names":[],"mappings":";aAYa,KAAK;kBAGL,KAAK;kBAEL,KAAK;qBAEL,KAAK;oBAEL,KAAK;uBASN,KAAK;eAiBJ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,KAAK,OAAO;;oBA7ClD,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM"}