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

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,101 @@ export class BigIntArithmetics {
51
92
  this.#setValue(value);
52
93
  }
53
94
 
54
- get unsafeNumber() {
55
- return parseFloat((this.bigIntValue / this.decimalMultiplier).toString());
56
- }
95
+ /**
96
+ * @deprecated Use `getBaseValue('string')` instead
97
+ */
57
98
  get baseValue() {
58
- return this.#getBaseValue('string') as string;
99
+ return this.getBaseValue('string') as string;
59
100
  }
101
+ /**
102
+ * @deprecated Use `getBaseValue('number')` instead
103
+ */
60
104
  get baseValueNumber() {
61
- return this.#getBaseValue('number') as number;
105
+ return this.getBaseValue('number') as number;
62
106
  }
107
+ /**
108
+ * @deprecated Use `getBaseValue('bigint')` instead
109
+ */
63
110
  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
- );
111
+ return this.getBaseValue('bigint') as bigint;
71
112
  }
72
113
 
73
- add(...args: (BigIntArithmetics | string | number)[]) {
114
+ set(value: SKBigIntParams): this {
115
+ // @ts-expect-error False positive
116
+ return new this.constructor({ decimal: this.decimal, value, identifier: this.toString() });
117
+ }
118
+ add(...args: InitialisationValueType[]) {
74
119
  return this.#arithmetics('add', ...args);
75
120
  }
76
- sub(...args: (BigIntArithmetics | string | number)[]) {
121
+ sub(...args: InitialisationValueType[]) {
77
122
  return this.#arithmetics('sub', ...args);
78
123
  }
79
- mul(...args: (BigIntArithmetics | string | number)[]) {
124
+ mul(...args: InitialisationValueType[]) {
80
125
  return this.#arithmetics('mul', ...args);
81
126
  }
82
- div(...args: (BigIntArithmetics | string | number)[]) {
127
+ div(...args: InitialisationValueType[]) {
83
128
  return this.#arithmetics('div', ...args);
84
129
  }
85
- gt(value: BigIntArithmetics | string | number) {
130
+ gt(value: InitialisationValueType) {
86
131
  return this.bigIntValue > this.getBigIntValue(value);
87
132
  }
88
- gte(value: BigIntArithmetics | string | number) {
133
+ gte(value: InitialisationValueType) {
89
134
  return this.bigIntValue >= this.getBigIntValue(value);
90
135
  }
91
- lt(value: BigIntArithmetics | string | number) {
136
+ lt(value: InitialisationValueType) {
92
137
  return this.bigIntValue < this.getBigIntValue(value);
93
138
  }
94
- lte(value: BigIntArithmetics | string | number) {
139
+ lte(value: InitialisationValueType) {
95
140
  return this.bigIntValue <= this.getBigIntValue(value);
96
141
  }
97
- eqValue(value: BigIntArithmetics | string | number) {
142
+ eqValue(value: InitialisationValueType) {
98
143
  return this.bigIntValue === this.getBigIntValue(value);
99
144
  }
100
145
 
101
- getBigIntValue(value: BigIntArithmetics | string | number, decimal?: number) {
146
+ getValue<T extends 'number' | 'string'>(type: T): NumberPrimitivesType[T] {
147
+ const value = this.formatBigIntToSafeValue(
148
+ this.bigIntValue,
149
+ this.decimal || decimalFromMultiplier(this.decimalMultiplier),
150
+ );
151
+
152
+ switch (type) {
153
+ case 'number':
154
+ // @ts-expect-error False positive
155
+ return Number(value);
156
+ case 'string':
157
+ // @ts-expect-error False positive
158
+ return value;
159
+ default:
160
+ // @ts-expect-error False positive
161
+ return (this.bigIntValue * BigInt(this.decimal || 8n)) / this.decimalMultiplier;
162
+ }
163
+ }
164
+
165
+ getBaseValue<T extends 'number' | 'string' | 'bigint'>(type: T): NumberPrimitivesType[T] {
166
+ const divisor = this.decimalMultiplier / toMultiplier(this.decimal || BaseDecimal.THOR);
167
+ const baseValue = this.bigIntValue / divisor;
168
+
169
+ switch (type) {
170
+ case 'number':
171
+ // @ts-expect-error False positive
172
+ return Number(baseValue);
173
+ case 'string':
174
+ // @ts-expect-error False positive
175
+ return baseValue.toString();
176
+ default:
177
+ // @ts-expect-error False positive
178
+ return baseValue;
179
+ }
180
+ }
181
+
182
+ getBigIntValue(value: InitialisationValueType, decimal?: number) {
102
183
  if (!decimal && typeof value === 'object') return value.bigIntValue;
103
184
 
104
- value = typeof value === 'object' ? value.value : value;
105
- return this.#toBigInt(this.#toSafeValue(value), decimal);
185
+ const stringValue = getStringValue(value);
186
+ const safeValue = this.#toSafeValue(stringValue);
187
+
188
+ if (safeValue === '0' || safeValue === 'undefined') return 0n;
189
+ return this.#toBigInt(safeValue, decimal);
106
190
  }
107
191
 
108
192
  formatBigIntToSafeValue(value: bigint, decimal?: number) {
@@ -113,16 +197,13 @@ export class BigIntArithmetics {
113
197
  );
114
198
  const isNegative = value < 0n;
115
199
 
116
- let valueString = value.toString().substring(isNegative ? 1 : 0);
117
-
200
+ const valueString = value.toString().substring(isNegative ? 1 : 0);
118
201
  const padLength = decimalToUseForConversion - (valueString.length - 1);
119
202
 
120
- if (padLength > 0) {
121
- valueString = '0'.repeat(padLength) + valueString;
122
- }
203
+ const parsedValueString = padLength > 0 ? '0'.repeat(padLength) + valueString : valueString;
123
204
 
124
- const decimalIndex = valueString.length - decimalToUseForConversion;
125
- let decimalString = valueString.slice(-decimalToUseForConversion);
205
+ const decimalIndex = parsedValueString.length - decimalToUseForConversion;
206
+ let decimalString = parsedValueString.slice(-decimalToUseForConversion);
126
207
 
127
208
  // Check if we need to round up
128
209
  if (parseInt(decimalString[bigIntDecimal]) >= 5) {
@@ -135,54 +216,104 @@ export class BigIntArithmetics {
135
216
  decimalString = decimalString.substring(0, bigIntDecimal);
136
217
  }
137
218
 
138
- return `${isNegative ? '-' : ''}${valueString.slice(0, decimalIndex)}.${decimalString}`.replace(
139
- /\.?0*$/,
140
- '',
141
- );
219
+ return `${isNegative ? '-' : ''}${parsedValueString.slice(
220
+ 0,
221
+ decimalIndex,
222
+ )}.${decimalString}`.replace(/\.?0*$/, '');
142
223
  }
143
224
 
144
- toSignificant(significantDigits?: number) {
145
- const value = this.value.split('.');
146
- const integer = value[0];
147
- const decimal = value[1];
225
+ toSignificant(significantDigits: number = 6) {
226
+ const [int, dec] = this.getValue('string').split('.');
227
+ const integer = int || '';
228
+ const decimal = dec || '';
229
+ const valueLength = parseInt(integer) ? integer.length + decimal.length : decimal.length;
230
+
231
+ if (valueLength <= significantDigits) {
232
+ return this.getValue('string');
233
+ }
234
+
235
+ if (integer.length >= significantDigits) {
236
+ return integer.slice(0, significantDigits).padEnd(integer.length, '0');
237
+ }
148
238
 
149
- if (decimal) {
150
- return `${integer}.${decimal.slice(0, significantDigits || this.decimal)}`.replace(
151
- /\.?0*$/,
152
- '',
239
+ if (parseInt(integer)) {
240
+ return `${integer}.${decimal.slice(0, significantDigits - integer.length)}`.padEnd(
241
+ significantDigits - integer.length,
242
+ '0',
153
243
  );
154
244
  }
155
245
 
156
- return integer;
246
+ const trimmedDecimal = parseInt(decimal);
247
+ const slicedDecimal = `${trimmedDecimal}`.slice(0, significantDigits);
248
+
249
+ return `0.${slicedDecimal.padStart(
250
+ decimal.length - `${trimmedDecimal}`.length + slicedDecimal.length,
251
+ '0',
252
+ )}`;
253
+ }
254
+
255
+ toFixed(fixedDigits: number = 6) {
256
+ const [int, dec] = this.getValue('string').split('.');
257
+ const integer = int || '';
258
+ const decimal = dec || '';
259
+
260
+ if (parseInt(integer)) {
261
+ return `${integer}.${decimal.slice(0, fixedDigits)}`.padEnd(fixedDigits, '0');
262
+ }
263
+
264
+ const trimmedDecimal = parseInt(decimal);
265
+ const slicedDecimal = `${trimmedDecimal}`.slice(0, fixedDigits);
266
+
267
+ return `0.${slicedDecimal.padStart(
268
+ decimal.length - `${trimmedDecimal}`.length + slicedDecimal.length,
269
+ '0',
270
+ )}`;
157
271
  }
158
272
 
159
- #arithmetics(method: ArithmeticMethod, ...args: (BigIntArithmetics | string | number)[]): this {
273
+ toAbbreviation(digits = 2) {
274
+ const value = this.getValue('number');
275
+ const abbreviations = ['', 'K', 'M', 'B', 'T', 'Q', 'Qi', 'S'];
276
+ const tier = Math.floor(Math.log10(Math.abs(value)) / 3);
277
+ const suffix = abbreviations[tier];
278
+
279
+ if (!suffix) return this.getValue('string');
280
+
281
+ const scale = 10 ** (tier * 3);
282
+ const scaled = value / scale;
283
+
284
+ return `${scaled.toFixed(digits)}${suffix}`;
285
+ }
286
+
287
+ #arithmetics(method: 'add' | 'sub' | 'mul' | 'div', ...args: InitialisationValueType[]): this {
160
288
  const precisionDecimal = this.#retrievePrecisionDecimal(this, ...args);
161
289
  const precisionDecimalMultiplier = toMultiplier(precisionDecimal);
162
290
 
163
291
  const result = args.reduce(
164
- (acc, arg) => {
292
+ (acc: bigint, arg) => {
165
293
  const value = this.getBigIntValue(arg, precisionDecimal);
166
294
 
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;
295
+ switch (method) {
296
+ case 'add':
297
+ return acc + value;
298
+ case 'sub':
299
+ return acc - value;
300
+ /**
301
+ * Multiplication & division would end up with wrong result if we don't adjust the value
302
+ * 200000000n * 200000000n => 40000000000000000n
303
+ * 200000000n / 200000000n => 1n
304
+ * So we do the following:
305
+ * 200000000n * 200000000n = 40000000000000000n / 100000000n (decimals) => 400000000n
306
+ * (200000000n * 100000000n (decimals)) / 200000000n => 100000000n
307
+ */
308
+ case 'mul':
309
+ return (acc * value) / precisionDecimalMultiplier;
310
+ case 'div': {
311
+ if (value === 0n) throw new RangeError('Division by zero');
312
+ return (acc * precisionDecimalMultiplier) / value;
313
+ }
314
+ default:
315
+ return acc;
316
+ }
186
317
  },
187
318
  //normalize is to precision multiplier base
188
319
  (this.bigIntValue * precisionDecimalMultiplier) / this.decimalMultiplier,
@@ -194,16 +325,16 @@ export class BigIntArithmetics {
194
325
  value: result,
195
326
  });
196
327
 
197
- // @ts-expect-error - we know that the constructor is the same as the current class
328
+ // @ts-expect-error False positive
198
329
  return new this.constructor({ decimal: this.decimal, value, identifier: this.toString() });
199
330
  }
200
331
 
201
- #setValue(value: AllowedValueType, bigIntValue?: bigint) {
332
+ #setValue(value: InitialisationValueType) {
202
333
  const safeValue = this.#toSafeValue(value) || '0';
203
- this.bigIntValue = bigIntValue || this.#toBigInt(safeValue);
334
+ this.bigIntValue = this.#toBigInt(safeValue);
204
335
  }
205
336
 
206
- #retrievePrecisionDecimal(...args: (BigIntArithmetics | AllowedValueType)[]) {
337
+ #retrievePrecisionDecimal(...args: InitialisationValueType[]) {
207
338
  const decimals = args
208
339
  .map((arg) =>
209
340
  typeof arg === 'object'
@@ -217,19 +348,19 @@ export class BigIntArithmetics {
217
348
  #toBigInt(value: string, decimal?: number) {
218
349
  const multiplier = decimal ? toMultiplier(decimal) : this.decimalMultiplier;
219
350
  const padDecimal = decimalFromMultiplier(multiplier);
220
- const [integerPart, decimalPart = ''] = value.split('.');
351
+ const [integerPart = '', decimalPart = ''] = value.split('.');
221
352
 
222
353
  return BigInt(`${integerPart}${decimalPart.padEnd(padDecimal, '0')}`);
223
354
  }
224
355
 
225
- #toSafeValue(value: AllowedValueType) {
356
+ #toSafeValue(value: InitialisationValueType) {
226
357
  const parsedValue =
227
358
  typeof value === 'number'
228
359
  ? Number(value).toLocaleString('fullwide', {
229
360
  useGrouping: false,
230
361
  maximumFractionDigits: 20,
231
362
  })
232
- : value;
363
+ : getStringValue(value);
233
364
 
234
365
  const splitValue = `${parsedValue}`.replaceAll(',', '.').split('.');
235
366
 
@@ -242,18 +373,12 @@ export class BigIntArithmetics {
242
373
  const decimals = value.split('.')[1]?.length || 0;
243
374
  return Math.max(decimals, DEFAULT_DECIMAL);
244
375
  }
376
+ }
245
377
 
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
- }
378
+ function getStringValue(value: SKBigIntParams) {
379
+ return typeof value === 'object'
380
+ ? 'getValue' in value
381
+ ? value.getValue('string')
382
+ : value.value
383
+ : value;
259
384
  }
@@ -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
- };