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

Balances should be BigDecimal, or reason why they are not should be documented. #18

Open
myrle-krantz opened this issue Dec 14, 2015 · 13 comments
Milestone

Comments

@myrle-krantz
Copy link

Transmitting balances as strings is not intuitive and makes programming harder than it needs to be. Also this circumvents they otherwise strong java type system.

@myrle-krantz myrle-krantz changed the title Balances should be int or long, or reason why they are not should be documented. Balances should be BigDecimal, or reason why they are not should be documented. Feb 10, 2016
@jillesvangurp
Copy link
Contributor

Stellar stores them as 64 bit Long values. So, they are NOT decimals but values of your token and an absolute amount of stroops. Converting to and from decimals you risk rounding errors and other funkyness. I have a little TokenAmount class in my kotlin client wrapper that allows you to do math with tokens and conversions in a sane way.

@myrle-krantz
Copy link
Author

BigDecimal != double. BigDecimals are made to deal with the "funkyness" properly.

@graydon
Copy link

graydon commented Feb 28, 2019

BigDecimal is arbitrary precision. It has configurable rounding for dealing with un-representable results such as .33333, the same way (say) division of 64 bit longs needs to round such things, but otherwise it is essentially a "stronger" (though more expensive) representation of numbers than int64.

Unfortunately, this strength means that BigDecimal does not behave identically to calculations done inside stellar core unless carefully configured: our int64 payment and account-balance arithmetic does round even division-by-2 for units in the least position, whereas a BigDecimal will happily represent 0.5 and so forth. Furthermore, BigDecimal will not overflow in the cases where int64 will. So if you want to faithfully model stellar's arithmetic in BigDecimal, you'll need to be implementing all the rounding and boundary conditions of int64 yourself; might as well use int64 in that case.

Fwiw, we considered (at length!) using a more-general decimal type for Stellar but the core team concluded that the cost of doing arbitrary precision decimal was too high, computationally, and the behaviour of floating-point decimal was too unfamiliar and/or the software implementations too inaccessible across multiple languages. As well, many developers simply treat anything that says "floating point" with universal suspicion, no matter how well-specified and deterministic. So we went with fixed-precision int64 and a very small least-precision unit, which is both well understood and still small and efficient.

@myrle-krantz
Copy link
Author

Excellent arguments. But none of them in favor of using a string in the API. Users will then convert to just about anything, but not the right thing.

@graydon
Copy link

graydon commented Feb 28, 2019

Oh, no debate there! I do not know how strings came to be used here. Perhaps @bartekn knows?

@ire-and-curses
Copy link

The rationale is described in the getting started guide.

You should also note that the amount is a string rather than a number. When working with extremely small fractions or large values, floating point math can introduce small inaccuracies. Since not all systems have a native way to accurately represent extremely small or large decimals, Stellar uses strings as a reliable way to represent the exact amount across any system.

@graydon
Copy link

graydon commented Feb 28, 2019

@ire-and-curses the amounts in the strings are strictly integers though, not fractions.

See discussion of precision and representation here and the fact that the Java code supporting it just parses the inputs as int64 values, does not apply any scaling.

@ire-and-curses
Copy link

Right. I'm assuming it's a consistency thing. They're strings here because numbers are represented as strings everywhere beyond Horizon. And that's presumably because of JS using Float64 for everything natively, so JSON numbers would be problematic (and an issue in the JS-SDK, by far the most popular of the Stellar SDKs). But I wasn't there for the original discussion so this is all speculation.

@tomerweller
Copy link
Contributor

@myrle-krantz there's definitely an argument to be made for switching to representing amounts as long (int64) stroops rather than the funky strings that they are right now. I'd open a new issue with a title that represents that + outline the reasons why int64 is so much better that it's worth deprecating the strings + include a suggested deprecation path for moving forward without breaking existing consumers.

@graydon
Copy link

graydon commented Mar 1, 2019

Java supports ad-hoc overloading, so for method parameters you can just add variants of the same-named methods everywhere with long inputs instead of String (eg. PaymentOperation(KeyPair destination, Asset asset, long amount)). It does not support overloading on return type, so you'd need to name accessors differently, eg. add getAmountLong() or something.

(I mean: to avoid breaking existing clients.)

@myrle-krantz
Copy link
Author

It's been a while, but I believe I discussed this with bartek and that he asked me to file the ticket.

@myrle-krantz
Copy link
Author

@tomerweller The original title of the ticket was "Balances should be int or long, or reason why they are not should be documented". You're welcome to change the title back, close the issue or do nothing. I gain nothing one way or the other.

@jillesvangurp
Copy link
Contributor

@myrle-krantz I had no idea the ticket was renamed; naming it back sounds like a good idea since I don't think using big decimal is a good solution.

Given that stellar stores long values, the proper way would be using longs and not expose the user to precision errors related to using decimals or floats. BigDecimal has the problem it would end up being more precise than what happens in Stellar. Also, you can represent values in it that do not fit in a long.

The TokenAmount class I mentioned above would work in Java as well. It has an internal representation that uses a long, but supports conversions to deal with strings, doubles, and longs. It also handles token math internally by doing it correctly on the internal representation. That's why I developed this so that I could do simple arithmetic and get correct results.

@bartekn I'd recommend either switching to long or adopting something similar to what I did. String sort of moves the problem to the user and there's a big risk they do it wrong. Either way, it would be a compatibility breaking change.

@nullstyle nullstyle added this to the vNext milestone Apr 17, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants
@nullstyle @graydon @tomerweller @jillesvangurp @ire-and-curses @myrle-krantz and others