Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: formatting big.js objects with scale larger than 21 fails #716

Open
2 tasks done
bkiac opened this issue Jan 26, 2023 · 7 comments
Open
2 tasks done

[Bug]: formatting big.js objects with scale larger than 21 fails #716

bkiac opened this issue Jan 26, 2023 · 7 comments
Assignees
Labels
bug Something isn't working

Comments

@bkiac
Copy link

bkiac commented Jan 26, 2023

Is there an existing issue for this?

  • I have searched the existing issues

Current behavior

I'm using big.js with dinero because I need it for cryptocurrencies and large USD values but after following the documentation to create a custom calculator I noticed that formatting breaks if the scale is too large (>= 22)

My first problem was that I receive arbitrary precision USD values from an API and I need to calculate the scale of these values. This is how I did it, but I feel I'm missing something and this has to be an overkill:

const usd = (dollars: string) => {
	// Get cents from dollars
	const cents = new Big(dollars).mul(conversion);
	// Calculate remaining fraction
	const fraction = cents.mod(1);
	const fractionLength = fraction.eq(0) ? 0 : fraction.c.length;
	// Set scale based on fraction length
	const scale = USD.exponent.plus(fractionLength);
	// Make sure integer is passed to dinero
	const amount = new Big(cents.mul(USD.base.pow(fractionLength).toFixed()));
	return dinero({
		currency: USD,
		amount,
		scale,
	});
};

and when I receive a value that has precision 22 or larger, toDecimal function breaks and the output is in scientific notation with multiple decimal points:

const n = usd("1000000000.9876543211211111111111")
toDecimal(n) // 1000000000.9.876543211211111111111e+21

Expected behavior

I had to create a custom formatter to make it work:

const formatter: Formatter<Big> = {
	toNumber: (v) => (v ? v.toNumber() : 0),
	toString: (v) => (v ? v.toFixed() : ''),
};

Unless I'm doing something incredibly stupid with parsing the arbitrary precision, I think this should be added to the docs. I can open a PR if you can point me in the right direction.

And a quick question, why is the amount arg in the formatter functions optional?

Steps to reproduce

https://replit.com/@bkiac/dinerowithbig#index.ts

Version

2.0.0-alpha.13

Environment

any

Code of Conduct

  • I agree to follow this project's Code of Conduct
@bkiac bkiac added the bug Something isn't working label Jan 26, 2023
@johnhooks
Copy link
Contributor

I tried to look through this one for you, but its hard to debug the issue without a Big.js calculator in the codebase. I could just paste yours in, but if we're committing time to the issue we might as well solve it for good. @sarahdayan are you interested in a Big.js Dinero calculator and supporting it? Or should we just find this bug?

@sarahdayan
Copy link
Collaborator

@johnhooks Big.js is installed as a dev dependency for tests, so we can use test files to debug this issue (for example, in this spec file).

@johnhooks
Copy link
Contributor

I found it. I'll take another look.

@johnhooks
Copy link
Contributor

johnhooks commented Feb 18, 2023

@bkiac you are 100% on the right track. The solution is to add your own formatter when you use createDinero, because the default formatter will not format Big.js correctly.

formatter = {
  toNumber: Number,
  toString: String,
},

In PR #724 I'm currently doing this:

const dinero = createDinero({
calculator: {
add: (a, b) => a.plus(b),
compare: (a, b) => a.cmp(b) as unknown as ComparisonOperator,
decrement: (v) => v.minus(new Big(1)),
increment: (v) => v.plus(new Big(1)),
integerDivide: (a, b) => a.div(b).round(0, Big.roundDown),
modulo: (a, b) => a.mod(b),
multiply: (a, b) => a.times(b),
power: (a, b) => a.pow(Number(b)),
subtract: (a, b) => a.minus(b),
zero: () => new Big(0),
},
formatter: {
toNumber: (v) => v.toNumber(),
toString: (v) => v.toFixed(),
},
});

And a quick question, why is the amount arg in the formatter functions optional?

I'm trying to track down why the formatter has to handle possible undefined values.

@johnhooks
Copy link
Contributor

johnhooks commented Feb 18, 2023

My first problem was that I receive arbitrary precision USD values from an API and I need to calculate the scale of these values. This is how I did it, but I feel I'm missing something and this has to be an overkill

Just realized this was a multipart question.

dinero.js does not provide a way to determine scale from an input amount. I can definitely see the utility of a function to handle this in a uniform way. But the library expects input to be a "whole" number, it's safest for JavaScript number type and the only option for bigint.

@johnhooks
Copy link
Contributor

johnhooks commented Feb 18, 2023

@bkiac I couldn't find any reason that the value parameters of the Formatter were optional. PR #725 makes them required. @sarahdayan is that alright with you? Because if value is undefined in any situation I would prefer an error to be thrown rather than receive an arbitrary value.

@bkiac
Copy link
Author

bkiac commented Feb 20, 2023

ty for taking a look at this, making value and formatter required with a custom calculator is a good solution 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants