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

What library features should BigDecimal have? #14

Open
littledan opened this issue Nov 13, 2019 · 30 comments
Open

What library features should BigDecimal have? #14

littledan opened this issue Nov 13, 2019 · 30 comments
Labels
help wanted Extra attention is needed

Comments

@littledan
Copy link
Member

On BigDecimal.prototype, what sorts of methods should we have, for mathematical calculations? toPrecision, toExponential and toFixed (analogous to Number) seem like givens. Other possibilities:

@chicoxyzzy
Copy link
Member

For some use cases it would be very useful to have BigDecimal.prototype.decimalPlaces. Note that decimalPlaces methods from BigNumber.js and from Decimal.js will return 0 for .00000 as 0 and .00000 are indistinguishable in those libraries.

@chicoxyzzy
Copy link
Member

chicoxyzzy commented Nov 14, 2019

round, floor and ceil would be really useful. BigDecimal keeps number of decimal places, so all of them should accept parameter to get necessary precision. It could be a number of decimal places itself (or integer part if negative), i.e.

BigDecimal(.1234567).round(4) === 0.1235m;
// or
Object.is(BigDecimal(123.4567).round(-1), 120m);

, or an exponent parameter

BigDecimal(.1234567).round(-4) === 0.1235m;
// or
Object.is(BigDecimal(123.4567).round(1), 120m);

The latter seems more intuitive to me.

@chicoxyzzy
Copy link
Member

chicoxyzzy commented Nov 14, 2019

Thinking again, number of decimal places could be more intuitive if we'll have decimalPlaces method

// `round` takes  a number of decimal places
bd1.round(bd2.decimalPlaces()); // instead of `-bd2.decimalPlaces()`
// also, `bd2.decimalPlaces` getter?

Alternatively we can introduce precision (bikeshed the name)

// `round` takes  an exponent
bd1.round(bd2.precision()); // or `bd2.precision` getter

@littledan
Copy link
Member Author

If we have a round method, should we have options for different modes for how to round .5? (e.g., half goes up, half goes down, half goes to even?)

@chicoxyzzy
Copy link
Member

chicoxyzzy commented Nov 14, 2019

Yes, rounding modes are very useful indeed!

For reference:

@littledan
Copy link
Member Author

Can you say more about use cases for these that you're aware of? I can see that different Decimal libraries have decided to include different numbers of rounding modes. Which should we include?

@chicoxyzzy
Copy link
Member

chicoxyzzy commented Nov 14, 2019

HALF_UP mode (which is default rounding mode in Decimal.js) could be useful for charts (like candle chart) to make them look better. Math.round in JavaScript uses what's named HALF_CEIL in Decimal.js (this makes -0.5 round to -0), which is not very useful in fintech IMO, but should be present as well. Both UP and DOWN are often used for rounding ask/bid, order price, etc. (though this could be handled in different way with just floor/ceil and additional condition with order side or number sign). I've never used HALF_EVEN AFAIR.

@MaxGraey
Copy link

HALF_EVEN useful when you should do several intermediate roundings between operations. Because it has cumulative result to be a true average, and not skewed up or down. Also it uses in statistics and other math. Btw WebAssembly always round-to-nearest ties-to-even which in average has less rounding errors for cumulative operations as I mentioned before.

@littledan
Copy link
Member Author

Well, JS Number arithmetic operations round half-even, so maybe you have used it!

@chicoxyzzy
Copy link
Member

abs is another frequently used function

@qzb
Copy link

qzb commented Nov 15, 2019

Well, JS Number arithmetic operations round half-even, so maybe you have used it!

@littledan I didn't knew about that. Could you provide some example to illustrate this?

@chicoxyzzy
Copy link
Member

@qzb it's in the IEEE 754 standard, I believe

@MaxGraey
Copy link

MaxGraey commented Nov 15, 2019

btw Math.round is not round half-even, it's just equivalent to Math.floor(x + 0.5) but f64.nearest/f32.nearest in WebAssembly is really rounding to half-even.

I made little snippet to demonstrate this with emulation nearest:
rounding

[-2.6, -2.5, -1.5, -1.4, 0, 1.4, 1.5, 2.5, 2.6].map(x => Math.floor(x + 0.5)).reduce((a, b) => a + b, 0)
> 2
// same as Math.round

@littledan
Copy link
Member Author

It's not about Math.round, but rather all operations normally. For example, see the definition of multiplication on Numbers, whose last bullet point mentions

the product is computed and rounded to the nearest representable value using IEEE 754-2008 roundTiesToEven mode.

I think this mode will be important if we want to support high precision numerical calculations, to reduce errors.

@littledan
Copy link
Member Author

