diff --git a/apps/token/src/lib/abbreviate-number.test.ts b/apps/token/src/lib/abbreviate-number.test.ts new file mode 100644 index 000000000..dff5fe100 --- /dev/null +++ b/apps/token/src/lib/abbreviate-number.test.ts @@ -0,0 +1,46 @@ +import { BigNumber } from './bignumber'; +import { getAbbreviatedNumber } from './abbreviate-number'; + +it('For numbers less than 1000, do nothing', () => { + expect(getAbbreviatedNumber(new BigNumber('1'))).toStrictEqual('1'); + expect(getAbbreviatedNumber(new BigNumber('10'))).toStrictEqual('10'); + expect(getAbbreviatedNumber(new BigNumber('33'))).toStrictEqual('33'); + expect(getAbbreviatedNumber(new BigNumber('100'))).toStrictEqual('100'); + expect(getAbbreviatedNumber(new BigNumber('999'))).toStrictEqual('999'); +}); + +it('For numbers equal to or greater than 1000, abbreviate with a K', () => { + expect(getAbbreviatedNumber(new BigNumber('1000'))).toStrictEqual('1K'); + expect(getAbbreviatedNumber(new BigNumber('3333'))).toStrictEqual('3.3K'); + expect(getAbbreviatedNumber(new BigNumber('33333'))).toStrictEqual('33.3K'); + expect(getAbbreviatedNumber(new BigNumber('999999'))).toStrictEqual('1M'); +}); + +it('For numbers equal to or greater than 1,000,000, abbreviate with a M', () => { + expect(getAbbreviatedNumber(new BigNumber('1000000'))).toStrictEqual('1M'); + expect(getAbbreviatedNumber(new BigNumber('10000000'))).toStrictEqual('10M'); + expect(getAbbreviatedNumber(new BigNumber('100000000'))).toStrictEqual( + '100M' + ); + expect(getAbbreviatedNumber(new BigNumber('100000000000'))).toStrictEqual( + '100B' + ); + expect(getAbbreviatedNumber(new BigNumber('1000000000000000'))).toStrictEqual( + '1000T' + ); +}); + +it('Handles MAX_SAFE_INTEGER as expected', () => { + const massiveNumberAsString = `${Number.MAX_SAFE_INTEGER + 1}`; + const enormousNumberAsString = `${Number.MAX_SAFE_INTEGER + 2}`; + const giganticNumberAsString = `${Number.MAX_SAFE_INTEGER}9999`; + expect( + getAbbreviatedNumber(new BigNumber(massiveNumberAsString)) + ).toStrictEqual('9007.2T'); + expect( + getAbbreviatedNumber(new BigNumber(enormousNumberAsString)) + ).toStrictEqual('9007.2T'); + expect( + getAbbreviatedNumber(new BigNumber(giganticNumberAsString)) + ).toStrictEqual('90,071,992.5T'); +}); diff --git a/apps/token/src/lib/abbreviate-number.ts b/apps/token/src/lib/abbreviate-number.ts index 77305766b..f411a3c86 100644 --- a/apps/token/src/lib/abbreviate-number.ts +++ b/apps/token/src/lib/abbreviate-number.ts @@ -1,11 +1,9 @@ +import { getUserLocale } from '@vegaprotocol/react-helpers'; import type { BigNumber } from './bignumber'; export const getAbbreviatedNumber = (num: BigNumber) => { - const number = num.toNumber(); - if (number < 1000) { - return Number(num.toFixed()).toLocaleString(); - } else if (number < 1000000) { - return Number((number / 1000).toFixed()).toLocaleString() + 'K'; - } - return Number((number / 1000000).toFixed()).toLocaleString() + 'M'; + return Intl.NumberFormat(getUserLocale(), { + notation: 'compact', + maximumFractionDigits: 1, + }).format(num.toNumber()); }; diff --git a/apps/token/src/lib/bignumber.test.ts b/apps/token/src/lib/bignumber.test.ts new file mode 100644 index 000000000..c3d22c903 --- /dev/null +++ b/apps/token/src/lib/bignumber.test.ts @@ -0,0 +1,11 @@ +import { BigNumber } from './bignumber'; + +it('BigNumber wrapper lib sets a known configuration', () => { + const res = BigNumber.config({}); + // The one value configured by this export + expect(res.EXPONENTIAL_AT).toStrictEqual([-20000, 20000]); + // A default value + expect(res.DECIMAL_PLACES).toStrictEqual(20); + // A default value: ROUND_HALF_UP + expect(res.ROUNDING_MODE).toStrictEqual(4); +}); diff --git a/apps/token/src/lib/deterministic-shuffle.test.ts b/apps/token/src/lib/deterministic-shuffle.test.ts new file mode 100644 index 000000000..4002ad26a --- /dev/null +++ b/apps/token/src/lib/deterministic-shuffle.test.ts @@ -0,0 +1,111 @@ +import { + stringTo32BitHash, + createRandomGenerator, + deterministicShuffle, +} from './deterministic-shuffle'; + +it('Converts a string to a hash as expected', () => { + expect(stringTo32BitHash('test')).toEqual(1706); + expect(stringTo32BitHash('0x0ddba11')).toEqual(31040); + expect(stringTo32BitHash('Rhosllannerchrugog')).toEqual(27853302); +}); + +it('Random generator is deterministic by seed: matching output', () => { + const genSeedOne = createRandomGenerator(1); + const anotherGenSeedOne = createRandomGenerator(1); + + expect(genSeedOne()).toEqual(anotherGenSeedOne()); + expect(genSeedOne()).toEqual(anotherGenSeedOne()); + expect(genSeedOne()).toEqual(anotherGenSeedOne()); + + // Throw a result away so they are out of step + genSeedOne(); + + expect(genSeedOne()).not.toEqual(anotherGenSeedOne()); +}); + +it('Random generator is deterministic by seed: non-matching output', () => { + const genSeedOne = createRandomGenerator(1); + const genSeedTwo = createRandomGenerator(2); + + expect(genSeedOne()).not.toEqual(genSeedTwo()); + expect(genSeedOne()).not.toEqual(genSeedTwo()); + expect(genSeedOne()).not.toEqual(genSeedTwo()); +}); + +it('Random generator is deterministic by seed: switching seed overrides original seed and produces deterministic output', () => { + const genSeedOne = createRandomGenerator(1); + const genSeedTwo = createRandomGenerator(2); + + const firstTwoSeed = genSeedTwo(); + expect(genSeedOne()).not.toEqual(firstTwoSeed); + + const secondTwoSeed = genSeedTwo(); + expect(genSeedOne()).not.toEqual(secondTwoSeed); + + expect(genSeedOne(2)).toEqual(firstTwoSeed); + expect(genSeedOne()).toEqual(secondTwoSeed); +}); + +it('deterministicShuffle shuffles deterministically: strings', () => { + const defaultInputStrings = ['one', 'two', 'three', 'four', 'five']; + const testSeedOne = deterministicShuffle('test', defaultInputStrings); + const testSeedTwo = deterministicShuffle('test', defaultInputStrings); + const testSeedThree = deterministicShuffle('test', defaultInputStrings); + + expect(testSeedOne).toEqual(['three', 'four', 'one', 'two', 'five']); + expect(testSeedTwo).not.toEqual(testSeedOne); + expect(testSeedThree).not.toEqual(testSeedOne); + + const altSeedOne = deterministicShuffle( + 'anything-except-test', + defaultInputStrings + ); + expect(altSeedOne).not.toEqual(testSeedOne); +}); + +it('deterministicShuffle shuffles deterministically: numbers', () => { + const defaultInputNumbers = [1, 2, 3, 4, 5]; + const testSeedOne = deterministicShuffle('test', defaultInputNumbers); + const testSeedTwo = deterministicShuffle('test', defaultInputNumbers); + const testSeedThree = deterministicShuffle('test', defaultInputNumbers); + + expect(testSeedOne).toEqual([3, 4, 1, 2, 5]); + expect(testSeedTwo).not.toEqual(testSeedOne); + expect(testSeedThree).not.toEqual(testSeedOne); + + const altSeedOne = deterministicShuffle( + 'anything-except-test', + defaultInputNumbers + ); + expect(altSeedOne).not.toEqual(testSeedOne); +}); + +it('deterministicShuffle shuffles deterministically: objects', () => { + const defaultInputObjects = [ + { test: 1 }, + { test: 2 }, + { test: 3 }, + { test: 4 }, + { test: 5 }, + ]; + const testSeedOne = deterministicShuffle('test', defaultInputObjects); + const testSeedTwo = deterministicShuffle('test', defaultInputObjects); + const testSeedThree = deterministicShuffle('test', defaultInputObjects); + + expect(testSeedOne).toEqual([ + { test: 3 }, + { test: 4 }, + { test: 1 }, + { test: 2 }, + { test: 5 }, + ]); + expect(testSeedTwo).not.toEqual(testSeedOne); + expect(testSeedThree).not.toEqual(testSeedOne); + + const altSeedOne = deterministicShuffle( + 'anything-except-test', + defaultInputObjects + ); + expect(altSeedOne).not.toEqual(testSeedOne); +}); diff --git a/apps/token/src/lib/deterministic-shuffle.ts b/apps/token/src/lib/deterministic-shuffle.ts index 98a3d6640..c7addb060 100644 --- a/apps/token/src/lib/deterministic-shuffle.ts +++ b/apps/token/src/lib/deterministic-shuffle.ts @@ -1,5 +1,5 @@ // creates a random number generator function. -function createRandomGenerator(seed: number) { +export function createRandomGenerator(seed: number) { const a = 5486230734; // some big numbers const b = 6908969830; const m = 9853205067; @@ -13,7 +13,7 @@ function createRandomGenerator(seed: number) { } // function creates a 32bit hash of a string -function stringTo32BitHash(str: string) { +export function stringTo32BitHash(str: string) { let v = 0; for (let i = 0; i < str.length; i += 1) { v += str.charCodeAt(i) << i % 24;