-
Notifications
You must be signed in to change notification settings - Fork 308
use cents instead of decimal #412
Comments
👍 |
1 similar comment
👍 |
@whit537 can you expand on how this is beneficial? Even if we moved everything to pennies so we used whole numbers everywhere, we'd still have to use |
I agree with Sean, and if this is impacting the tips table, then it will I agree about having a precision limitation but it should at least 8 |
If we want to keep it in the new "Ready to Start" label we should reach an agreement on what the benefits are and why we want this. Right now we have 2 decimal places so going for cents does not take anything from us. What would we gain? |
I got the impression from @mjallday at Balanced that cents is sort of the normal way to implement currency. I do see it as being simpler to work with than |
No one has assigned this to themselves, so I am removing from "Ready to Start." |
I would say close this. I'm not convinced it's something worth doing. |
Yeah, could anyone hash out the benefits of doing this? |
I don't have anything beyond that. I have a nagging feeling that this could become an issue at scale(?) and it will be easier to fix now. I suppose we can close for now, though, in a fit of not prematurely optimizing. |
As I understand it we would have to use Decimal in Python anyway, wouldn't we? |
If I don't answer this in a few days, you should make me feel bad, as I do have strong feels about this... |
Also, !m @steveklabnik, and @seanlinsley again (IRC). :-) |
Whew. So. The issue basically boils down to two things:
Let's do both in turn. Problems with non-integer mathOkay, so, as you know, basically, computers can't really represent ℝ. So we use floating point numbers. The issue with IEEE 754 numbers is that from the ℝ perspective, you introduce inaccuracies. For example, Python, which uses double-precision IEEE 754 floats: >>> sum = 0.0
>>> for i in range(10):
... sum += 0.1
...
>>> sum
0.9999999999999999 Integers, however, do not have this problem. In our case, we want to represent tenths, so we can say that our base unit is 0.1, and do this instead: >>> sum = 0
>>> for i in range(100):
... sum += 1
...
>>> sum
10 By explicitly choosing a precision and then using integer maths, you can avoid whole classes of these kinds of errors. You also can't Superman III: http://en.wikipedia.org/wiki/Salami_slicing Social signalingThat said, you're probably okay. After all, the worst that happens is you have a cent or two here off, and Gittip isn't doing the kind of volume that would make this prohibitive. But by using cents, you communicate "we understand this issue, and are following other best practices." To me, using floating point numbers in your API says "I don't understand issues around representing fractional numbers," and since that's a huuuuge part of doing money stuff.... And that's how you end up with snarky tweets like mine. Which wasn't actually even pointed at Gittip. ConclusionSo that's what's up. Purely for the social signaling value, I think it's worth your time to change, though it might not be worth enough to prioritize it immediately. |
Gittip is not using floating point for dollars. We are using a decimal type, which does have exact representations for a few digits to the right of the decimal point. |
@bruceadams sure but as per @steveklabnik's point about social signaling above I think it's valuable to make sure that is communicated clearly. When I look at https://www.gittip.com/about/paydays.json or https://www.gittip.com/about/charts.json I see an API returning monetary values as floats. This makes me suspect that Gittip may not be handling those values safely (despite internal use of Decimals). It also requires any API consumer working with that data to convert responses into a safer representation before manipulating them or worse encourages use of monetary values in an unsafe format. I think it would be valuable to expose such data in a form which reflects a clear understanding of common problems manipulating monetary values and which encourages good practices on the part of consumers of your data. Integer amounts would be a good start, a set of an amount, unit, and currency might be even better. Perhaps deciding on a desired public representation of your data types can drive what, if any, change is needed internally. |
@jonah-carbonfive can you point us to APIs that we should emulate? |
@seanlinsley in @whit537 original ticket, he points to the Balanced and Strip APIs. Balanced: https://docs.balancedpayments.com/current/api#retrieve-a-credit |
@jonah-carbonfive touches on this, but I think it's good to make it an explicit goal of the API: You should make it difficult for users to do the wrong thing with your API. If you present the number in integer form, then a consumer of your API has to make the decision to convert it to a float. Hopefully folks are using something like the the money gem to handle working with currency. |
As @jaredonline mentioned I think the Ruby 'money' gem is a good example of how to represent these values. I think Square offers a reasonable example of how to represent money in an API response at https://connect.squareup.com/docs/connect/datatypes though it may be useful for responses to specifically note the units used to express each currency amount. |
@jonah-carbonfive makes extremely good observations about the Gittip code. I am certainly in favor of moving to using integers for money in Gttip (and elsewhere). I had supposed it wasn't too important due to the use of Decimal. I had not noticed that we aren't consistent about using decimal. Thank you @jonah-carbonfive for seeing that and pointing it out here! |
Using Decimal everywhere to avoid floating point issues should be just fine. Using integer cents is just another means to the same end. As for the API, the JSON spec doesn't mention anything about the internal representation of numbers. It does say this, which I found interesting:
This suggests that as far as JSON is concerned, there's nothing wrong with serializing monetary values as numbers and that a parser that read numbers as decimals is correct. In fact, you could even make the argument that JSON parsers that return numbers as floats are lossy and actually incorrect. 😀 |
Some arguments against using integer cents to represent currency:
In 15 years of developing e-commerce systems I've never had a problem which could've been fixed by changing from decimal types to integer types. |
@bruceadams Where are we not consistent? |
@pixeltrix yup, that's why you need to choose a precision too. |
#1673 is an example of a limitation of dealing with a fractional fee in a cent-based system. |
This is the strongest point in this thread, imo. @jeresig hit a snag using the Gittip API around this (though I don't have the reference in front of me atm). |
@whit537 I haven't done anything with gittip nor have I been on IRC anywhere in the past couple of days. Maybe you meant another frew? :) |
Sorry to bother you, @frew! :-( |
Ping @frioux. |
:-) |
Some pretty smart people recently released an OSS billing system. To avoid loss of precision involved in conversions (cents are not the smallest division of currency in the US, see your local gas station for evidence) they use integer millicents, IE 1000 millicents == 1¢. Just another option, but ultimately, floats and money are not a great idea. |
@frioux We're actually using Decimal, which maintains precision, but I find the math for cents to be the easiest to work with. That dot somehow breaks the number up too much for me >.> Millicents would probably also be fairly easy for me to work with |
There are no significant advantages in using cents instead of decimal. |
I'm using Decimal everywhere for dollar values but it turns out that best practice is to deal with cents. That's how the Balanced API is set up (Stripe, too). It makes it much easier cause then we can use plain ints instead of more complicated datatypes, in both Python and Postgres.
The text was updated successfully, but these errors were encountered: