From 13dae41c5708b4a7b74a0a043b34868a8363a78d Mon Sep 17 00:00:00 2001 From: Fabian Date: Thu, 25 Apr 2019 16:34:03 +0200 Subject: [PATCH] Fabo/fix rounding issue in small number (#2487) * fix rounding issue in small number * added comment * 'changelog' --- app/src/renderer/scripts/num.js | 186 ++++++++++++++-------------- changes/fao_fix-rounding | 1 + test/unit/specs/scripts/num.spec.js | 98 +++++++-------- 3 files changed, 146 insertions(+), 139 deletions(-) create mode 100644 changes/fao_fix-rounding diff --git a/app/src/renderer/scripts/num.js b/app/src/renderer/scripts/num.js index 7441159dcb..5b40e693fd 100644 --- a/app/src/renderer/scripts/num.js +++ b/app/src/renderer/scripts/num.js @@ -1,90 +1,96 @@ -"use strict" -import BigNumber from "bignumber.js" - -/** - * Defines all numerical methods - * @module num - */ - -export const SMALLEST = 1e-6 -const language = window.navigator.userLanguage || window.navigator.language -export function full(number = 0) { - return new Intl.NumberFormat(language, { minimumFractionDigits: 6 }) - .format(number) -} -export function shortNumber(number = 0) { - return new Intl.NumberFormat(language, { minimumFractionDigits: 4 }).format(number) + `…` -} -export function pretty(number = 0) { - return new Intl.NumberFormat( - language, - { minimumFractionDigits: 2, maximumFractionDigits: 2 } - ).format(Math.round(number * 100) / 100) -} -// pretty print long decimals not in scientific notation -export function prettyDecimals(number = 0) { - let longDecimals = new Intl.NumberFormat( - language, - { minimumFractionDigits: 20, maximumFractionDigits: 20 } - ).format(number) - - // remove all trailing zeros - while (longDecimals.charAt(longDecimals.length - 1) === `0`) { - longDecimals = longDecimals.substr(0, longDecimals.length - 1) - } - - // remove decimal separator from whole numbers - if (Number.isNaN(Number(longDecimals.charAt(longDecimals.length - 1)))) { - longDecimals = longDecimals.substr(0, longDecimals.length - 1) - } - - return longDecimals -} -export function prettyInt(number = 0) { - return new Intl.NumberFormat(language).format(Math.round(number)) -} -export function percentInt(number = 0) { - return new Intl.NumberFormat(language).format(Math.round(number * 100)) + `%` -} -export function percent(number = 0) { - return new Intl.NumberFormat( - language, - { minimumFractionDigits: 2, maximumFractionDigits: 2 } - ).format(Math.round(number * 10000) / 100) + `%` -} -export function atoms(number = 0) { - return BigNumber(number).div(1e6).toNumber() -} -export function uatoms(number = 0) { - return BigNumber(number).times(1e6).toString() -} - -// convert micro denoms like uatom to display denoms like ATOM -export function viewDenom(denom) { - if (denom.charAt(0) === `u`) { - return denom.substr(1).toUpperCase() - } - return denom.toUpperCase() -} - -export function viewCoin({ amount, denom }) { - return { - amount: full(atoms(amount)), - denom: viewDenom(denom) - } -} - -export default { - SMALLEST, - atoms, - uatoms, - viewDenom, - viewCoin, - full, - shortNumber, - pretty, - prettyInt, - percent, - percentInt, - prettyDecimals -} +"use strict" +import BigNumber from "bignumber.js" + +/** + * Defines all numerical methods + * @module num + */ + +// truncate decimals to not round when using Intl.NumberFormat +function truncate(number, digits) { + return Math.trunc(number * Math.pow(10, digits)) / Math.pow(10, digits) +} + +export const SMALLEST = 1e-6 +const language = window.navigator.userLanguage || window.navigator.language +export function full(number = 0) { + return new Intl.NumberFormat(language, { minimumFractionDigits: 6 }) + .format(truncate(number, 6)) +} +export function shortNumber(number = 0) { + return new Intl.NumberFormat(language, { minimumFractionDigits: 4 }) + .format(truncate(number, 4)) + `…` +} +export function pretty(number = 0) { + return new Intl.NumberFormat( + language, + { minimumFractionDigits: 2, maximumFractionDigits: 2 } + ).format(Math.round(number * 100) / 100) +} +// pretty print long decimals not in scientific notation +export function prettyDecimals(number = 0) { + let longDecimals = new Intl.NumberFormat( + language, + { minimumFractionDigits: 20, maximumFractionDigits: 20 } + ).format(number) + + // remove all trailing zeros + while (longDecimals.charAt(longDecimals.length - 1) === `0`) { + longDecimals = longDecimals.substr(0, longDecimals.length - 1) + } + + // remove decimal separator from whole numbers + if (Number.isNaN(Number(longDecimals.charAt(longDecimals.length - 1)))) { + longDecimals = longDecimals.substr(0, longDecimals.length - 1) + } + + return longDecimals +} +export function prettyInt(number = 0) { + return new Intl.NumberFormat(language).format(Math.round(number)) +} +export function percentInt(number = 0) { + return new Intl.NumberFormat(language).format(Math.round(number * 100)) + `%` +} +export function percent(number = 0) { + return new Intl.NumberFormat( + language, + { minimumFractionDigits: 2, maximumFractionDigits: 2 } + ).format(Math.round(number * 10000) / 100) + `%` +} +export function atoms(number = 0) { + return BigNumber(number).div(1e6).toNumber() +} +export function uatoms(number = 0) { + return BigNumber(number).times(1e6).toString() +} + +// convert micro denoms like uatom to display denoms like ATOM +export function viewDenom(denom) { + if (denom.charAt(0) === `u`) { + return denom.substr(1).toUpperCase() + } + return denom.toUpperCase() +} + +export function viewCoin({ amount, denom }) { + return { + amount: full(atoms(amount)), + denom: viewDenom(denom) + } +} + +export default { + SMALLEST, + atoms, + uatoms, + viewDenom, + viewCoin, + full, + shortNumber, + pretty, + prettyInt, + percent, + percentInt, + prettyDecimals +} diff --git a/changes/fao_fix-rounding b/changes/fao_fix-rounding new file mode 100644 index 0000000000..71b7645e5d --- /dev/null +++ b/changes/fao_fix-rounding @@ -0,0 +1 @@ +[Fixed] [#2487](https://github.com/cosmos/lunie/pull/2487) Remove rounding in small numbers @faboweb \ No newline at end of file diff --git a/test/unit/specs/scripts/num.spec.js b/test/unit/specs/scripts/num.spec.js index 7aeaf2f388..6eb8c2e212 100644 --- a/test/unit/specs/scripts/num.spec.js +++ b/test/unit/specs/scripts/num.spec.js @@ -1,49 +1,49 @@ -import num from "renderer/scripts/num" - -describe(`number helper`, () => { - it(`should format numbers showing many decimals`, () => { - expect(num.full(1001950.123456)).toBe(`1,001,950.123456`) - }) - - it(`should format numbers showing many decimals`, () => { - expect(num.shortNumber(1.123456789)).toBe(`1.1235…`) - }) - - it(`should format numbers showing few decimals`, () => { - expect(num.pretty(1001950.123456)).toBe(`1,001,950.12`) - }) - - it(`should format numbers showing no decimals`, () => { - expect(num.prettyInt(1001950.123456)).toBe(`1,001,950`) - }) - - it(`should format percent without decimals`, () => { - expect(num.percentInt(0.2612)).toBe(`26%`) - }) - - it(`should format percent with decimals`, () => { - expect(num.percent(0.2612)).toBe(`26.12%`) - }) - - it(`should format long decimals well`, () => { - expect(num.prettyDecimals(1e-8)).toBe(`0.00000001`) - }) - - it(`should format long decimals well if whole number`, () => { - expect(num.prettyDecimals(12)).toBe(`12`) - }) - - it(`should convert utam denom to atom denom`, () => { - expect(num.viewDenom(`uatom`)).toBe(`ATOM`) - }) - - it(`should convert SDK coins to view coins`, () => { - expect(num.viewCoin({ - denom: `uatom`, - amount: 1000000 - })).toEqual({ - denom: `ATOM`, - amount: `1.000000` - }) - }) -}) +import num from "renderer/scripts/num" + +describe(`number helper`, () => { + it(`should format numbers showing many decimals`, () => { + expect(num.full(1001950.123456)).toBe(`1,001,950.123456`) + }) + + it(`should format numbers showing many decimals`, () => { + expect(num.shortNumber(1.123456789)).toBe(`1.1234…`) + }) + + it(`should format numbers showing few decimals`, () => { + expect(num.pretty(1001950.123456)).toBe(`1,001,950.12`) + }) + + it(`should format numbers showing no decimals`, () => { + expect(num.prettyInt(1001950.123456)).toBe(`1,001,950`) + }) + + it(`should format percent without decimals`, () => { + expect(num.percentInt(0.2612)).toBe(`26%`) + }) + + it(`should format percent with decimals`, () => { + expect(num.percent(0.2612)).toBe(`26.12%`) + }) + + it(`should format long decimals well`, () => { + expect(num.prettyDecimals(1e-8)).toBe(`0.00000001`) + }) + + it(`should format long decimals well if whole number`, () => { + expect(num.prettyDecimals(12)).toBe(`12`) + }) + + it(`should convert utam denom to atom denom`, () => { + expect(num.viewDenom(`uatom`)).toBe(`ATOM`) + }) + + it(`should convert SDK coins to view coins`, () => { + expect(num.viewCoin({ + denom: `uatom`, + amount: 1000000 + })).toEqual({ + denom: `ATOM`, + amount: `1.000000` + }) + }) +})