-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add unit conversion utils * test: add unit conversion unit tests * feat: add balance conversion with strings * test: add unit test to balance conversion with strings * docs: improve ts-docs for unit conversion
- Loading branch information
Showing
3 changed files
with
305 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
/** | ||
* Converts an amount from its decimal representation. | ||
* | ||
* This function takes an amount, either as a number or bigint, and a specified number of decimal places, | ||
* and returns the amount divided by 10 to the power of the decimal places. | ||
* | ||
* @param amount - The amount to convert, either as a number or bigint. | ||
* @param decimal - The number of decimal places to consider. | ||
* @returns The amount converted from its decimal representation. | ||
* | ||
* @example | ||
* // Converts 10000000 to 1 with 7 decimal places | ||
* const result = fromDecimals(10000000, 7); | ||
*/ | ||
export const fromDecimals = <T extends number | bigint>(amount: T, decimal: number): T => { | ||
const multiplier = typeof amount === 'bigint' ? BigInt(10 ** decimal) : 10 ** decimal | ||
return (amount / (multiplier as any)) as T | ||
} | ||
|
||
/** | ||
* Converts an amount to its decimal representation. | ||
* | ||
* This function takes an amount, either as a number or bigint, and a specified number of decimal places, | ||
* and returns the amount multiplied by 10 to the power of the decimal places. | ||
* | ||
* @param amount - The amount to convert, either as a number or bigint. | ||
* @param decimal - The number of decimal places to consider. | ||
* @returns The amount converted to its decimal representation. | ||
* | ||
* @example | ||
* // Converts 1 to 10000000 with 7 decimal places | ||
* const result = toDecimals(1, 7); | ||
*/ | ||
export const toDecimals = <T extends number | bigint>(amount: T, decimal: number): T => { | ||
const multiplier = typeof amount === 'bigint' ? BigInt(10 ** decimal) : 10 ** decimal | ||
return (amount * (multiplier as any)) as T | ||
} | ||
|
||
/** | ||
* Converts an amount from Stroops to the standard unit. | ||
* | ||
* This function takes an amount in Stroops (smallest unit), either as a number or bigint, | ||
* and converts it to the standard unit by dividing by 10^7. | ||
* | ||
* @param amount - The amount in Stroops, either as a number or bigint. | ||
* @returns The amount converted from Stroops. | ||
* | ||
* @example | ||
* // Converts 10000000 Stroops to 1 standard unit | ||
* const result = fromStroops(10000000); | ||
*/ | ||
export const fromStroops = <T extends number | bigint>(amount: T): T => { | ||
return fromDecimals(amount, 7) as T | ||
} | ||
|
||
/** | ||
* Converts an amount to Stroops from the standard unit. | ||
* | ||
* This function takes an amount in the standard unit, either as a number or bigint, | ||
* and converts it to Stroops (smallest unit) by multiplying by 10^7. | ||
* | ||
* @param amount - The amount to convert, either as a number or bigint. | ||
* @returns The amount converted to Stroops. | ||
* | ||
* @example | ||
* // Converts 1 standard unit to 10000000 Stroops | ||
* const result = toStroops(1); | ||
*/ | ||
export const toStroops = <T extends number | bigint>(amount: T): T => { | ||
return toDecimals(amount, 7) as T | ||
} | ||
|
||
/** | ||
* Converts a number balance to its string representation with a specified number of decimal places. | ||
* | ||
* This function takes a number and converts it to a string with the specified number of decimal places. | ||
* It handles rounding and ensures the fractional part is padded with zeros if necessary. | ||
* | ||
* @param amount - The number balance to convert. | ||
* @param decimal - The number of decimal places to include in the string. | ||
* @returns The string representation of the number balance. | ||
* | ||
* @example | ||
* // Converts 123.456 to '123.4560' with 4 decimal places | ||
* const result = numberBalanceToString(123.456, 4); | ||
*/ | ||
export const numberBalanceToString = (amount: number, decimal: number): string => { | ||
const integerPart = Math.floor(amount).toString() | ||
const multiplier = 10 ** decimal | ||
const fractionalPart = Math.round((amount - Math.floor(amount)) * multiplier) | ||
.toString() | ||
.padStart(decimal, '0') | ||
return `${integerPart}.${fractionalPart}` | ||
} | ||
|
||
/** | ||
* Converts a string representation of a number balance to its numeric form. | ||
* | ||
* This function takes a string representing a number with a decimal point and converts it to a number. | ||
* It handles both integer and fractional parts. | ||
* | ||
* @param amountStr - The string representation of the number balance. | ||
* @returns The numeric form of the balance. | ||
* | ||
* @example | ||
* // Converts '123.456' to 123.456 | ||
* const result = numberBalanceFromString('123.456'); | ||
*/ | ||
export const numberBalanceFromString = (amountStr: string): number => { | ||
const [integerPart, fractionalPart = ''] = amountStr.split('.') | ||
const integerAmount = parseInt(integerPart, 10) | ||
const fractionalAmount = fractionalPart ? parseInt(fractionalPart, 10) / 10 ** fractionalPart.length : 0 | ||
return integerAmount + fractionalAmount | ||
} | ||
|
||
/** | ||
* Converts a bigint balance to its string representation with a specified number of decimal places. | ||
* | ||
* The bigint number will be considered as the whole number to be partitioned with the specified decimals. | ||
* When the number of decimals is 0, no "." will be added. | ||
* | ||
* @param amount - The bigint balance to convert. | ||
* @param decimal - The number of decimal places to include in the string. | ||
* @returns The string representation of the bigint balance. | ||
* | ||
* @example | ||
* // Converts 123456n to '1234.56' with 2 decimal places | ||
* const result = bigIntBalanceToString(123456n, 2); | ||
*/ | ||
export const bigIntBalanceToString = (amount: bigint, decimal: number): string => { | ||
if (decimal === 0) return amount.toString() | ||
const amountStr = amount.toString() | ||
const integerPart = amountStr.slice(0, -decimal) || '0' | ||
const fractionalPart = amountStr.slice(-decimal).padStart(decimal, '0') | ||
return `${integerPart}.${fractionalPart}` | ||
} | ||
|
||
/** | ||
* Converts a string representation of a bigint balance to its bigint form. | ||
* | ||
* This function takes a string representing a bigint with a decimal point and converts it to a bigint. | ||
* It handles both integer and fractional parts by removing the decimal point and concatenating the parts. | ||
* | ||
* @param amountStr - The string representation of the bigint balance. | ||
* @returns The bigint form of the balance. | ||
* | ||
* @example | ||
* // Converts '1234.56' to 123456n | ||
* const result = bigIntBalanceFromString('1234.56'); | ||
*/ | ||
export const bigIntBalanceFromString = (amountStr: string): bigint => { | ||
const [integerPart, fractionalPart = ''] = amountStr.split('.') | ||
const combinedStr = integerPart + fractionalPart.padEnd(fractionalPart.length, '0') | ||
return BigInt(combinedStr) | ||
} | ||
|
||
/** | ||
* Converts a balance to its string representation with a specified number of decimal places. | ||
* | ||
* This function takes a balance, either as a number or bigint, and converts it to a string with the specified number of decimal places. | ||
* It delegates the conversion to either `numberBalanceToString` or `bigIntBalanceToString` based on the type of the balance. | ||
* | ||
* @param amount - The balance to convert, either as a number or bigint. | ||
* @param decimal - The number of decimal places to include in the string. | ||
* @returns The string representation of the balance. | ||
* | ||
* @example | ||
* // Converts 123.456 to '123.4560' with 4 decimal places | ||
* const result = balanceToString(123.456, 4); | ||
* | ||
* // Converts 123456n to '1234.56' with 2 decimal places | ||
* const result = balanceToString(123456n, 2); | ||
*/ | ||
export const balanceToString = <T extends number | bigint>(amount: T, decimal: number): string => { | ||
return typeof amount === 'bigint' ? bigIntBalanceToString(amount, decimal) : numberBalanceToString(amount, decimal) | ||
} |
128 changes: 128 additions & 0 deletions
128
src/stellar-plus/utils/unit-conversion/index.unit.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { | ||
fromDecimals, | ||
fromStroops, | ||
toDecimals, | ||
toStroops, | ||
bigIntBalanceToString, | ||
bigIntBalanceFromString, | ||
numberBalanceToString, | ||
numberBalanceFromString, | ||
balanceToString, | ||
} from '.' | ||
|
||
describe('Unit conversion', () => { | ||
describe('function toStroops', () => { | ||
it('should convert number amounts to stroops(7 decimals) and return a number', () => { | ||
expect(toStroops(152)).toBe(1520000000) | ||
}) | ||
|
||
it('should convert bigint amounts to stroops(7 decimals) and return a bigint', () => { | ||
expect(toStroops(BigInt(131))).toBe(BigInt(1310000000)) | ||
}) | ||
}) | ||
|
||
describe('function fromStroops', () => { | ||
it('should convert number amounts from stroops(7 decimals) and return a number', () => { | ||
expect(fromStroops(1520000000)).toBe(152) | ||
}) | ||
|
||
it('should convert bigint amounts from stroops(7 decimals) and return a bigint', () => { | ||
expect(fromStroops(BigInt(1310000000))).toBe(BigInt(131)) | ||
}) | ||
}) | ||
|
||
describe('function toDecimal', () => { | ||
it('should convert number amounts to any decimals and return a number', () => { | ||
expect(toDecimals(297, 2)).toBe(29700) | ||
}) | ||
|
||
it('should convert bigint amounts to any decimals and return a bigint', () => { | ||
expect(toDecimals(BigInt(987), 4)).toBe(BigInt(9870000)) | ||
}) | ||
}) | ||
|
||
describe('function fromStroops', () => { | ||
it('should convert number amounts from any decimals and return a number', () => { | ||
expect(fromDecimals(67500000000, 8)).toBe(675) | ||
}) | ||
|
||
it('should convert bigint amounts from any decimals and return a bigint', () => { | ||
expect(fromDecimals(BigInt(454000000), 6)).toBe(BigInt(454)) | ||
}) | ||
}) | ||
|
||
describe('number balances and strings', () => { | ||
it('numberBalanceToString should convert number amounts to strings with a "." separator and the number of explicit decimals', () => { | ||
expect(numberBalanceToString(329, 4)).toBe('329.0000') | ||
expect(numberBalanceToString(329.6, 4)).toBe('329.6000') | ||
expect(numberBalanceToString(0.006, 4)).toBe('0.0060') | ||
}) | ||
it('numberBalanceToString should round numbers properly when shortenning the decimals', () => { | ||
expect(numberBalanceToString(0.3297, 3)).toBe('0.330') | ||
expect(numberBalanceToString(0.3294, 3)).toBe('0.329') | ||
expect(numberBalanceToString(1.006, 2)).toBe('1.01') | ||
expect(numberBalanceToString(1.005, 2)).toBe('1.00') | ||
}) | ||
|
||
it('numberBalanceFromString should convert string amounts to number considering a "." separator', () => { | ||
expect(numberBalanceFromString('329.0000')).toBe(329) | ||
expect(numberBalanceFromString('329.6000')).toBe(329.6) | ||
expect(numberBalanceFromString('721.00006000')).toBe(721.00006) | ||
expect(numberBalanceFromString('0.329')).toBe(0.329) | ||
expect(numberBalanceFromString('0.00001000')).toBe(0.00001) | ||
expect(numberBalanceFromString('1.01')).toBe(1.01) | ||
expect(numberBalanceFromString('123')).toBe(123) | ||
}) | ||
}) | ||
describe('bigint balances and strings', () => { | ||
it('bigIntBalanceToString should convert bigint amounts to strings with a "." separator and the number of explicit decimals', () => { | ||
expect(bigIntBalanceToString(BigInt(32900000000), 4)).toBe('3290000.0000') | ||
expect(bigIntBalanceToString(BigInt(32960000000), 4)).toBe('3296000.0000') | ||
expect(bigIntBalanceToString(BigInt(32960000000), 5)).toBe('329600.00000') | ||
expect(bigIntBalanceToString(BigInt(32960000000), 6)).toBe('32960.000000') | ||
expect(bigIntBalanceToString(BigInt(32960000000), 7)).toBe('3296.0000000') | ||
expect(bigIntBalanceToString(BigInt(32960000000), 8)).toBe('329.60000000') | ||
expect(bigIntBalanceToString(BigInt(3296), 3)).toBe('3.296') | ||
expect(bigIntBalanceToString(BigInt(3296), 4)).toBe('0.3296') | ||
expect(bigIntBalanceToString(BigInt(3296), 5)).toBe('0.03296') | ||
expect(bigIntBalanceToString(BigInt(3296), 6)).toBe('0.003296') | ||
expect(bigIntBalanceToString(BigInt(3296), 7)).toBe('0.0003296') | ||
}) | ||
|
||
it('bigIntBalanceFromStringshould convert strings with a "." separator to bigint amounts', () => { | ||
expect(bigIntBalanceFromString('3290.0000').toString()).toBe(BigInt(32900000).toString()) | ||
expect(bigIntBalanceFromString('3296.0000').toString()).toBe(BigInt(32960000).toString()) | ||
expect(bigIntBalanceFromString('0.3296').toString()).toBe(BigInt(3296).toString()) | ||
expect(bigIntBalanceFromString('123').toString()).toBe(BigInt(123).toString()) | ||
}) | ||
|
||
it('bigIntBalanceToString and bigIntBalanceFromStringshould should be consisten both ways', () => { | ||
expect(bigIntBalanceToString(bigIntBalanceFromString('3290.0000'), 4)).toBe('3290.0000') | ||
expect(bigIntBalanceToString(bigIntBalanceFromString('3290.0000'), 3)).toBe('32900.000') | ||
expect(bigIntBalanceToString(bigIntBalanceFromString('3290.0000'), 2)).toBe('329000.00') | ||
expect(bigIntBalanceToString(bigIntBalanceFromString('3290.0000'), 1)).toBe('3290000.0') | ||
expect(bigIntBalanceToString(bigIntBalanceFromString('3290.0000'), 0)).toBe('32900000') | ||
|
||
expect(bigIntBalanceFromString(bigIntBalanceToString(BigInt(1234), 0)).toString()).toBe(BigInt(1234).toString()) | ||
expect(bigIntBalanceFromString(bigIntBalanceToString(BigInt(5678), 1)).toString()).toBe(BigInt(5678).toString()) | ||
expect(bigIntBalanceFromString(bigIntBalanceToString(BigInt(9876), 2)).toString()).toBe(BigInt(9876).toString()) | ||
expect(bigIntBalanceFromString(bigIntBalanceToString(BigInt(5432), 3)).toString()).toBe(BigInt(5432).toString()) | ||
expect(bigIntBalanceFromString(bigIntBalanceToString(BigInt(1122), 4)).toString()).toBe(BigInt(1122).toString()) | ||
expect(bigIntBalanceFromString(bigIntBalanceToString(BigInt(1), 5)).toString()).toBe(BigInt(1).toString()) | ||
}) | ||
}) | ||
|
||
describe('general balances and strings', () => { | ||
it('should convert number amounts to strings with a "." separator and the number of explicit decimals', () => { | ||
expect(balanceToString(329, 4)).toBe('329.0000') | ||
expect(balanceToString(329.6, 4)).toBe('329.6000') | ||
expect(balanceToString(0.3296, 4)).toBe('0.3296') | ||
}) | ||
|
||
it('should convert bigint amounts to strings with a "." separator and the number of explicit decimals', () => { | ||
expect(balanceToString(BigInt(32900000000), 4)).toBe('3290000.0000') | ||
expect(balanceToString(BigInt(32960000000), 4)).toBe('3296000.0000') | ||
expect(balanceToString(BigInt(3296), 4)).toBe('0.3296') | ||
}) | ||
}) | ||
}) |