Skip to content

Commit 0a7cc73

Browse files
committed
feat: convert numbers
1 parent 68eb5c5 commit 0a7cc73

File tree

5 files changed

+178
-0
lines changed

5 files changed

+178
-0
lines changed

src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1+
// Client
12
export * from './core/client';
3+
4+
// Utils
5+
export * from './utils/index';
6+
7+
// Types
8+
export type * from './types/index';

src/types/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './assets';
2+
export * from './client';
3+
export * from './router';
4+
export * from './swap';

src/utils/convert.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { test, describe, expect } from 'bun:test';
2+
import { fromNano, toNano } from './convert';
3+
4+
const stringCases: { nano: string; real: string }[] = [
5+
{ real: '1', nano: '1000000000' },
6+
{ real: '10', nano: '10000000000' },
7+
{ real: '0.1', nano: '100000000' },
8+
{ real: '0.33', nano: '330000000' },
9+
{ real: '0.000000001', nano: '1' },
10+
{ real: '10.000000001', nano: '10000000001' },
11+
{ real: '1000000.000000001', nano: '1000000000000001' },
12+
{ real: '100000000000', nano: '100000000000000000000' },
13+
];
14+
15+
const numberCases: { nano: string; real: number }[] = [
16+
{ real: -0, nano: '0' },
17+
{ real: 0, nano: '0' },
18+
{
19+
real: 1e64,
20+
nano: '10000000000000000000000000000000000000000000000000000000000000000000000000',
21+
},
22+
{ real: 1, nano: '1000000000' },
23+
{ real: 10, nano: '10000000000' },
24+
{ real: 0.1, nano: '100000000' },
25+
{ real: 0.33, nano: '330000000' },
26+
{ real: 0.000000001, nano: '1' },
27+
{ real: 10.000000001, nano: '10000000001' },
28+
{ real: 1000000.000000001, nano: '1000000000000001' },
29+
{ real: 100000000000, nano: '100000000000000000000' },
30+
];
31+
32+
test('convert numbers', () => {});
33+
34+
describe('Convert numbers', () => {
35+
test('should throw an error for NaN', () => {
36+
expect(() => toNano(NaN)).toThrow();
37+
});
38+
39+
test('should throw an error for Infinity', () => {
40+
expect(() => toNano(Infinity)).toThrow();
41+
});
42+
43+
test('should throw an error for -Infinity', () => {
44+
expect(() => toNano(-Infinity)).toThrow();
45+
});
46+
47+
test('should throw an error due to insufficient precision of number', () => {
48+
expect(() => toNano(10000000.000000001)).toThrow();
49+
});
50+
51+
test('should convert numbers toNano', () => {
52+
for (let r of numberCases) {
53+
let c = toNano(r.real);
54+
expect(c).toBe(BigInt(r.nano));
55+
}
56+
});
57+
58+
test('should convert strings toNano', () => {
59+
for (let r of stringCases) {
60+
let c = toNano(r.real);
61+
expect(c).toBe(BigInt(r.nano));
62+
}
63+
});
64+
65+
test('should convert with custom decimals', () => {
66+
expect(toNano(1, 6)).toEqual(1000000n);
67+
});
68+
69+
test('should convert fromNano', () => {
70+
for (let r of stringCases) {
71+
let c = fromNano(r.nano);
72+
expect(c).toEqual(r.real);
73+
}
74+
});
75+
76+
test('should convert fromNano with custom decimals', () => {
77+
expect(fromNano(1000000, 6)).toEqual('1');
78+
});
79+
});

src/utils/convert.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
export function toNano(src: number | string | bigint, decimals: number = 9): bigint {
2+
if (typeof src === 'bigint') {
3+
return src * 10n ** BigInt(decimals);
4+
} else {
5+
if (typeof src === 'number') {
6+
if (!Number.isFinite(src)) {
7+
throw Error('Invalid number');
8+
}
9+
10+
if (Math.log10(src) <= 6) {
11+
src = src.toLocaleString('en', { minimumFractionDigits: 9, useGrouping: false });
12+
} else if (src - Math.trunc(src) === 0) {
13+
src = src.toLocaleString('en', { maximumFractionDigits: 0, useGrouping: false });
14+
} else {
15+
throw Error('Not enough precision for a number value. Use string value instead');
16+
}
17+
}
18+
19+
// Check sign
20+
let neg = false;
21+
while (src.startsWith('-')) {
22+
neg = !neg;
23+
src = src.slice(1);
24+
}
25+
26+
// Split string
27+
if (src === '.') {
28+
throw Error('Invalid number');
29+
}
30+
let parts = src.split('.');
31+
if (parts.length > 2) {
32+
throw Error('Invalid number');
33+
}
34+
35+
// Prepare parts
36+
let whole = parts[0];
37+
let frac = parts[1];
38+
if (!whole) {
39+
whole = '0';
40+
}
41+
if (!frac) {
42+
frac = '0';
43+
}
44+
if (frac.length > 9) {
45+
throw Error('Invalid number');
46+
}
47+
while (frac.length < 9) {
48+
frac += '0';
49+
}
50+
51+
// Convert
52+
let r = BigInt(whole) * 10n ** BigInt(decimals) + BigInt(frac);
53+
if (neg) {
54+
r = -r;
55+
}
56+
return r;
57+
}
58+
}
59+
60+
export function fromNano(src: bigint | number | string, decimals: number = 9) {
61+
let v = BigInt(src);
62+
let neg = false;
63+
if (v < 0) {
64+
neg = true;
65+
v = -v;
66+
}
67+
68+
// Convert fraction
69+
let frac = v % 10n ** BigInt(decimals);
70+
let facStr = frac.toString();
71+
while (facStr.length < 9) {
72+
facStr = '0' + facStr;
73+
}
74+
facStr = facStr.match(/^([0-9]*[1-9]|0)(0*)/)![1];
75+
76+
// Convert whole
77+
let whole = v / 10n ** BigInt(decimals);
78+
let wholeStr = whole.toString();
79+
80+
// Value
81+
let value = `${wholeStr}${facStr === '0' ? '' : `.${facStr}`}`;
82+
if (neg) {
83+
value = '-' + value;
84+
}
85+
86+
return value;
87+
}

src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './convert';

0 commit comments

Comments
 (0)