littledan commented Jan 8, 2020

QuickJS's 2020-01-05 release includes sqrt and round. The current README mentions pow, which QuickJS omits in that version. In personal communication, Fabrice Bellard writes,

The generic case of the power operator ("**") is quite difficult to
implement (it is the most complicated transcendental floating point
operation). If you keep the infinite precision design, I suggest to only
support positive integer exponents because it corresponds to iterated
infinite precision multiplications. Negative integer exponents could be
added provided the division is exact.

Including a generic BigDecimal.pow() significantly complicates the implementation because you need exp() and log() to implement it (so you can include BigDecimal.exp and BigDecimal.log at no addional cost !). Then you introduce the question of correctly rounding it or not (correctly rounding is more complicated to implement but gives deterministic results which is good for testing).

If you want to keep the implementation as simple as possible you should omit ** and BigDecimal.pow.

Currently QuickJS currently only supports positive integer exponents in ** and has no support for BigDecimal.pow or other transcendental functions.

For completeness, QuickJS supports BigDecimal.add/sub/mul with an
optional rounding object as for the division. It allows to bound the
memory if the user needs it. As for BigDecimal.sqrt,
maximumFractionDigits is not supported but could be added.

Personally, with this feedback, I'm leaning towards including add/sub/mul as well as pow/exp/log and sqrt, unless we find that the implementations will be way too difficult. Of course everything would be defined to give correct rounding. I'd omit the restricted form of **.

@ljharb
Copy link
Member

ljharb commented Jan 8, 2020

What do you mean, omit the restricted form? Are you referring to fabrice’s suggestion of only positive exponents?

@littledan
Copy link
Member Author

@ljharb Yes.

@peteroupc
Copy link

peteroupc commented Apr 22, 2020

Of the calculation methods in the current proposal, perhaps the only exotic one is the "partition" method. I haven't seen a method like that in any arbitrary-precision number library, nor does a similar method appear in the General Decimal Arithmetic Specification or any other specification or standard I am aware of. Usually some kind of remainder method or a divide-and-remainder method (e.g., in Java's BigDecimal) occurs in libraries and specifications instead.

In any case, the "partition" method, if its definition is as suggested in #13 (comment), could get unwieldy and take unnecessary memory if the number of partitions gets very large.

@jessealama
Copy link
Collaborator

I'd like to offer some support for square roots as an "advanced" function that ought to be included in the standard library.

