iso-price 1.0.3

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 (85) hide show
  1. package/LICENSE +21 -0
  2. package/dist/contract/index.d.ts +25 -0
  3. package/dist/contract/index.js +60 -0
  4. package/dist/contract/index.js.map +1 -0
  5. package/dist/domain.objects/IsoCurrency.d.ts +63 -0
  6. package/dist/domain.objects/IsoCurrency.js +70 -0
  7. package/dist/domain.objects/IsoCurrency.js.map +1 -0
  8. package/dist/domain.objects/IsoPrice.d.ts +16 -0
  9. package/dist/domain.objects/IsoPrice.js +3 -0
  10. package/dist/domain.objects/IsoPrice.js.map +1 -0
  11. package/dist/domain.objects/IsoPriceExponent.d.ts +28 -0
  12. package/dist/domain.objects/IsoPriceExponent.js +33 -0
  13. package/dist/domain.objects/IsoPriceExponent.js.map +1 -0
  14. package/dist/domain.objects/IsoPriceHuman.d.ts +19 -0
  15. package/dist/domain.objects/IsoPriceHuman.js +3 -0
  16. package/dist/domain.objects/IsoPriceHuman.js.map +1 -0
  17. package/dist/domain.objects/IsoPriceRoundMode.d.ts +23 -0
  18. package/dist/domain.objects/IsoPriceRoundMode.js +28 -0
  19. package/dist/domain.objects/IsoPriceRoundMode.js.map +1 -0
  20. package/dist/domain.objects/IsoPriceShape.d.ts +30 -0
  21. package/dist/domain.objects/IsoPriceShape.js +3 -0
  22. package/dist/domain.objects/IsoPriceShape.js.map +1 -0
  23. package/dist/domain.objects/IsoPriceWords.d.ts +20 -0
  24. package/dist/domain.objects/IsoPriceWords.js +3 -0
  25. package/dist/domain.objects/IsoPriceWords.js.map +1 -0
  26. package/dist/domain.operations/arithmetic/allocatePrice.d.ts +48 -0
  27. package/dist/domain.operations/arithmetic/allocatePrice.js +167 -0
  28. package/dist/domain.operations/arithmetic/allocatePrice.js.map +1 -0
  29. package/dist/domain.operations/arithmetic/dividePrice.d.ts +40 -0
  30. package/dist/domain.operations/arithmetic/dividePrice.js +127 -0
  31. package/dist/domain.operations/arithmetic/dividePrice.js.map +1 -0
  32. package/dist/domain.operations/arithmetic/multiplyPrice.d.ts +38 -0
  33. package/dist/domain.operations/arithmetic/multiplyPrice.js +89 -0
  34. package/dist/domain.operations/arithmetic/multiplyPrice.js.map +1 -0
  35. package/dist/domain.operations/arithmetic/subPrices.d.ts +28 -0
  36. package/dist/domain.operations/arithmetic/subPrices.js +62 -0
  37. package/dist/domain.operations/arithmetic/subPrices.js.map +1 -0
  38. package/dist/domain.operations/arithmetic/sumPrices.d.ts +44 -0
  39. package/dist/domain.operations/arithmetic/sumPrices.js +88 -0
  40. package/dist/domain.operations/arithmetic/sumPrices.js.map +1 -0
  41. package/dist/domain.operations/cast/asIsoPrice.d.ts +35 -0
  42. package/dist/domain.operations/cast/asIsoPrice.js +117 -0
  43. package/dist/domain.operations/cast/asIsoPrice.js.map +1 -0
  44. package/dist/domain.operations/cast/asIsoPriceHuman.d.ts +25 -0
  45. package/dist/domain.operations/cast/asIsoPriceHuman.js +106 -0
  46. package/dist/domain.operations/cast/asIsoPriceHuman.js.map +1 -0
  47. package/dist/domain.operations/cast/asIsoPriceShape.d.ts +25 -0
  48. package/dist/domain.operations/cast/asIsoPriceShape.js +164 -0
  49. package/dist/domain.operations/cast/asIsoPriceShape.js.map +1 -0
  50. package/dist/domain.operations/cast/asIsoPriceWords.d.ts +25 -0
  51. package/dist/domain.operations/cast/asIsoPriceWords.js +103 -0
  52. package/dist/domain.operations/cast/asIsoPriceWords.js.map +1 -0
  53. package/dist/domain.operations/guard/isIsoPrice.d.ts +18 -0
  54. package/dist/domain.operations/guard/isIsoPrice.js +29 -0
  55. package/dist/domain.operations/guard/isIsoPrice.js.map +1 -0
  56. package/dist/domain.operations/guard/isIsoPriceHuman.d.ts +24 -0
  57. package/dist/domain.operations/guard/isIsoPriceHuman.js +76 -0
  58. package/dist/domain.operations/guard/isIsoPriceHuman.js.map +1 -0
  59. package/dist/domain.operations/guard/isIsoPriceShape.d.ts +29 -0
  60. package/dist/domain.operations/guard/isIsoPriceShape.js +50 -0
  61. package/dist/domain.operations/guard/isIsoPriceShape.js.map +1 -0
  62. package/dist/domain.operations/guard/isIsoPriceWords.d.ts +24 -0
  63. package/dist/domain.operations/guard/isIsoPriceWords.js +48 -0
  64. package/dist/domain.operations/guard/isIsoPriceWords.js.map +1 -0
  65. package/dist/domain.operations/precision/getIsoPriceExponentByCurrency.d.ts +15 -0
  66. package/dist/domain.operations/precision/getIsoPriceExponentByCurrency.js +49 -0
  67. package/dist/domain.operations/precision/getIsoPriceExponentByCurrency.js.map +1 -0
  68. package/dist/domain.operations/precision/roundPrice.d.ts +25 -0
  69. package/dist/domain.operations/precision/roundPrice.js +24 -0
  70. package/dist/domain.operations/precision/roundPrice.js.map +1 -0
  71. package/dist/domain.operations/precision/setPricePrecision.d.ts +29 -0
  72. package/dist/domain.operations/precision/setPricePrecision.js +119 -0
  73. package/dist/domain.operations/precision/setPricePrecision.js.map +1 -0
  74. package/dist/domain.operations/statistics/calcPriceAvg.d.ts +21 -0
  75. package/dist/domain.operations/statistics/calcPriceAvg.js +92 -0
  76. package/dist/domain.operations/statistics/calcPriceAvg.js.map +1 -0
  77. package/dist/domain.operations/statistics/calcPriceStdev.d.ts +23 -0
  78. package/dist/domain.operations/statistics/calcPriceStdev.js +137 -0
  79. package/dist/domain.operations/statistics/calcPriceStdev.js.map +1 -0
  80. package/dist/index.d.ts +1 -0
  81. package/dist/index.js +18 -0
  82. package/dist/index.js.map +1 -0
  83. package/license.md +21 -0
  84. package/package.json +102 -0
  85. package/readme.md +373 -0
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.priceAllocate = exports.allocatePrice = void 0;
4
+ const helpful_errors_1 = require("helpful-errors");
5
+ const IsoPriceExponent_1 = require("../../domain.objects/IsoPriceExponent");
6
+ const asIsoPriceShape_1 = require("../cast/asIsoPriceShape");
7
+ const asIsoPriceWords_1 = require("../cast/asIsoPriceWords");
8
+ function allocatePrice(input, options) {
9
+ const shape = (0, asIsoPriceShape_1.asIsoPriceShape)(input.of);
10
+ const exponent = shape.exponent ?? IsoPriceExponent_1.IsoPriceExponent.CENTI;
11
+ // handle equal parts allocation
12
+ if ('parts' in input.into) {
13
+ const parts = input.into.parts;
14
+ if (parts < 1) {
15
+ throw new helpful_errors_1.BadRequestError('cannot allocate into less than 1 part', {
16
+ input,
17
+ });
18
+ }
19
+ return allocateEqualParts(shape.amount, shape.currency, exponent, parts, input.remainder, options?.format);
20
+ }
21
+ // handle ratio allocation
22
+ const ratios = input.into.ratios;
23
+ if (ratios.length < 1) {
24
+ throw new helpful_errors_1.BadRequestError('cannot allocate into less than 1 part', {
25
+ input,
26
+ });
27
+ }
28
+ return allocateByRatios(shape.amount, shape.currency, exponent, ratios, input.remainder, options?.format);
29
+ }
30
+ exports.allocatePrice = allocatePrice;
31
+ /**
32
+ * .what = allocates amount into equal parts
33
+ * .why = splits evenly with remainder distribution
34
+ */
35
+ const allocateEqualParts = (amount, currency, exponent, parts, remainderMode, format) => {
36
+ const divisor = BigInt(parts);
37
+ const baseAmount = amount / divisor;
38
+ const remainder = amount % divisor;
39
+ // create base allocations
40
+ const allocations = Array(parts).fill(baseAmount);
41
+ // distribute remainder
42
+ distributeRemainder(allocations, remainder, remainderMode, baseAmount);
43
+ return formatAllocations(allocations, currency, exponent, format);
44
+ };
45
+ /**
46
+ * .what = allocates amount by ratios
47
+ * .why = splits proportionally with remainder distribution
48
+ */
49
+ const allocateByRatios = (amount, currency, exponent, ratios, remainderMode, format) => {
50
+ // validate ratios
51
+ if (ratios.some((r) => r < 0)) {
52
+ throw new helpful_errors_1.BadRequestError('ratios must be non-negative', { ratios });
53
+ }
54
+ const totalRatio = ratios.reduce((sum, r) => sum + r, 0);
55
+ if (totalRatio === 0) {
56
+ throw new helpful_errors_1.BadRequestError('total ratio cannot be zero', { ratios });
57
+ }
58
+ // calculate base allocations via largest remainder method
59
+ const totalRatioBigInt = BigInt(totalRatio);
60
+ const allocations = [];
61
+ let allocated = 0n;
62
+ for (let i = 0; i < ratios.length; i++) {
63
+ const ratio = BigInt(ratios[i]);
64
+ const allocation = (amount * ratio) / totalRatioBigInt;
65
+ allocations.push(allocation);
66
+ allocated += allocation;
67
+ }
68
+ // calculate remainder from truncation
69
+ const remainder = amount - allocated;
70
+ // distribute remainder
71
+ distributeRemainder(allocations, remainder, remainderMode, 0n, // for ratio mode, pass 0 as baseAmount (not used for largest)
72
+ ratios);
73
+ return formatAllocations(allocations, currency, exponent, format);
74
+ };
75
+ /**
76
+ * .what = distributes remainder per mode
77
+ * .why = ensures no cent is lost in allocation
78
+ */
79
+ const distributeRemainder = (allocations, remainder, mode, baseAmount, ratios) => {
80
+ // handle negative remainder (can happen with negative amounts)
81
+ const isNegative = remainder < 0n;
82
+ const absRemainder = isNegative ? -remainder : remainder;
83
+ const increment = isNegative ? -1n : 1n;
84
+ // get indices for distribution
85
+ const indices = getDistributionIndices(allocations.length, Number(absRemainder), mode, allocations, ratios);
86
+ // apply remainder distribution
87
+ for (const idx of indices) {
88
+ allocations[idx] = allocations[idx] + increment;
89
+ }
90
+ };
91
+ /**
92
+ * .what = determines which indices receive remainder cents
93
+ * .why = implements different distribution strategies
94
+ */
95
+ const getDistributionIndices = (length, count, mode, allocations, ratios) => {
96
+ if (count === 0)
97
+ return [];
98
+ if (count >= length) {
99
+ // each gets at least one
100
+ const indices = Array.from({ length }, (_, i) => i);
101
+ // distribute extra to first positions
102
+ const extra = count - length;
103
+ for (let i = 0; i < extra; i++) {
104
+ indices.push(i % length);
105
+ }
106
+ return indices;
107
+ }
108
+ switch (mode) {
109
+ case 'first':
110
+ return Array.from({ length: count }, (_, i) => i);
111
+ case 'last':
112
+ return Array.from({ length: count }, (_, i) => length - 1 - i);
113
+ case 'largest': {
114
+ // hamilton/largest remainder method: give to those with largest fractional parts
115
+ // for equal parts, all have same fractional part, so fall back to first
116
+ // for ratios, calculate fractional remainders
117
+ if (!ratios) {
118
+ return Array.from({ length: count }, (_, i) => i);
119
+ }
120
+ // calculate fractional remainders for each ratio
121
+ const totalRatio = ratios.reduce((sum, r) => sum + r, 0);
122
+ const fractions = ratios.map((r, i) => ({
123
+ index: i,
124
+ fraction: (r / totalRatio) * Number(allocations.reduce((a, b) => a + b, 0n)) -
125
+ Number(allocations[i]),
126
+ }));
127
+ // sort by fraction (largest first)
128
+ fractions.sort((a, b) => b.fraction - a.fraction);
129
+ return fractions.slice(0, count).map((f) => f.index);
130
+ }
131
+ case 'random': {
132
+ // pseudo-random but deterministic based on allocation values
133
+ const indices = Array.from({ length }, (_, i) => i);
134
+ // use a simple deterministic shuffle based on allocation sum
135
+ const seed = Number(allocations.reduce((a, b) => a + b, 0n) % 1000000n);
136
+ indices.sort((a, b) => {
137
+ const hashA = (a * 31 + seed) % 1000;
138
+ const hashB = (b * 31 + seed) % 1000;
139
+ return hashA - hashB;
140
+ });
141
+ return indices.slice(0, count);
142
+ }
143
+ default:
144
+ return Array.from({ length: count }, (_, i) => i);
145
+ }
146
+ };
147
+ /**
148
+ * .what = converts bigint allocations to output format
149
+ * .why = enables both words and shape output
150
+ */
151
+ const formatAllocations = (allocations, currency, exponent, format) => {
152
+ const shapes = allocations.map((amount) => ({
153
+ amount,
154
+ currency,
155
+ exponent: exponent,
156
+ }));
157
+ if (format === 'shape') {
158
+ return shapes;
159
+ }
160
+ return shapes.map((s) => (0, asIsoPriceWords_1.asIsoPriceWords)(s));
161
+ };
162
+ /**
163
+ * .what = alias for allocatePrice
164
+ * .why = provides price-prefixed variant
165
+ */
166
+ exports.priceAllocate = allocatePrice;
167
+ //# sourceMappingURL=allocatePrice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allocatePrice.js","sourceRoot":"","sources":["../../../src/domain.operations/arithmetic/allocatePrice.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { IsoPrice } from '../../domain.objects/IsoPrice';
2
+ import { IsoPriceRoundMode } from '../../domain.objects/IsoPriceRoundMode';
3
+ import type { IsoPriceShape } from '../../domain.objects/IsoPriceShape';
4
+ import type { IsoPriceWords } from '../../domain.objects/IsoPriceWords';
5
+ /**
6
+ * .what = divides a price by a scalar
7
+ * .why = enables per-unit calculations and cost breakdowns
8
+ *
9
+ * auto-scales precision when divisors are large.
10
+ * for small divisors, maintains input precision with round.
11
+ *
12
+ * @throws BadRequestError if divisor is zero
13
+ *
14
+ * @example
15
+ * dividePrice({ of: 'USD 10.00', by: 4 })
16
+ * // => 'USD 2.50'
17
+ *
18
+ * @example
19
+ * dividePrice({ of: '$0.25', by: 1_000_000 })
20
+ * // => 'USD 0.000_000_250' (nano precision)
21
+ */
22
+ export declare function dividePrice<TCurrency extends string = string>(input: {
23
+ of: IsoPrice<TCurrency> | string;
24
+ by: number;
25
+ }, options?: {
26
+ format?: 'words';
27
+ round?: IsoPriceRoundMode;
28
+ }): IsoPriceWords<TCurrency>;
29
+ export declare function dividePrice<TCurrency extends string = string>(input: {
30
+ of: IsoPrice<TCurrency> | string;
31
+ by: number;
32
+ }, options: {
33
+ format: 'shape';
34
+ round?: IsoPriceRoundMode;
35
+ }): IsoPriceShape<TCurrency>;
36
+ /**
37
+ * .what = alias for dividePrice
38
+ * .why = provides price-prefixed variant
39
+ */
40
+ export declare const priceDivide: typeof dividePrice;
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.priceDivide = exports.dividePrice = void 0;
4
+ const helpful_errors_1 = require("helpful-errors");
5
+ const IsoPriceExponent_1 = require("../../domain.objects/IsoPriceExponent");
6
+ const IsoPriceRoundMode_1 = require("../../domain.objects/IsoPriceRoundMode");
7
+ const asIsoPriceShape_1 = require("../cast/asIsoPriceShape");
8
+ const asIsoPriceWords_1 = require("../cast/asIsoPriceWords");
9
+ /**
10
+ * .what = gets the numeric exponent value from exponent string
11
+ * .why = needed for precision calculation
12
+ */
13
+ const getExponentValue = (exponent) => {
14
+ const match = exponent.match(/\^(-?\d+)$/);
15
+ if (!match)
16
+ return -2; // default to centi
17
+ return parseInt(match[1], 10);
18
+ };
19
+ /**
20
+ * .what = determines required precision based on divisor
21
+ * .why = ensures result has meaningful precision for large divisors
22
+ */
23
+ const getRequiredExponent = (inputExponent, divisor) => {
24
+ const absDivisor = Math.abs(divisor);
25
+ // for small divisors (< 100), maintain input precision with round
26
+ if (absDivisor < 100)
27
+ return inputExponent;
28
+ // for medium divisors (100 - 999,999), use milli precision
29
+ if (absDivisor < 1000000)
30
+ return IsoPriceExponent_1.IsoPriceExponent.MILLI;
31
+ // for large divisors (1M - 999M), use nano precision
32
+ if (absDivisor < 1000000000)
33
+ return IsoPriceExponent_1.IsoPriceExponent.NANO;
34
+ // for huge divisors, use pico precision
35
+ return IsoPriceExponent_1.IsoPriceExponent.PICO;
36
+ };
37
+ function dividePrice(input, options) {
38
+ if (input.by === 0) {
39
+ throw new helpful_errors_1.BadRequestError('cannot divide by zero', { input });
40
+ }
41
+ const shape = (0, asIsoPriceShape_1.asIsoPriceShape)(input.of);
42
+ const inputExponent = shape.exponent ?? IsoPriceExponent_1.IsoPriceExponent.CENTI;
43
+ const roundMode = options?.round ?? IsoPriceRoundMode_1.IsoPriceRoundMode.HALF_UP;
44
+ // determine output precision based on divisor
45
+ const outputExponent = getRequiredExponent(inputExponent, input.by);
46
+ const outputExpValue = getExponentValue(outputExponent);
47
+ const inputExpValue = getExponentValue(inputExponent);
48
+ // scale amount to output precision
49
+ const scaleDiff = inputExpValue - outputExpValue;
50
+ const scaledAmount = shape.amount * 10n ** BigInt(scaleDiff);
51
+ // perform division with round
52
+ const divisor = BigInt(Math.round(Math.abs(input.by)));
53
+ const sign = input.by < 0 ? -1n : 1n;
54
+ let quotient;
55
+ const remainder = scaledAmount % divisor;
56
+ // apply round mode
57
+ if (remainder === 0n) {
58
+ quotient = scaledAmount / divisor;
59
+ }
60
+ else {
61
+ const baseQuotient = scaledAmount / divisor;
62
+ const isNegative = scaledAmount < 0n;
63
+ const absRemainder = remainder < 0n ? -remainder : remainder;
64
+ // use doubled remainder for accurate half comparison (avoids truncation from divisor/2)
65
+ const doubledRemainder = absRemainder * 2n;
66
+ const isExactlyHalf = doubledRemainder === divisor;
67
+ const isMoreThanHalf = doubledRemainder > divisor;
68
+ switch (roundMode) {
69
+ case IsoPriceRoundMode_1.IsoPriceRoundMode.FLOOR:
70
+ quotient = isNegative ? baseQuotient - 1n : baseQuotient;
71
+ break;
72
+ case IsoPriceRoundMode_1.IsoPriceRoundMode.CEIL:
73
+ quotient = isNegative ? baseQuotient : baseQuotient + 1n;
74
+ break;
75
+ case IsoPriceRoundMode_1.IsoPriceRoundMode.HALF_UP:
76
+ quotient =
77
+ isExactlyHalf || isMoreThanHalf
78
+ ? isNegative
79
+ ? baseQuotient - 1n
80
+ : baseQuotient + 1n
81
+ : baseQuotient;
82
+ break;
83
+ case IsoPriceRoundMode_1.IsoPriceRoundMode.HALF_DOWN:
84
+ quotient = isMoreThanHalf
85
+ ? isNegative
86
+ ? baseQuotient - 1n
87
+ : baseQuotient + 1n
88
+ : baseQuotient;
89
+ break;
90
+ case IsoPriceRoundMode_1.IsoPriceRoundMode.HALF_EVEN: {
91
+ const isEven = baseQuotient % 2n === 0n;
92
+ if (isExactlyHalf) {
93
+ quotient = isEven ? baseQuotient : baseQuotient + 1n;
94
+ }
95
+ else if (isMoreThanHalf) {
96
+ quotient = isNegative ? baseQuotient - 1n : baseQuotient + 1n;
97
+ }
98
+ else {
99
+ quotient = baseQuotient;
100
+ }
101
+ break;
102
+ }
103
+ default:
104
+ quotient = baseQuotient;
105
+ }
106
+ }
107
+ // apply divisor sign
108
+ quotient = quotient * sign;
109
+ // build result shape
110
+ const resultShape = {
111
+ amount: quotient,
112
+ currency: shape.currency,
113
+ exponent: outputExponent,
114
+ };
115
+ // return in requested format
116
+ if (options?.format === 'shape') {
117
+ return resultShape;
118
+ }
119
+ return (0, asIsoPriceWords_1.asIsoPriceWords)(resultShape);
120
+ }
121
+ exports.dividePrice = dividePrice;
122
+ /**
123
+ * .what = alias for dividePrice
124
+ * .why = provides price-prefixed variant
125
+ */
126
+ exports.priceDivide = dividePrice;
127
+ //# sourceMappingURL=dividePrice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dividePrice.js","sourceRoot":"","sources":["../../../src/domain.operations/arithmetic/dividePrice.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { IsoPrice } from '../../domain.objects/IsoPrice';
2
+ import { IsoPriceRoundMode } from '../../domain.objects/IsoPriceRoundMode';
3
+ import type { IsoPriceShape } from '../../domain.objects/IsoPriceShape';
4
+ import type { IsoPriceWords } from '../../domain.objects/IsoPriceWords';
5
+ /**
6
+ * .what = multiplies a price by a scalar
7
+ * .why = enables quantity calculations and percentage markups
8
+ *
9
+ * maintains input precision by default. uses higher internal precision
10
+ * to avoid intermediate loss, then rounds back to input precision.
11
+ *
12
+ * @example
13
+ * multiplyPrice({ of: 'USD 10.00', by: 3 })
14
+ * // => 'USD 30.00'
15
+ *
16
+ * @example
17
+ * multiplyPrice({ of: 'USD 100.00', by: 1.08 })
18
+ * // => 'USD 108.00' (tax markup)
19
+ */
20
+ export declare function multiplyPrice<TCurrency extends string = string>(input: {
21
+ of: IsoPrice<TCurrency> | string;
22
+ by: number;
23
+ }, options?: {
24
+ format?: 'words';
25
+ round?: IsoPriceRoundMode;
26
+ }): IsoPriceWords<TCurrency>;
27
+ export declare function multiplyPrice<TCurrency extends string = string>(input: {
28
+ of: IsoPrice<TCurrency> | string;
29
+ by: number;
30
+ }, options: {
31
+ format: 'shape';
32
+ round?: IsoPriceRoundMode;
33
+ }): IsoPriceShape<TCurrency>;
34
+ /**
35
+ * .what = alias for multiplyPrice
36
+ * .why = provides price-prefixed variant
37
+ */
38
+ export declare const priceMultiply: typeof multiplyPrice;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.priceMultiply = exports.multiplyPrice = void 0;
4
+ const IsoPriceExponent_1 = require("../../domain.objects/IsoPriceExponent");
5
+ const IsoPriceRoundMode_1 = require("../../domain.objects/IsoPriceRoundMode");
6
+ const asIsoPriceShape_1 = require("../cast/asIsoPriceShape");
7
+ const asIsoPriceWords_1 = require("../cast/asIsoPriceWords");
8
+ /**
9
+ * .what = gets the numeric exponent value from exponent string
10
+ * .why = needed for precision scale
11
+ */
12
+ const getExponentValue = (exponent) => {
13
+ const match = exponent.match(/\^(-?\d+)$/);
14
+ if (!match)
15
+ return -2; // default to centi
16
+ return parseInt(match[1], 10);
17
+ };
18
+ /**
19
+ * .what = applies round mode to a bigint
20
+ * .why = needed to round back to input precision
21
+ */
22
+ const applyRound = (amount, divisor, mode) => {
23
+ const quotient = amount / divisor;
24
+ const remainder = amount % divisor;
25
+ if (remainder === 0n)
26
+ return quotient;
27
+ const isNegative = amount < 0n;
28
+ const absRemainder = remainder < 0n ? -remainder : remainder;
29
+ const halfDivisor = divisor / 2n;
30
+ switch (mode) {
31
+ case IsoPriceRoundMode_1.IsoPriceRoundMode.FLOOR:
32
+ return isNegative ? quotient - 1n : quotient;
33
+ case IsoPriceRoundMode_1.IsoPriceRoundMode.CEIL:
34
+ return isNegative ? quotient : quotient + 1n;
35
+ case IsoPriceRoundMode_1.IsoPriceRoundMode.HALF_UP:
36
+ return absRemainder >= halfDivisor
37
+ ? isNegative
38
+ ? quotient - 1n
39
+ : quotient + 1n
40
+ : quotient;
41
+ case IsoPriceRoundMode_1.IsoPriceRoundMode.HALF_DOWN:
42
+ return absRemainder > halfDivisor
43
+ ? isNegative
44
+ ? quotient - 1n
45
+ : quotient + 1n
46
+ : quotient;
47
+ case IsoPriceRoundMode_1.IsoPriceRoundMode.HALF_EVEN: {
48
+ const isEven = quotient % 2n === 0n;
49
+ if (absRemainder === halfDivisor) {
50
+ return isEven ? quotient : quotient + 1n;
51
+ }
52
+ return absRemainder > halfDivisor
53
+ ? isNegative
54
+ ? quotient - 1n
55
+ : quotient + 1n
56
+ : quotient;
57
+ }
58
+ default:
59
+ return quotient;
60
+ }
61
+ };
62
+ function multiplyPrice(input, options) {
63
+ const shape = (0, asIsoPriceShape_1.asIsoPriceShape)(input.of);
64
+ const inputExponent = shape.exponent ?? IsoPriceExponent_1.IsoPriceExponent.CENTI;
65
+ const roundMode = options?.round ?? IsoPriceRoundMode_1.IsoPriceRoundMode.HALF_UP;
66
+ // scale multiplier to avoid float precision issues (use 12 decimal places)
67
+ const scaleFactor = BigInt(Math.round(input.by * 1e12));
68
+ const multiplied = shape.amount * scaleFactor;
69
+ // round back to original precision
70
+ const rounded = applyRound(multiplied, BigInt(1e12), roundMode);
71
+ // build result shape
72
+ const resultShape = {
73
+ amount: rounded,
74
+ currency: shape.currency,
75
+ exponent: inputExponent,
76
+ };
77
+ // return in requested format
78
+ if (options?.format === 'shape') {
79
+ return resultShape;
80
+ }
81
+ return (0, asIsoPriceWords_1.asIsoPriceWords)(resultShape);
82
+ }
83
+ exports.multiplyPrice = multiplyPrice;
84
+ /**
85
+ * .what = alias for multiplyPrice
86
+ * .why = provides price-prefixed variant
87
+ */
88
+ exports.priceMultiply = multiplyPrice;
89
+ //# sourceMappingURL=multiplyPrice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multiplyPrice.js","sourceRoot":"","sources":["../../../src/domain.operations/arithmetic/multiplyPrice.ts"],"names":[],"mappings":";;;AACA,4EAAyE;AACzE,8EAA2E;AAG3E,6DAA0D;AAC1D,6DAA0D;AAE1D;;;GAGG;AACH,MAAM,gBAAgB,GAAG,CAAC,QAAmC,EAAU,EAAE;IACvE,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC,CAAC,CAAC,mBAAmB;IAC1C,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAC;AACjC,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,GAAG,CACjB,MAAc,EACd,OAAe,EACf,IAAuB,EACf,EAAE;IACV,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAClC,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IAEnC,IAAI,SAAS,KAAK,EAAE;QAAE,OAAO,QAAQ,CAAC;IAEtC,MAAM,UAAU,GAAG,MAAM,GAAG,EAAE,CAAC;IAC/B,MAAM,YAAY,GAAG,SAAS,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7D,MAAM,WAAW,GAAG,OAAO,GAAG,EAAE,CAAC;IAEjC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,qCAAiB,CAAC,KAAK;YAC1B,OAAO,UAAU,CAAC,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC/C,KAAK,qCAAiB,CAAC,IAAI;YACzB,OAAO,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC;QAC/C,KAAK,qCAAiB,CAAC,OAAO;YAC5B,OAAO,YAAY,IAAI,WAAW;gBAChC,CAAC,CAAC,UAAU;oBACV,CAAC,CAAC,QAAQ,GAAG,EAAE;oBACf,CAAC,CAAC,QAAQ,GAAG,EAAE;gBACjB,CAAC,CAAC,QAAQ,CAAC;QACf,KAAK,qCAAiB,CAAC,SAAS;YAC9B,OAAO,YAAY,GAAG,WAAW;gBAC/B,CAAC,CAAC,UAAU;oBACV,CAAC,CAAC,QAAQ,GAAG,EAAE;oBACf,CAAC,CAAC,QAAQ,GAAG,EAAE;gBACjB,CAAC,CAAC,QAAQ,CAAC;QACf,KAAK,qCAAiB,CAAC,SAAS,CAAC,CAAC,CAAC;YACjC,MAAM,MAAM,GAAG,QAAQ,GAAG,EAAE,KAAK,EAAE,CAAC;YACpC,IAAI,YAAY,KAAK,WAAW,EAAE,CAAC;gBACjC,OAAO,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC;YAC3C,CAAC;YACD,OAAO,YAAY,GAAG,WAAW;gBAC/B,CAAC,CAAC,UAAU;oBACV,CAAC,CAAC,QAAQ,GAAG,EAAE;oBACf,CAAC,CAAC,QAAQ,GAAG,EAAE;gBACjB,CAAC,CAAC,QAAQ,CAAC;QACf,CAAC;QACD;YACE,OAAO,QAAQ,CAAC;IACpB,CAAC;AACH,CAAC,CAAC;AAyBF,SAAgB,aAAa,CAC3B,KAAuD,EACvD,OAAmE;IAEnE,MAAM,KAAK,GAAG,IAAA,iCAAe,EAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACxC,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ,IAAI,mCAAgB,CAAC,KAAK,CAAC;IAC/D,MAAM,SAAS,GAAG,OAAO,EAAE,KAAK,IAAI,qCAAiB,CAAC,OAAO,CAAC;IAE9D,2EAA2E;IAC3E,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,GAAG,WAAW,CAAC;IAE9C,mCAAmC;IACnC,MAAM,OAAO,GAAG,UAAU,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;IAEhE,qBAAqB;IACrB,MAAM,WAAW,GAA6B;QAC5C,MAAM,EAAE,OAAO;QACf,QAAQ,EAAE,KAAK,CAAC,QAAqB;QACrC,QAAQ,EAAE,aAAa;KACxB,CAAC;IAEF,6BAA6B;IAC7B,IAAI,OAAO,EAAE,MAAM,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,OAAO,IAAA,iCAAe,EAAC,WAAW,CAAC,CAAC;AACtC,CAAC;AA3BD,sCA2BC;AAED;;;GAGG;AACU,QAAA,aAAa,GAAG,aAAa,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { IsoPrice } from '../../domain.objects/IsoPrice';
2
+ import type { IsoPriceShape } from '../../domain.objects/IsoPriceShape';
3
+ import type { IsoPriceWords } from '../../domain.objects/IsoPriceWords';
4
+ /**
5
+ * .what = subtracts second price from first
6
+ * .why = enables price difference computation
7
+ *
8
+ * @throws BadRequestError if currencies do not match
9
+ *
10
+ * @example
11
+ * subPrices('USD 50.00', 'USD 20.00')
12
+ * // => 'USD 30.00'
13
+ *
14
+ * @example
15
+ * subPrices('USD 10.00', 'USD 50.00')
16
+ * // => 'USD -40.00' (negative allowed)
17
+ */
18
+ export declare function subPrices<TCurrency extends string = string>(minuend: IsoPrice<TCurrency> | string, subtrahend: IsoPrice<TCurrency> | string, options?: {
19
+ format?: 'words';
20
+ }): IsoPriceWords<TCurrency>;
21
+ export declare function subPrices<TCurrency extends string = string>(minuend: IsoPrice<TCurrency> | string, subtrahend: IsoPrice<TCurrency> | string, options: {
22
+ format: 'shape';
23
+ }): IsoPriceShape<TCurrency>;
24
+ /**
25
+ * .what = alias for subPrices
26
+ * .why = provides price-prefixed variant
27
+ */
28
+ export declare const priceSub: typeof subPrices;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.priceSub = exports.subPrices = void 0;
4
+ const helpful_errors_1 = require("helpful-errors");
5
+ const IsoPriceExponent_1 = require("../../domain.objects/IsoPriceExponent");
6
+ const asIsoPriceShape_1 = require("../cast/asIsoPriceShape");
7
+ const asIsoPriceWords_1 = require("../cast/asIsoPriceWords");
8
+ /**
9
+ * .what = gets the numeric exponent value from exponent string
10
+ * .why = needed for precision comparison and normalization
11
+ */
12
+ const getExponentValue = (exponent) => {
13
+ const match = exponent.match(/\^(-?\d+)$/);
14
+ if (!match)
15
+ return -2; // default to centi
16
+ return parseInt(match[1], 10);
17
+ };
18
+ function subPrices(minuend, subtrahend, options) {
19
+ // convert to shapes
20
+ const minuendShape = (0, asIsoPriceShape_1.asIsoPriceShape)(minuend);
21
+ const subtrahendShape = (0, asIsoPriceShape_1.asIsoPriceShape)(subtrahend);
22
+ // verify currencies match
23
+ if (minuendShape.currency !== subtrahendShape.currency) {
24
+ throw new helpful_errors_1.BadRequestError('currency mismatch in price subtraction', {
25
+ minuend: minuendShape.currency,
26
+ subtrahend: subtrahendShape.currency,
27
+ });
28
+ }
29
+ const currency = minuendShape.currency;
30
+ // find the highest precision (lowest exponent value)
31
+ const minuendExp = getExponentValue(minuendShape.exponent ?? IsoPriceExponent_1.IsoPriceExponent.CENTI);
32
+ const subtrahendExp = getExponentValue(subtrahendShape.exponent ?? IsoPriceExponent_1.IsoPriceExponent.CENTI);
33
+ const targetExponentValue = Math.min(minuendExp, subtrahendExp);
34
+ const targetExponent = minuendExp <= subtrahendExp
35
+ ? (minuendShape.exponent ?? IsoPriceExponent_1.IsoPriceExponent.CENTI)
36
+ : (subtrahendShape.exponent ?? IsoPriceExponent_1.IsoPriceExponent.CENTI);
37
+ // normalize amounts to target precision
38
+ const minuendShift = minuendExp - targetExponentValue;
39
+ const subtrahendShift = subtrahendExp - targetExponentValue;
40
+ const normalizedMinuend = minuendShape.amount * 10n ** BigInt(minuendShift);
41
+ const normalizedSubtrahend = subtrahendShape.amount * 10n ** BigInt(subtrahendShift);
42
+ // compute difference
43
+ const difference = normalizedMinuend - normalizedSubtrahend;
44
+ // build result shape
45
+ const resultShape = {
46
+ amount: difference,
47
+ currency,
48
+ exponent: targetExponent,
49
+ };
50
+ // return in requested format
51
+ if (options?.format === 'shape') {
52
+ return resultShape;
53
+ }
54
+ return (0, asIsoPriceWords_1.asIsoPriceWords)(resultShape);
55
+ }
56
+ exports.subPrices = subPrices;
57
+ /**
58
+ * .what = alias for subPrices
59
+ * .why = provides price-prefixed variant
60
+ */
61
+ exports.priceSub = subPrices;
62
+ //# sourceMappingURL=subPrices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subPrices.js","sourceRoot":"","sources":["../../../src/domain.operations/arithmetic/subPrices.ts"],"names":[],"mappings":";;;AAAA,mDAAiD;AAGjD,4EAAyE;AAGzE,6DAA0D;AAC1D,6DAA0D;AAE1D;;;GAGG;AACH,MAAM,gBAAgB,GAAG,CAAC,QAAmC,EAAU,EAAE;IACvE,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC,CAAC,CAAC,mBAAmB;IAC1C,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAC;AACjC,CAAC,CAAC;AA0BF,SAAgB,SAAS,CACvB,OAAqC,EACrC,UAAwC,EACxC,OAAwC;IAExC,oBAAoB;IACpB,MAAM,YAAY,GAAG,IAAA,iCAAe,EAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,eAAe,GAAG,IAAA,iCAAe,EAAC,UAAU,CAAC,CAAC;IAEpD,0BAA0B;IAC1B,IAAI,YAAY,CAAC,QAAQ,KAAK,eAAe,CAAC,QAAQ,EAAE,CAAC;QACvD,MAAM,IAAI,gCAAe,CAAC,wCAAwC,EAAE;YAClE,OAAO,EAAE,YAAY,CAAC,QAAQ;YAC9B,UAAU,EAAE,eAAe,CAAC,QAAQ;SACrC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAqB,CAAC;IAEpD,qDAAqD;IACrD,MAAM,UAAU,GAAG,gBAAgB,CACjC,YAAY,CAAC,QAAQ,IAAI,mCAAgB,CAAC,KAAK,CAChD,CAAC;IACF,MAAM,aAAa,GAAG,gBAAgB,CACpC,eAAe,CAAC,QAAQ,IAAI,mCAAgB,CAAC,KAAK,CACnD,CAAC;IACF,MAAM,mBAAmB,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;IAChE,MAAM,cAAc,GAClB,UAAU,IAAI,aAAa;QACzB,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,IAAI,mCAAgB,CAAC,KAAK,CAAC;QACnD,CAAC,CAAC,CAAC,eAAe,CAAC,QAAQ,IAAI,mCAAgB,CAAC,KAAK,CAAC,CAAC;IAE3D,wCAAwC;IACxC,MAAM,YAAY,GAAG,UAAU,GAAG,mBAAmB,CAAC;IACtD,MAAM,eAAe,GAAG,aAAa,GAAG,mBAAmB,CAAC;IAC5D,MAAM,iBAAiB,GAAG,YAAY,CAAC,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC;IAC5E,MAAM,oBAAoB,GACxB,eAAe,CAAC,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC,eAAe,CAAC,CAAC;IAE1D,qBAAqB;IACrB,MAAM,UAAU,GAAG,iBAAiB,GAAG,oBAAoB,CAAC;IAE5D,qBAAqB;IACrB,MAAM,WAAW,GAA6B;QAC5C,MAAM,EAAE,UAAU;QAClB,QAAQ;QACR,QAAQ,EAAE,cAAkC;KAC7C,CAAC;IAEF,6BAA6B;IAC7B,IAAI,OAAO,EAAE,MAAM,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,OAAO,IAAA,iCAAe,EAAC,WAAW,CAAC,CAAC;AACtC,CAAC;AAtDD,8BAsDC;AAED;;;GAGG;AACU,QAAA,QAAQ,GAAG,SAAS,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { IsoPrice } from '../../domain.objects/IsoPrice';
2
+ import type { IsoPriceShape } from '../../domain.objects/IsoPriceShape';
3
+ import type { IsoPriceWords } from '../../domain.objects/IsoPriceWords';
4
+ /**
5
+ * .what = sums multiple prices
6
+ * .why = enables price aggregation with mixed precision support
7
+ *
8
+ * supports two call signatures:
9
+ * - spread: `sumPrices('USD 10.00', 'USD 20.00')`
10
+ * - array: `sumPrices(['USD 10.00', 'USD 20.00'])`
11
+ * - with options: `sumPrices(['USD 10.00'], { format: 'shape' })`
12
+ *
13
+ * @throws BadRequestError if currencies do not match
14
+ *
15
+ * @example
16
+ * sumPrices('USD 10.00', 'USD 20.00')
17
+ * // => 'USD 30.00'
18
+ *
19
+ * @example
20
+ * sumPrices(['USD 10.00', 'USD 20.00'], { format: 'shape' })
21
+ * // => { amount: 3000n, currency: 'USD' }
22
+ */
23
+ export declare function sumPrices<TCurrency extends string = string>(...args: (IsoPrice<TCurrency> | string)[]): IsoPriceWords<TCurrency>;
24
+ export declare function sumPrices<TCurrency extends string = string>(prices: (IsoPrice<TCurrency> | string)[], options?: {
25
+ format?: 'words';
26
+ }): IsoPriceWords<TCurrency>;
27
+ export declare function sumPrices<TCurrency extends string = string>(prices: (IsoPrice<TCurrency> | string)[], options: {
28
+ format: 'shape';
29
+ }): IsoPriceShape<TCurrency>;
30
+ /**
31
+ * .what = alias for sumPrices
32
+ * .why = provides semantic name for binary addition
33
+ */
34
+ export declare const addPrices: typeof sumPrices;
35
+ /**
36
+ * .what = alias for sumPrices
37
+ * .why = provides price-prefixed variant
38
+ */
39
+ export declare const priceSum: typeof sumPrices;
40
+ /**
41
+ * .what = alias for sumPrices
42
+ * .why = provides price-prefixed variant
43
+ */
44
+ export declare const priceAdd: typeof sumPrices;