@swapkit/helpers 1.0.0-rc.2 → 1.0.0-rc.22

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.
@@ -1,11 +1,59 @@
1
- import { DEFAULT_DECIMAL, formatBigIntToSafeValue } from '../helpers/number.ts';
1
+ import { BaseDecimal } from '@swapkit/types';
2
2
 
3
- type AllowedValueType = bigint | number | string;
4
- type ArithmeticMethod = 'add' | 'sub' | 'mul' | 'div';
3
+ import type { SwapKitNumber } from './swapKitNumber.ts';
5
4
 
5
+ type NumberPrimitivesType = {
6
+ bigint: bigint;
7
+ number: number;
8
+ string: string;
9
+ };
10
+ export type NumberPrimitives = bigint | number | string;
11
+ type InitialisationValueType = NumberPrimitives | BigIntArithmetics | SwapKitNumber;
12
+
13
+ type SKBigIntParams = InitialisationValueType | { decimal?: number; value: number | string };
14
+
15
+ const DEFAULT_DECIMAL = 8;
6
16
  const toMultiplier = (decimal: number) => 10n ** BigInt(decimal);
7
17
  const decimalFromMultiplier = (multiplier: bigint) => Math.log10(parseFloat(multiplier.toString()));
8
18
 
19
+ export function formatBigIntToSafeValue({
20
+ value,
21
+ bigIntDecimal = DEFAULT_DECIMAL,
22
+ decimal = DEFAULT_DECIMAL,
23
+ }: {
24
+ value: bigint;
25
+ bigIntDecimal?: number;
26
+ decimal?: number;
27
+ }) {
28
+ const isNegative = value < 0n;
29
+ let valueString = value.toString().substring(isNegative ? 1 : 0);
30
+
31
+ const padLength = decimal - (valueString.length - 1);
32
+
33
+ if (padLength > 0) {
34
+ valueString = '0'.repeat(padLength) + valueString;
35
+ }
36
+
37
+ const decimalIndex = valueString.length - decimal;
38
+ let decimalString = valueString.slice(-decimal);
39
+
40
+ // Check if we need to round up
41
+ if (parseInt(decimalString[bigIntDecimal]) >= 5) {
42
+ // Increment the last decimal place and slice off the rest
43
+ decimalString = `${decimalString.substring(0, bigIntDecimal - 1)}${(
44
+ parseInt(decimalString[bigIntDecimal - 1]) + 1
45
+ ).toString()}`;
46
+ } else {
47
+ // Just slice off the extra digits
48
+ decimalString = decimalString.substring(0, bigIntDecimal);
49
+ }
50
+
51
+ return `${isNegative ? '-' : ''}${valueString.slice(0, decimalIndex)}.${decimalString}`.replace(
52
+ /\.?0*$/,
53
+ '',
54
+ );
55
+ }
56
+
9
57
  export class BigIntArithmetics {
10
58
  decimalMultiplier: bigint = 10n ** 8n;
11
59
  bigIntValue: bigint = 0n;
@@ -23,26 +71,19 @@ export class BigIntArithmetics {
23
71
  from,
24
72
  to,
25
73
  }: {
26
- value: BigIntArithmetics | string | number;
74
+ value: InstanceType<typeof SwapKitNumber>;
27
75
  from: number;
28
76
  to: number;
29
77
  }) {
30
- return BigIntArithmetics.fromBigInt(
31
- (new BigIntArithmetics(value).bigIntValue * toMultiplier(to)) / toMultiplier(from),
78
+ return this.fromBigInt(
79
+ (value.getBaseValue('bigint') * toMultiplier(to)) / toMultiplier(from),
32
80
  to,
33
81
  );
34
82
  }
35
83
 
36
- constructor(
37
- valueOrParams:
38
- | BigIntArithmetics
39
- | string
40
- | number
41
- | { decimal?: number; value: number | string },
42
- ) {
43
- const complexInit = typeof valueOrParams === 'object';
44
- const value = complexInit ? valueOrParams.value : valueOrParams;
45
- this.decimal = complexInit ? valueOrParams.decimal : undefined;
84
+ constructor(params: SKBigIntParams) {
85
+ const value = getStringValue(params);
86
+ this.decimal = typeof params === 'object' ? params.decimal : undefined;
46
87
 
47
88
  // use the multiplier to keep track of decimal point - defaults to 8 if lower than 8
48
89
  this.decimalMultiplier = toMultiplier(
@@ -51,58 +92,82 @@ export class BigIntArithmetics {
51
92
  this.#setValue(value);
52
93
  }
53
94
 
54
- get unsafeNumber() {
55
- return parseFloat((this.bigIntValue / this.decimalMultiplier).toString());
56
- }
57
- get baseValue() {
58
- return this.#getBaseValue('string') as string;
59
- }
60
- get baseValueNumber() {
61
- return this.#getBaseValue('number') as number;
62
- }
63
- get baseValueBigInt() {
64
- return this.#getBaseValue('number') as bigint;
65
- }
66
- get value() {
67
- return this.formatBigIntToSafeValue(
68
- this.bigIntValue,
69
- this.decimal || decimalFromMultiplier(this.decimalMultiplier),
70
- );
95
+ set(value: SKBigIntParams): this {
96
+ // @ts-expect-error False positive
97
+ return new this.constructor({ decimal: this.decimal, value, identifier: this.toString() });
71
98
  }
72
-
73
- add(...args: (BigIntArithmetics | string | number)[]) {
99
+ add(...args: InitialisationValueType[]) {
74
100
  return this.#arithmetics('add', ...args);
75
101
  }
76
- sub(...args: (BigIntArithmetics | string | number)[]) {
102
+ sub(...args: InitialisationValueType[]) {
77
103
  return this.#arithmetics('sub', ...args);
78
104
  }
79
- mul(...args: (BigIntArithmetics | string | number)[]) {
105
+ mul(...args: InitialisationValueType[]) {
80
106
  return this.#arithmetics('mul', ...args);
81
107
  }
82
- div(...args: (BigIntArithmetics | string | number)[]) {
108
+ div(...args: InitialisationValueType[]) {
83
109
  return this.#arithmetics('div', ...args);
84
110
  }
85
- gt(value: BigIntArithmetics | string | number) {
111
+ gt(value: InitialisationValueType) {
86
112
  return this.bigIntValue > this.getBigIntValue(value);
87
113
  }
88
- gte(value: BigIntArithmetics | string | number) {
114
+ gte(value: InitialisationValueType) {
89
115
  return this.bigIntValue >= this.getBigIntValue(value);
90
116
  }
91
- lt(value: BigIntArithmetics | string | number) {
117
+ lt(value: InitialisationValueType) {
92
118
  return this.bigIntValue < this.getBigIntValue(value);
93
119
  }
94
- lte(value: BigIntArithmetics | string | number) {
120
+ lte(value: InitialisationValueType) {
95
121
  return this.bigIntValue <= this.getBigIntValue(value);
96
122
  }
97
- eqValue(value: BigIntArithmetics | string | number) {
123
+ eqValue(value: InitialisationValueType) {
98
124
  return this.bigIntValue === this.getBigIntValue(value);
99
125
  }
100
126
 
101
- getBigIntValue(value: BigIntArithmetics | string | number, decimal?: number) {
127
+ getValue<T extends 'number' | 'string'>(type: T): NumberPrimitivesType[T] {
128
+ const value = this.formatBigIntToSafeValue(
129
+ this.bigIntValue,
130
+ this.decimal || decimalFromMultiplier(this.decimalMultiplier),
131
+ );
132
+
133
+ switch (type) {
134
+ case 'number':
135
+ // @ts-expect-error False positive
136
+ return Number(value);
137
+ case 'string':
138
+ // @ts-expect-error False positive
139
+ return value;
140
+ default:
141
+ // @ts-expect-error False positive
142
+ return (this.bigIntValue * BigInt(this.decimal || 8n)) / this.decimalMultiplier;
143
+ }
144
+ }
145
+
146
+ getBaseValue<T extends 'number' | 'string' | 'bigint'>(type: T): NumberPrimitivesType[T] {
147
+ const divisor = this.decimalMultiplier / toMultiplier(this.decimal || BaseDecimal.THOR);
148
+ const baseValue = this.bigIntValue / divisor;
149
+
150
+ switch (type) {
151
+ case 'number':
152
+ // @ts-expect-error False positive
153
+ return Number(baseValue);
154
+ case 'string':
155
+ // @ts-expect-error False positive
156
+ return baseValue.toString();
157
+ default:
158
+ // @ts-expect-error False positive
159
+ return baseValue;
160
+ }
161
+ }
162
+
163
+ getBigIntValue(value: InitialisationValueType, decimal?: number) {
102
164
  if (!decimal && typeof value === 'object') return value.bigIntValue;
103
165
 
104
- value = typeof value === 'object' ? value.value : value;
105
- return this.#toBigInt(this.#toSafeValue(value), decimal);
166
+ const stringValue = getStringValue(value);
167
+ const safeValue = this.#toSafeValue(stringValue);
168
+
169
+ if (safeValue === '0' || safeValue === 'undefined') return 0n;
170
+ return this.#toBigInt(safeValue, decimal);
106
171
  }
107
172
 
108
173
  formatBigIntToSafeValue(value: bigint, decimal?: number) {
@@ -113,16 +178,13 @@ export class BigIntArithmetics {
113
178
  );
114
179
  const isNegative = value < 0n;
115
180
 
116
- let valueString = value.toString().substring(isNegative ? 1 : 0);
117
-
181
+ const valueString = value.toString().substring(isNegative ? 1 : 0);
118
182
  const padLength = decimalToUseForConversion - (valueString.length - 1);
119
183
 
120
- if (padLength > 0) {
121
- valueString = '0'.repeat(padLength) + valueString;
122
- }
184
+ const parsedValueString = padLength > 0 ? '0'.repeat(padLength) + valueString : valueString;
123
185
 
124
- const decimalIndex = valueString.length - decimalToUseForConversion;
125
- let decimalString = valueString.slice(-decimalToUseForConversion);
186
+ const decimalIndex = parsedValueString.length - decimalToUseForConversion;
187
+ let decimalString = parsedValueString.slice(-decimalToUseForConversion);
126
188
 
127
189
  // Check if we need to round up
128
190
  if (parseInt(decimalString[bigIntDecimal]) >= 5) {
@@ -135,54 +197,129 @@ export class BigIntArithmetics {
135
197
  decimalString = decimalString.substring(0, bigIntDecimal);
136
198
  }
137
199
 
138
- return `${isNegative ? '-' : ''}${valueString.slice(0, decimalIndex)}.${decimalString}`.replace(
139
- /\.?0*$/,
140
- '',
141
- );
200
+ return `${isNegative ? '-' : ''}${parsedValueString.slice(
201
+ 0,
202
+ decimalIndex,
203
+ )}.${decimalString}`.replace(/\.?0*$/, '');
142
204
  }
143
205
 
144
- toSignificant(significantDigits?: number) {
145
- const value = this.value.split('.');
146
- const integer = value[0];
147
- const decimal = value[1];
206
+ toSignificant(significantDigits: number = 6) {
207
+ const [int, dec] = this.getValue('string').split('.');
208
+ const integer = int || '';
209
+ const decimal = dec || '';
210
+ const valueLength = parseInt(integer) ? integer.length + decimal.length : decimal.length;
211
+
212
+ if (valueLength <= significantDigits) {
213
+ return this.getValue('string');
214
+ }
215
+
216
+ if (integer.length >= significantDigits) {
217
+ return integer.slice(0, significantDigits).padEnd(integer.length, '0');
218
+ }
148
219
 
149
- if (decimal) {
150
- return `${integer}.${decimal.slice(0, significantDigits || this.decimal)}`.replace(
151
- /\.?0*$/,
152
- '',
220
+ if (parseInt(integer)) {
221
+ return `${integer}.${decimal.slice(0, significantDigits - integer.length)}`.padEnd(
222
+ significantDigits - integer.length,
223
+ '0',
153
224
  );
154
225
  }
155
226
 
156
- return integer;
227
+ const trimmedDecimal = parseInt(decimal);
228
+ const slicedDecimal = `${trimmedDecimal}`.slice(0, significantDigits);
229
+
230
+ return `0.${slicedDecimal.padStart(
231
+ decimal.length - `${trimmedDecimal}`.length + slicedDecimal.length,
232
+ '0',
233
+ )}`;
234
+ }
235
+
236
+ toFixed(fixedDigits: number = 6) {
237
+ const [int, dec] = this.getValue('string').split('.');
238
+ const integer = int || '';
239
+ const decimal = dec || '';
240
+
241
+ if (parseInt(integer)) {
242
+ return `${integer}.${decimal.slice(0, fixedDigits)}`.padEnd(fixedDigits, '0');
243
+ }
244
+
245
+ const trimmedDecimal = parseInt(decimal);
246
+ const slicedDecimal = `${trimmedDecimal}`.slice(0, fixedDigits);
247
+
248
+ return `0.${slicedDecimal.padStart(
249
+ decimal.length - `${trimmedDecimal}`.length + slicedDecimal.length,
250
+ '0',
251
+ )}`;
252
+ }
253
+
254
+ toAbbreviation(digits = 2) {
255
+ const value = this.getValue('number');
256
+ const abbreviations = ['', 'K', 'M', 'B', 'T', 'Q', 'Qi', 'S'];
257
+ const tier = Math.floor(Math.log10(Math.abs(value)) / 3);
258
+ const suffix = abbreviations[tier];
259
+
260
+ if (!suffix) return this.getValue('string');
261
+
262
+ const scale = 10 ** (tier * 3);
263
+ const scaled = value / scale;
264
+
265
+ return `${scaled.toFixed(digits)}${suffix}`;
266
+ }
267
+
268
+ toCurrency(
269
+ currency = '$',
270
+ {
271
+ currencyPosition = 'start',
272
+ decimal = 2,
273
+ decimalSeparator = '.',
274
+ thousandSeparator = ',',
275
+ } = {},
276
+ ) {
277
+ const value = this.getValue('number');
278
+ const [int, dec = ''] = value.toFixed(6).split('.');
279
+ const integer = int.replace(/\B(?=(\d{3})+(?!\d))/g, thousandSeparator);
280
+
281
+ const parsedValue =
282
+ !int && !dec
283
+ ? '0.00'
284
+ : int === '0'
285
+ ? `${parseFloat(`0.${dec}`)}`.replace('.', decimalSeparator)
286
+ : `${integer}${dec ? `${decimalSeparator}${dec.slice(0, decimal)}` : ''}`;
287
+
288
+ return `${currencyPosition === 'start' ? currency : ''}${parsedValue}${
289
+ currencyPosition === 'end' ? currency : ''
290
+ }`;
157
291
  }
158
292
 
159
- #arithmetics(method: ArithmeticMethod, ...args: (BigIntArithmetics | string | number)[]): this {
293
+ #arithmetics(method: 'add' | 'sub' | 'mul' | 'div', ...args: InitialisationValueType[]): this {
160
294
  const precisionDecimal = this.#retrievePrecisionDecimal(this, ...args);
161
295
  const precisionDecimalMultiplier = toMultiplier(precisionDecimal);
162
296
 
163
297
  const result = args.reduce(
164
- (acc, arg) => {
298
+ (acc: bigint, arg) => {
165
299
  const value = this.getBigIntValue(arg, precisionDecimal);
166
300
 
167
- if ('div' === method && value === 0n) throw new RangeError('Division by zero');
168
-
169
- /**
170
- * Normal arithmetic - add & sub => 200000000n +- 200000000n
171
- */
172
- if ('add' === method) return acc + value;
173
- if ('sub' === method) return acc - value;
174
-
175
- /**
176
- * Multiplication & division would end up with wrong result if we don't adjust the value
177
- * 200000000n * 200000000n => 40000000000000000n
178
- * 200000000n / 200000000n => 1n
179
- * So we do the following:
180
- * 200000000n * 200000000n = 40000000000000000n / 100000000n (decimals) => 400000000n
181
- * (200000000n * 100000000n (decimals)) / 200000000n => 100000000n
182
- */
183
- if ('mul' === method) return (acc * value) / precisionDecimalMultiplier;
184
- // 'div' === method
185
- return (acc * precisionDecimalMultiplier) / value;
301
+ switch (method) {
302
+ case 'add':
303
+ return acc + value;
304
+ case 'sub':
305
+ return acc - value;
306
+ /**
307
+ * Multiplication & division would end up with wrong result if we don't adjust the value
308
+ * 200000000n * 200000000n => 40000000000000000n
309
+ * 200000000n / 200000000n => 1n
310
+ * So we do the following:
311
+ * 200000000n * 200000000n = 40000000000000000n / 100000000n (decimals) => 400000000n
312
+ * (200000000n * 100000000n (decimals)) / 200000000n => 100000000n
313
+ */
314
+ case 'mul':
315
+ return (acc * value) / precisionDecimalMultiplier;
316
+ case 'div': {
317
+ if (value === 0n) throw new RangeError('Division by zero');
318
+ return (acc * precisionDecimalMultiplier) / value;
319
+ }
320
+ default:
321
+ return acc;
322
+ }
186
323
  },
187
324
  //normalize is to precision multiplier base
188
325
  (this.bigIntValue * precisionDecimalMultiplier) / this.decimalMultiplier,
@@ -194,16 +331,16 @@ export class BigIntArithmetics {
194
331
  value: result,
195
332
  });
196
333
 
197
- // @ts-expect-error - we know that the constructor is the same as the current class
334
+ // @ts-expect-error False positive
198
335
  return new this.constructor({ decimal: this.decimal, value, identifier: this.toString() });
199
336
  }
200
337
 
201
- #setValue(value: AllowedValueType, bigIntValue?: bigint) {
338
+ #setValue(value: InitialisationValueType) {
202
339
  const safeValue = this.#toSafeValue(value) || '0';
203
- this.bigIntValue = bigIntValue || this.#toBigInt(safeValue);
340
+ this.bigIntValue = this.#toBigInt(safeValue);
204
341
  }
205
342
 
206
- #retrievePrecisionDecimal(...args: (BigIntArithmetics | AllowedValueType)[]) {
343
+ #retrievePrecisionDecimal(...args: InitialisationValueType[]) {
207
344
  const decimals = args
208
345
  .map((arg) =>
209
346
  typeof arg === 'object'
@@ -217,19 +354,19 @@ export class BigIntArithmetics {
217
354
  #toBigInt(value: string, decimal?: number) {
218
355
  const multiplier = decimal ? toMultiplier(decimal) : this.decimalMultiplier;
219
356
  const padDecimal = decimalFromMultiplier(multiplier);
220
- const [integerPart, decimalPart = ''] = value.split('.');
357
+ const [integerPart = '', decimalPart = ''] = value.split('.');
221
358
 
222
359
  return BigInt(`${integerPart}${decimalPart.padEnd(padDecimal, '0')}`);
223
360
  }
224
361
 
225
- #toSafeValue(value: AllowedValueType) {
362
+ #toSafeValue(value: InitialisationValueType) {
226
363
  const parsedValue =
227
364
  typeof value === 'number'
228
365
  ? Number(value).toLocaleString('fullwide', {
229
366
  useGrouping: false,
230
367
  maximumFractionDigits: 20,
231
368
  })
232
- : value;
369
+ : getStringValue(value);
233
370
 
234
371
  const splitValue = `${parsedValue}`.replaceAll(',', '.').split('.');
235
372
 
@@ -242,18 +379,12 @@ export class BigIntArithmetics {
242
379
  const decimals = value.split('.')[1]?.length || 0;
243
380
  return Math.max(decimals, DEFAULT_DECIMAL);
244
381
  }
382
+ }
245
383
 
246
- #getBaseValue<T extends 'number' | 'string' | 'bigint'>(type: T) {
247
- const divisor = this.decimalMultiplier / toMultiplier(this.decimal || 0);
248
- const baseValue = this.bigIntValue / divisor;
249
-
250
- switch (type) {
251
- case 'number':
252
- return Number(baseValue);
253
- case 'string':
254
- return baseValue.toString();
255
- default:
256
- return baseValue;
257
- }
258
- }
384
+ function getStringValue(value: SKBigIntParams) {
385
+ return typeof value === 'object'
386
+ ? 'getValue' in value
387
+ ? value.getValue('string')
388
+ : value.value
389
+ : value;
259
390
  }
@@ -1,4 +1,4 @@
1
- import { BigIntArithmetics } from './bigIntArithmetics.ts';
1
+ import { BigIntArithmetics, formatBigIntToSafeValue } from './bigIntArithmetics.ts';
2
2
 
3
3
  export type SwapKitValueType = BigIntArithmetics | string | number;
4
4
 
@@ -7,7 +7,10 @@ export class SwapKitNumber extends BigIntArithmetics {
7
7
  return this.eqValue(value);
8
8
  }
9
9
 
10
- toString() {
11
- return this.value;
10
+ static fromBigInt(value: bigint, decimal?: number) {
11
+ return new SwapKitNumber({
12
+ decimal,
13
+ value: formatBigIntToSafeValue({ value, bigIntDecimal: decimal, decimal }),
14
+ });
12
15
  }
13
16
  }
@@ -1,40 +0,0 @@
1
- // THORChain base amount is 1e8
2
- export const DEFAULT_DECIMAL = 8;
3
-
4
- export const formatBigIntToSafeValue = ({
5
- value,
6
- bigIntDecimal = DEFAULT_DECIMAL,
7
- decimal = DEFAULT_DECIMAL,
8
- }: {
9
- value: bigint;
10
- bigIntDecimal?: number;
11
- decimal?: number;
12
- }) => {
13
- const isNegative = value < 0n;
14
- let valueString = value.toString().substring(isNegative ? 1 : 0);
15
-
16
- const padLength = decimal - (valueString.length - 1);
17
-
18
- if (padLength > 0) {
19
- valueString = '0'.repeat(padLength) + valueString;
20
- }
21
-
22
- const decimalIndex = valueString.length - decimal;
23
- let decimalString = valueString.slice(-decimal);
24
-
25
- // Check if we need to round up
26
- if (parseInt(decimalString[bigIntDecimal]) >= 5) {
27
- // Increment the last decimal place and slice off the rest
28
- decimalString = `${decimalString.substring(0, bigIntDecimal - 1)}${(
29
- parseInt(decimalString[bigIntDecimal - 1]) + 1
30
- ).toString()}`;
31
- } else {
32
- // Just slice off the extra digits
33
- decimalString = decimalString.substring(0, bigIntDecimal);
34
- }
35
-
36
- return `${isNegative ? '-' : ''}${valueString.slice(0, decimalIndex)}.${decimalString}`.replace(
37
- /\.?0*$/,
38
- '',
39
- );
40
- };