One reason for including sqrt (in the context of our leaning toward Decimal128 as the underlying data model for Decimal) is that IEEE 754 actually lists sqrt as a non-optional function to be implemented. (This is not, by itself, a knock-down argument for including sqrt. But I think we should value the years -- decades? -- of research that went into IEEE 754 Decimal128/Decimal64/etc. Surely they have done a lot of analysis of use cases for decimal than we have and there's a good reason why sqrt is in the "must implement" list.)

Another reason: although the bulk of the feedback we've received from JS developers suggests that basic arithmetic (addition, multiplication, etc.) is likely to be sufficient, square roots do indeed pop up occasionally. They are used for computing distances and indeed presenting such numbers for human consumption.

@jessealama
Copy link
Collaborator

I'd like to argue that we don't need trigonometric functions in Decimal.

The Math object already has the (hyperbolic) trig functions. If one wants to compute the cosine of a Decimal value, the way to do it would be to convert the Decimal value to a string, cast that to a Number, call Math.cos on that, get enough decimal digits of that string by using toFixed, and then (if necessary) convert that string into a Decimal. If one is worried about any possible rounding errors, just call toFixed with an extra decimal digit (or two, to be really safe).

That sounds like a lot of juggling, but it works. I'm having trouble seeing the added value of trig functions in Decimal, given that they already exist out-of-the-box in Math, and are (I assume) battle-tested.

From a mathematical point of view, the trig functions are (almost) never going to produce an exact decimal value (which are all rational numbers). (By the way, this is as true of BigDecimal as it is of Decimal128.) Thus, the exact semantics that Decimal offers is a bit of an illusion when it comes to such functions. The computation of such functions, whether in decimal or binary floats, is going to have to stop after a certain number of digits, and the result is a best-effort approximation.

What are some use cases for trig functions that would require, say, 20 or more decimal digits of precision (something that we could get with Decimal128)? In other words, do Math's trig functions suffer from a problem that could be solved in Decimal?

@jessealama
Copy link
Collaborator

Just extending my previous argument against the trig functions:

I think Decimal also ought to exclude logarithms (whether natural or base ten) and exponentiation (whether natural exponentiation or a two-argument pow variant). The reasoning for excluding them is the same: battle-tested support already exists for these in Math and I struggle to imagine use cases where one needs even more precision (digits) than are on offer from JS's 64-bit binary floats.

One counterargument I can entertain is that exponentiation and logarithm do show up in some business/financial calculations, such as computing compound interest of an investment, economic growth/decay formulas, and so on. But, again, the counterargument would be: does Math's version of these functions, working with JS Numbers, deliver insufficient accuracy?

@ljharb
Copy link
Member

ljharb commented Aug 29, 2023

Exponentiation is very critical, and has an operator, **. I don’t see how it can be excluded.

@jessealama jessealama added the help wanted Extra attention is needed label Aug 29, 2023
@shuckster
Copy link

does Math's version of these functions, working with JS Numbers, deliver insufficient accuracy?

It's not just a question of accuracy, but of IEEE-754 confusion, especially in the world of finance. Enough so that there are dozens of user-land libs that attempt to address the problem.

The issue is especially prevalent with crypto-currency, where figures regularly sit well beyond the upper-bounds of JavaScript's Number type.

@jessealama
Copy link
Collaborator

Exponentiation is very critical, and has an operator, **. I don’t see how it can be excluded.

One version that I think would be valuable would be to have exponentiation for integer exponents (negative or not). It's a pretty straightforward implementation.

Exponentiation for non-integer exponents is a bit more exotic. There are some use cases in business/finance calculations. But the question, to my mind, would be whether a Decimal version of this would have any added value compared to Math.exp.

@jessealama
Copy link
Collaborator

does Math's version of these functions, working with JS Numbers, deliver insufficient accuracy?

It's not just a question of accuracy, but of IEEE-754 confusion, especially in the world of finance. Enough so that there are dozens of user-land libs that attempt to address the problem.

The issue is especially prevalent with crypto-currency, where figures regularly sit well beyond the upper-bounds of JavaScript's Number type.

Quite right! Decimal does aim to provide a big improvement over JS's Number. I'm looking forward to a day where we can handle a lot of digits, secure in the knowledge that we're working with them correctly. What I had in mind, in my recent contributions to this issue, was whether the Decimal API should include more "exotic" things like exponentiation (with non-integer exponents), logarithms, and trigonometric functions. My gut intuition is that, for cryptocurrency applications, it's enough to (1) be able to represent decimals accurately, and (2) do basic arithmetic. My hunch is that such applications probably wouldn't need trigonometric functions, etc. I guess crypto applications are basically no different from other finance/business calculations, the only difference being that many more digits need to be supported. And that's what Decimal definitely will give us. But thinking about mathematical functions beyond basic arithmetic that should be available: If you can give me some pointers which would be reason to believe that trig, logarithms, and exponentiation are used in cryptocurrency, please do let me know!

@ljharb
Copy link
Member

ljharb commented Aug 30, 2023

One version that I think would be valuable would be to have exponentiation for integer exponents

During BigInt, multiple delegates made clear their extreme discomfort with surprising value-dependent semantics for number operations, so I'm not sure if this would be viable.

@jessealama
Copy link
Collaborator

One version that I think would be valuable would be to have exponentiation for integer exponents

During BigInt, multiple delegates made clear their extreme discomfort with surprising value-dependent semantics for number operations, so I'm not sure if this would be viable.

Just to reformulate: Do you mean that we ought to support a ** b for all Decimal values a and b (which is a valid suggestion), or not support exponentiation at all (because the "easy" approach of restricting the second argument to integers would likely encounter resistance)?

@ljharb
Copy link
Member

ljharb commented Aug 31, 2023

The former, since decimal numbers without exponentiation seems untenable to me.

@jessealama
Copy link
Collaborator

The line of reasoning make sense. I'm curious to know why a version of Decimal without exponentiation would be untenable.

I find myself conflicted about whether Decimal needs things like exp, log, sqrt, and trig functions. Part of me says "hell yeah!" Another part of me looks at the JS developer survey data and finds that there's very little expressed need for those.

What's your take on the argument that, if one needs exp, log, etc., just use Math? To my eyes, the only value that Decimal could add, concerning those functions, would be that you would get more decimal digits from the results. But do we really need, say, 25+ decimal digits for a call to exp? If we use Math, we get a pretty good result, albeit with somewhat fewer decimal digits. (The argument is restricted to "advanced" functions where an exact result is, in general, unavailable, because the values are almost always irrational numbers. I'm not talking about addition, multiplication, etc., where binary floats typically yield inexact results whereas Decimal yields exact results.)

@ljharb
Copy link
Member

ljharb commented Sep 1, 2023

The primary benefit to me for Decimal is if it can replace Number entirely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

8 participants