Skip to content

Commit

Permalink
feat: scale unit with native bigint
Browse files Browse the repository at this point in the history
  • Loading branch information
itsmnthn committed Mar 20, 2024
1 parent 77d00bf commit e997ace
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 52 deletions.
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './formatter'
export * from './web3'
export * from './units/unscale'
export * from './units/scale'

export const simulateAsyncPause = (duration = 1000) =>
new Promise<void>((resolve) => {
Expand Down
88 changes: 88 additions & 0 deletions packages/utils/src/units/scale.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { expect, test } from 'vitest'

import { scale, setAlwaysRoundDown } from './scale'

test('converts number to unit of a given length', () => {
expect(scale(69, 1)).toMatchInlineSnapshot('690n')
expect(scale(13, 5)).toMatchInlineSnapshot('1300000n')
expect(scale(420, 10)).toMatchInlineSnapshot('4200000000000n')
expect(scale(20, 9)).toMatchInlineSnapshot('20000000000n')
expect(scale(40, 18)).toMatchInlineSnapshot('40000000000000000000n')
expect(scale(1.2345, 4)).toMatchInlineSnapshot('12345n')
expect(scale(1.0045, 4)).toMatchInlineSnapshot('10045n')
expect(scale(1.2345000, 4)).toMatchInlineSnapshot('12345n')
expect(scale('6942069420.12345678912345', 18)).toMatchInlineSnapshot('6942069420123456789123450000n')
expect(scale('6942069420.00045678912345', 18)).toMatchInlineSnapshot('6942069420000456789123450000n')
expect(scale('6942123123123069420.1234544444678912345', 50))
.toMatchInlineSnapshot('694212312312306942012345444446789123450000000000000000000000000000000n')
expect(scale(-69, 1)).toMatchInlineSnapshot('-690n')
expect(scale(-1.2345, 4)).toMatchInlineSnapshot('-12345n')
expect(scale('-6942069420.12345678912345', 18)).toMatchInlineSnapshot('-6942069420123456789123450000n')
expect(scale('-6942123123123069420.1234544444678912345', 50))
.toMatchInlineSnapshot('-694212312312306942012345444446789123450000000000000000000000000000000n')
})

test('decimals === 0', () => {
expect(scale('69.2352112312312451512412341231', 0)).toMatchInlineSnapshot('69n')
expect(scale('69.5952141234124125231523412312', 0)).toMatchInlineSnapshot('70n')
expect(scale('12301000000000000020000', 0)).toMatchInlineSnapshot('12301000000000000020000n')
expect(scale('12301000000000000020000.123', 0)).toMatchInlineSnapshot('12301000000000000020000n')
expect(scale('12301000000000000020000.5', 0)).toMatchInlineSnapshot('12301000000000000020001n')
expect(scale('99999999999999999999999.5', 0)).toMatchInlineSnapshot('100000000000000000000000n')
})

test('decimals < fraction length', () => {
expect(scale(69.23521, 0)).toMatchInlineSnapshot('69n')
expect(scale(69.56789, 0)).toMatchInlineSnapshot('70n')
expect(scale(69.23521, 1)).toMatchInlineSnapshot('692n')
expect(scale(69.23521, 2)).toMatchInlineSnapshot('6924n')
expect(scale(69.23221, 2)).toMatchInlineSnapshot('6923n')
expect(scale(69.23261, 3)).toMatchInlineSnapshot('69233n')
expect(scale(999999.99999, 3)).toMatchInlineSnapshot('1000000000n')
expect(scale(699999.99999, 3)).toMatchInlineSnapshot('700000000n')
expect(scale(699999.98999, 3)).toMatchInlineSnapshot('699999990n')
expect(scale(699959.99999, 3)).toMatchInlineSnapshot('699960000n')
expect(scale(699099.99999, 3)).toMatchInlineSnapshot('699100000n')
expect(scale(100000.000999, 3)).toMatchInlineSnapshot('100000001n')
expect(scale(100000.990999, 3)).toMatchInlineSnapshot('100000991n')
expect(scale(69.00221, 3)).toMatchInlineSnapshot('69002n')
expect(scale('1.0536059576998882', 7)).toMatchInlineSnapshot('10536060n')
expect(scale('1.0053059576998882', 7)).toMatchInlineSnapshot('10053060n')
expect(scale('1.0000000900000000', 7)).toMatchInlineSnapshot('10000001n')
expect(scale('1.0000009900000000', 7)).toMatchInlineSnapshot('10000010n')
expect(scale('1.0000099900000000', 7)).toMatchInlineSnapshot('10000100n')
expect(scale('1.0000092900000000', 7)).toMatchInlineSnapshot('10000093n')
expect(scale('1.5536059576998882', 7)).toMatchInlineSnapshot('15536060n')
expect(scale('1.0536059476998882', 7)).toMatchInlineSnapshot('10536059n')
expect(scale('1.4545454545454545', 7)).toMatchInlineSnapshot('14545455n')
expect(scale('1.1234567891234567', 7)).toMatchInlineSnapshot('11234568n')
expect(scale('1.8989898989898989', 7)).toMatchInlineSnapshot('18989899n')
expect(scale('9.9999999999999999', 7)).toMatchInlineSnapshot('100000000n')
expect(scale('0.0536059576998882', 7)).toMatchInlineSnapshot('536060n')
expect(scale('0.0053059576998882', 7)).toMatchInlineSnapshot('53060n')
expect(scale('0.0000000900000000', 7)).toMatchInlineSnapshot('1n')
expect(scale('0.0000009900000000', 7)).toMatchInlineSnapshot('10n')
expect(scale('0.0000099900000000', 7)).toMatchInlineSnapshot('100n')
expect(scale('0.0000092900000000', 7)).toMatchInlineSnapshot('93n')
expect(scale('0.0999999999999999', 7)).toMatchInlineSnapshot('1000000n')
expect(scale('0.0099999999999999', 7)).toMatchInlineSnapshot('100000n')
expect(scale('0.00000000059', 9)).toMatchInlineSnapshot('1n')
expect(scale('0.0000000003', 9)).toMatchInlineSnapshot('0n')
expect(scale('69.00000000000', 9)).toMatchInlineSnapshot('69000000000n')
expect(scale('69.00000000019', 9)).toMatchInlineSnapshot('69000000000n')
expect(scale('69.00000000059', 9)).toMatchInlineSnapshot('69000000001n')
expect(scale('69.59000000059', 9)).toMatchInlineSnapshot('69590000001n')
expect(scale('69.59000002359', 9)).toMatchInlineSnapshot('69590000024n')
expect(scale('100.00000023', 6)).toMatchInlineSnapshot('100000000n')
})

// round down with striping extra decimals
test('decimals > fraction length with always round down', () => {
expect(scale(69.9, 0)).toMatchInlineSnapshot('70n')
expect(scale(69.23521, 0)).toMatchInlineSnapshot('69n')
expect(scale(69.23521, 2)).toMatchInlineSnapshot('6924n')
setAlwaysRoundDown(true)
expect(scale(69.9, 0)).toMatchInlineSnapshot('69n')
expect(scale(69.23521, 2)).toMatchInlineSnapshot('6923n')
setAlwaysRoundDown(true)
})
71 changes: 71 additions & 0 deletions packages/utils/src/units/scale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
let isAlwaysRoundDown = false

/**
* Set the default rounding behavior for the scale function. If true, the scale function will always round down.
* If false, the scale function will round off.
* @notice use with caution, as this will affect all calls to the scale function.
* @warning use if you want your entire application to always round down.
*/
export function setAlwaysRoundDown(value: boolean) {
isAlwaysRoundDown = value
}

/**
* Multiplies a string representation of a number by a given exponent of base 10 (10exponent).
* @returns A BigInt number
* @example
* scale('112', 18) // 112000000000000000000n
* scale(112, 18) // 112000000000000000000n
* scale(112.5632, 0) // 113n
* scale(112.5632, 0, true) // 112n
*/
export function scale(value: string | number, decimals: number, roundDown = isAlwaysRoundDown) {
let [integer, fraction = '0'] = `${value}`.split('.')

const negative = integer.startsWith('-')
if (negative)
integer = integer.slice(1)

// trim leading zeros.
fraction = fraction.replace(/(0+)$/, '')

// round off if the fraction is larger than the number of decimals.
if (decimals === 0) {
if (Math.round(Number(`.${fraction}`)) === 1 && !roundDown)
integer = `${BigInt(integer) + BigInt(1)}`
fraction = ''
}
else if (fraction.length > decimals && !roundDown) {
const left = fraction.slice(0, decimals - 1)
const unit = fraction.slice(decimals - 1, decimals)
const right = fraction.slice(decimals)

const rounded = Math.round(Number(`${unit}.${right}`))
if (rounded > 9)
fraction = `${BigInt(left) + BigInt(1)}0`.padStart(left.length + 1, '0')
else fraction = `${left}${rounded}`

if (fraction.length > decimals) {
fraction = fraction.slice(1)
integer = `${BigInt(integer) + BigInt(1)}`
}

fraction = fraction.slice(0, decimals)
}
else if (fraction.length > decimals && roundDown) {
fraction = fraction.slice(0, decimals)
}
else {
fraction = fraction.padEnd(decimals, '0')
}

return BigInt(`${negative ? '-' : ''}${integer}${fraction}`)
}

/**
* wrapper for scale function that returns a string representation of the result.
* @deprecated use scale().toString() instead
*/
export function scaleToString(value: string | number, decimals: number, roundDown = isAlwaysRoundDown) {
return scale(value, decimals, roundDown).toString()
}
22 changes: 1 addition & 21 deletions packages/utils/src/web3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,30 +40,10 @@ export type BigNumberish = BN | bigint | string | number
* @param decimals The number of decimals to scale to
* @returns The scaled number (e.g. 1 with 6 decimal -> 1000000)
*/
export const scale = (amount: string | number | bigint, decimals: BigNumberish = 6): BN => {
const scale = (amount: string | number | bigint, decimals: BigNumberish = 6): BN => {
return parseUnits(amount.toString(), decimals)
}

/**
* Scale the given number to the given decimals (e.g. 1 with 6 decimal -> 1000000)
* @param amount The number to scale
* @param decimals The number of decimals to scale to
* @returns The scaled number in string (e.g. 1 with 6 decimal -> "1000000")
*/
export const scaleToString = (amount: string | number | bigint, decimals: BigNumberish = 6): string => {
return parseUnits(amount.toString(), decimals).toString()
}

/**
* sanitize and scale the given user number to the given decimals (e.g. 100.00000023 with 6 decimal -> 1000000)
* @param amount The number to scale
* @param decimals The number of decimals to sanitize with and scale to
* @returns The scaled number in string (e.g. 100.00000023 with 6 decimal -> 1000000)
*/
export const scaleUserAmount = (amount: string | number, decimals = 6) => {
return scaleToString(shortenDecimals(amount, decimals), decimals)
}

/**
* UnScale the given base number to the given decimals (e.g. 1000000000000000000000012 with 18 decimal -> 1000000)
* @param amount The number to un scale
Expand Down
31 changes: 0 additions & 31 deletions packages/utils/test/web3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
calcTotalPrice, calcUnitPrice, calcUnits,
decreaseNumByPercentage,
getFormattedAmount, getPercentOfAmount, getPercentageOfAmount, increaseNumByPercentage,
scale, scaleToString, scaleUserAmount,
unScaleToBase,
} from '../src'

Expand Down Expand Up @@ -85,36 +84,6 @@ describe('getPercentOfAmount', () => {
})
})

describe('scale', () => {
it('Scale amount by given decimals and returns BN', () => {
const num = '12'
const decimals = 6
const result = scale(num, decimals)
expect(result).toEqual(BN.from(12000000))
expect(formatUnits(result, decimals)).toEqual('12.0')
})
})

describe('scaleToString', () => {
it('Scale amount by given decimals and returns string', () => {
const num = '45.36624'
const decimals = 6
const result = scaleToString(num, decimals)
expect(result).toEqual('45366240')
expect(formatUnits(result, decimals)).toEqual('45.36624')
})
})

describe('scaleUserAmount', () => {
it('Scale and Sanitize amount by given decimals and returns string', () => {
const num = '45.366249072'
const decimals = 6
const result = scaleUserAmount(num, decimals)
expect(result).toEqual('45366249')
expect(formatUnits(result, decimals)).toEqual('45.366249')
})
})

describe('unScaleToBase', () => {
it('unScale to base amount by given decimals and returns string with no decimals', () => {
const num = '45366240903847'
Expand Down

0 comments on commit e997ace

Please sign in to comment.