diff --git a/package-lock.json b/package-lock.json index 3a21c0e..b42198f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.8.1", "@fontsource/rubik": "^4.5.14", "@react-icons/all-files": "^4.1.0", + "big.js": "^6.2.1", "react": "^17.0.2", "react-dom": "^17.0.2", "react-query": "^4.0.0-alpha.20", @@ -23,6 +24,7 @@ "@testing-library/jest-dom": "^5.16.1", "@testing-library/react": "^11.2.7", "@testing-library/user-event": "^12.8.3", + "@types/big.js": "^6.2.2", "@types/jest": "^27.4.1", "@types/node": "^12.20.37", "@types/react": "^17.0.37", @@ -5709,6 +5711,12 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/big.js": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@types/big.js/-/big.js-6.2.2.tgz", + "integrity": "sha512-e2cOW9YlVzFY2iScnGBBkplKsrn2CsObHQ2Hiw4V1sSyiGbgWL8IyqE3zFi1Pt5o1pdAtYkDAIsF3KKUPjdzaA==", + "dev": true + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -7309,12 +7317,15 @@ } }, "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.1.tgz", + "integrity": "sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ==", "engines": { "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bigjs" } }, "node_modules/binary-extensions": { @@ -14664,6 +14675,15 @@ "node": ">=8.9.0" } }, + "node_modules/loader-utils/node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", diff --git a/package.json b/package.json index 2763ecc..46247e0 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@testing-library/jest-dom": "^5.16.1", "@testing-library/react": "^11.2.7", "@testing-library/user-event": "^12.8.3", + "@types/big.js": "^6.2.2", "@types/jest": "^27.4.1", "@types/node": "^12.20.37", "@types/react": "^17.0.37", @@ -34,8 +35,8 @@ "dotenv": "^16.0.1", "eslint-config-react-app": "^7.0.0", "prettier": "^2.6.0", - "ts-jest": "^27.1.3", - "react-scripts": "5.0.1" + "react-scripts": "5.0.1", + "ts-jest": "^27.1.3" }, "dependencies": { "@chakra-ui/react": "^1.8.6", @@ -43,6 +44,7 @@ "@emotion/styled": "^11.8.1", "@fontsource/rubik": "^4.5.14", "@react-icons/all-files": "^4.1.0", + "big.js": "^6.2.1", "react": "^17.0.2", "react-dom": "^17.0.2", "react-query": "^4.0.0-alpha.20", diff --git a/src/helpers/__tests__/common.test.ts b/src/helpers/__tests__/common.test.ts index 54b51d8..ed62c97 100644 --- a/src/helpers/__tests__/common.test.ts +++ b/src/helpers/__tests__/common.test.ts @@ -5,7 +5,7 @@ function setupParseNumberToBigInt( numStr: string, maxDigits?: number, ) { - expect(parseNumberToBigInt(parseFloat(numStr), maxDigits)).toEqual( + expect(parseNumberToBigInt(numStr, maxDigits)).toEqual( expectedBigInt, ) } @@ -15,6 +15,7 @@ describe("parseNumberToBigInt", () => { setupParseNumberToBigInt(BigInt(1000000000), "1.0") setupParseNumberToBigInt(BigInt(1000005599), "1.000005599") setupParseNumberToBigInt(BigInt(1000000), ".001") + setupParseNumberToBigInt(BigInt("1000000000000001000000000"), "1000000000000001") setupParseNumberToBigInt(BigInt(1), ".000000001") setupParseNumberToBigInt(BigInt(1), ".00001", 5) setupParseNumberToBigInt(BigInt(10000), ".1", 5) @@ -37,6 +38,7 @@ describe("amountFormatter", () => { setupAmountFormatter("0.000000001", BigInt(1)) setupAmountFormatter("1", BigInt(1000000000)) setupAmountFormatter("120.000000005", BigInt(120000000005)) + setupAmountFormatter("1,000,000,000,000,001", BigInt("1000000000000001000000000")) setupAmountFormatter("1.5", BigInt(150000), undefined, 5) setupAmountFormatter("155.55559", BigInt(15555559), undefined, 5) setupAmountFormatter("9,155.55559", BigInt(915555559), undefined, 5) diff --git a/src/helpers/common.ts b/src/helpers/common.ts index 4ddaf1c..5653cf7 100644 --- a/src/helpers/common.ts +++ b/src/helpers/common.ts @@ -1,3 +1,8 @@ +import Big from "big.js" + +// Big constructor will throw if argument is not a string or Big +Big.strict = true + const DEFAULT_MAX_DIGITS = 9 export const makeShortId = (idString: string): string => { @@ -7,20 +12,24 @@ export const makeShortId = (idString: string): string => { } export const parseNumberToBigInt = ( - v: number, + v: string, maxDigits: number = DEFAULT_MAX_DIGITS, -) => BigInt(Math.round(v * 10 ** maxDigits)) +) => { + const amount = Big(v) + const precision = Big(`1e${maxDigits}`) + const b = amount.times(precision).toFixed() + return BigInt(b) +} export const amountFormatter = ( amt: bigint, minDigits: number = 0, maxDigits: number = DEFAULT_MAX_DIGITS, ) => { - const amount = parseFloat(amt.toString()) / 10 ** maxDigits - const amountString = new Intl.NumberFormat("en-US", { - minimumFractionDigits: minDigits, - maximumFractionDigits: maxDigits, - }).format(amount) - - return amountString + const precision = Big(`1e${-maxDigits}`) + const amount = Big(amt.toString()).times(precision) + return new Intl.NumberFormat("en-US", { + minimumFractionDigits: minDigits, + maximumFractionDigits: maxDigits, + }).format(amount.toFixed() as any) // Typescript complains about the string parameter but it should be supported }