]> git.codecow.com Git - libnemo.git/commitdiff
Improve unit conversion.
authorChris Duncan <chris@zoso.dev>
Fri, 8 Aug 2025 15:09:42 +0000 (08:09 -0700)
committerChris Duncan <chris@zoso.dev>
Fri, 8 Aug 2025 15:09:42 +0000 (08:09 -0700)
src/lib/tools.ts
test/test.tools.mjs

index 8bf22bffa7c2fe1ff61a486dcf3b132ef5a18938..4f66a7be0764fb4f23ac190af9f9b39c8f21df95 100644 (file)
@@ -21,51 +21,66 @@ type SweepResult = {
 /**
 * Converts a decimal amount of nano from one unit divider to another.
 *
-* @param {bigint|string} amount - Decimal amount to convert
+* @param {(bigint|string)} amount - Decimal amount to convert
 * @param {string} inputUnit - Current denomination
 * @param {string} outputUnit - Desired denomination
 */
-export async function convert (amount: bigint | string, inputUnit: string, outputUnit: string): Promise<string> {
-       if (typeof amount === 'bigint') {
-               amount = amount.toString()
+export async function convert (amount: bigint | string, inputUnit: string, outputUnit: string): Promise<string>
+export async function convert (amount: unknown, inputUnit: unknown, outputUnit: unknown): Promise<string> {
+       if (typeof amount !== 'bigint' && typeof amount !== 'string') {
+               throw new Error('Invalid amount', { cause: typeof amount })
        }
-       if (typeof amount !== 'string' || amount === '' || !/^[0-9]*\.?[0-9]*$/.test(amount)) {
-               throw new Error('Invalid amount')
+       if (typeof amount === 'string' && !/^[0-9]+\.?[0-9]*$/.test(amount)) {
+               throw new Error('Invalid amount', { cause: amount })
        }
-       let [i = '', f = ''] = amount.toString().split('.')
-       i = i.replace(/^0*/g, '')
-       f = f.replace(/0*$/g, '')
-
-       inputUnit = inputUnit.toUpperCase()
-       outputUnit = outputUnit.toUpperCase()
-       if (!(inputUnit in UNITS)) {
+       if (typeof inputUnit !== 'string') {
+               throw new TypeError('Invalid input unit', { cause: typeof inputUnit })
+       }
+       if (typeof outputUnit !== 'string') {
+               throw new TypeError('Invalid output unit', { cause: typeof outputUnit })
+       }
+       if (typeof inputUnit !== 'string' || UNITS[inputUnit.toUpperCase()] == null) {
                throw new Error(`Unknown denomination ${inputUnit}, expected one of the following: ${Object.keys(UNITS)}`)
        }
-       if (!(outputUnit in UNITS)) {
+       if (typeof outputUnit !== 'string' || UNITS[outputUnit.toUpperCase()] == null) {
                throw new Error(`Unknown denomination ${outputUnit}, expected one of the following: ${Object.keys(UNITS)}`)
        }
 
+       let i = '0', f = '0'
+       if (typeof amount === 'bigint') {
+               i = amount.toString()
+       } else {
+               [i, f] = amount.split('.')
+       }
+
        // convert to raw
-       i = `${i}${f.slice(0, UNITS[inputUnit])}`.padEnd(i.length + UNITS[inputUnit], '0')
-       f = f.slice(UNITS[inputUnit])
-       if (i.length > 39) {
-               throw new Error('Amount exceeds Nano limits')
+       const inUnit = UNITS[inputUnit.toUpperCase()]
+       i = i.padEnd(i.length + inUnit, '0')
+       let int = BigInt(i)
+       if (f != null) {
+               f = f.padEnd(f.length + inUnit, '0')
+               let frac = BigInt(f)
+               const remainder = frac % (10n ** BigInt(inUnit))
+               if (remainder > 0n) {
+                       throw new Error('Amount contains fractional raw', { cause: remainder })
+               }
+               frac /= 10n ** BigInt(f.length - inUnit)
+               int += frac
        }
-       if (i.length < 1) {
-               throw new Error('Amount must be at least 1 raw')
+       if (int > MAX_SUPPLY) {
+               throw new Error('Amount exceeds available supply')
        }
-       if (f.length > 0) {
-               throw new Error('Amount contains fractional raw')
+       if (int < 1n) {
+               throw new Error('Amount must be at least 1 raw')
        }
+       i = int.toString().padStart(40, '0')
 
        // convert to desired denomination
-       if (UNITS[outputUnit] !== 0) {
-               f = i.padStart(40, '0').slice(-UNITS[outputUnit]) + f
-               i = i.slice(0, -UNITS[outputUnit])
-       }
-       i = i.replace(/^0*/g, '') ?? '0'
-       f = f.replace(/0*$/g, '')
-       return `${i}${f ? '.' : ''}${f}`
+       const outUnit = UNITS[outputUnit.toUpperCase()]
+       f = i.slice(40 - outUnit).replace(/0*$/g, '')
+       i = i.slice(0, 40 - outUnit).replace(/^0*/g, '')
+
+       return `${i === '' ? '0' : i}${f === '' ? '' : '.'}${f}`
 }
 
 function hash (data: string | string[], encoding?: 'hex'): Uint8Array<ArrayBuffer>
index 5d05c9750cc4f1b3f44c96a3b699a021c3834c6b..1df3913467494e032807ff2cfdcb75b0c3d2acd5 100644 (file)
@@ -49,7 +49,7 @@ await Promise.all([
 \r
                await test('should convert 1 raw to 10^-29 nano', async () => {\r
                        const result = await Tools.convert('1', 'RAW', 'NANO')\r
-                       assert.equal(result, '.000000000000000000000000000001')\r
+                       assert.equal(result, '0.000000000000000000000000000001')\r
                })\r
 \r
                await test('should ignore leading and trailing zeros', async () => {\r