Hacker News new | past | comments | ask | show | jobs | submit login

> The python example is using floats for currency.

Dumb question, but what is the proper way to handle currency? Custom number objects? Strings for any number of decimal places?




This is a complex topic, mainly for two reasons: 1. it works on two layers (storage and code) 2. there is a context to take care of.

[Modern] programming languages have decimal/rational data types, which (within limits) are exact. Where this is not possible, and/or it's undesirable for any reason, just use an int and scale it manually (e.g. 1.05 dollars = int 105).

However, point 2 is very problematic and important to consider. How do account 3 items that cost 1/3$ each (e.g. if in a bundle)? What if they're sold separately? This really depends on the requirements.

My 20 cents: if you start a project, start storing currency in an exact form. Once a project grows, correcting the FP error problem is a big PITA (assuming it's realistically possible).


>[Modern] programming languages have decimal/rational data types

This caveat is kind of funny, in light of COBOL having support for decimal / fixed precision data types baked directly into the language.

It's not a problem with "non-modern" languages, it's a problem with C and many of its successors. That's precisely why many "non-modern" languages have stuck around so long.

https://medium.com/the-technical-archaeologist/is-cobol-hold...

Additionally, mainframes are so strongly optimized for hardware-accelerated fixed point decimal computing that for a lot of financial calculations it can be legitimately difficult to match their performance with standard commercial hardware.


> It's not a problem with "non-modern" languages, it's a problem with C and many of its successors.

Not really. Any semi-decent modern language allows the creation of custom types which support the desired behavior and often some syntactic sugar (like operator overloading) to make their usage more natural. Take C++, for example, the archetypal "C successor": It's almost trivial to define a class which stores a fixed-precision number and overload the +, -, *, etc. operators to make it as convenient as a built-in type, and put it in library. In my book, this is vastly superior to making such a type a built-in, because you can never satisfy everyone's requirements.


It is also trivial to keep doing C mistakes with a C++ compiler, hence no matter how many ISO revisions it will still have, lack of safety due to C copy-paste compatibility will never be fixed.


> [...] no matter how many ISO revisions it will still have, lack of safety due to C copy-paste compatibility will never be fixed.

Okay, no idea how that's relevant to "built-in decimal types" vs "library-defined decimal types", but if it makes you feel better, you can do the same in Rust or Python, two languages which are "modern" compared to COBOL, don't inherit C's flaws, and which enable defining custom number types/classes/whatever together with convenient operator overloading.


Rust I agree, Python not really as the language doesn't provide any way to keep invariants.


> Python not really as the language doesn't provide any way to keep invariants

Again, how is that relevant? If there's no way to enforce an invariant in custom data types, then there's also no way to enforce invariants in code using built-in data types.


It is surely relevant.

Rust provides the mechanisms to enforce them, while in Python, like all dynamic languages, everything is up for grabs.


What I meant [1] was: In Python, invariants are enforced by conventions, not by the compiler. If that's not suitable for a given use case, then Python is entirely unsuited for that use case, regardless whether it provides built-in decimal types or user-defined decimal types. That's why I said that your objection regarding invariant enforcement is irrelevant to this discussion.

[1] (but was to lazy to write out)


It is quite simple to do the same in Julia


> How do account 3 items that cost 1/3$ each (e.g. if in a bundle)?

You never account for fractional discrete items, it makes no sense. A bundle is one product, and a split bundle is another. For products sold by weight or volume, it's usually handled with a unit price, and a fractional quantity. That way the continuous values can be rounded but money that is accounted for needs not be.


The problem is also stupid people and companies.

My last job they wanted me to invoice them hours worked, which was some number like 7.6.

This number plays badly when you run it through GST and other things - you get repeaters.

So I looked up common practice here, even tried asking finance who just said "be exact", and eventually settled on that below 1 cent fractions I would round up to the nearest cent in my favour for each line item.

First invoice I hand them, they manually tally up all the line items and hours, and complain it's over by 55 cents.

So I change it to give rounded line items but straight multiplied to the total - and they complain it doesn't match.

Finally I just print decimal exact numbers (which are occasionally huge) and they stop complaining - because excel is now happy the sums match when they keep second guessing my invoices.

All of this of course was irrelevant - I still had to put hours into their payroll system as well (which they checked against) and my contract specifically stated what my day rate was to be in lieu of notice.

So how should you do currency? Probably in whatever form that matches how finance are using excel, which does it wrong.


I wish this was untrue, but I have spent years hearing the words "why dont my reports match?" - no amount of logic, diagrams, explaining, the next quarter or instance - "why dont my reports match?"

BECAUSE EXCEL SUCKS MY DUDE.


Well, they did say to be exact, and you handed them approximations, so...


The “exact” version they wanted was full of approximations too. They just didn’t have enough numerical literacy to understand how to say how much approximation they are ok with.

I guarantee nothing in anyone’s time accounting system is measured to double-precision accuracy. Or at least, I’ve never quite figured out the knack myself for stopping work within a particular 6 picosecond window.


Sure, but at the end of the day someone had to pay me an integer amount of cents. They wanted a total which was a normal dollar figure. But when you sum up 7.6 times whatever a whole lot, you might get a nice round number or you might get an irrational repeater.

What's notable is clearly no one had actually thought this through at a policy level - the answer was "excel goes brrrr" depending on how they want to add up and subtotal things.


Generally what is done is that “int 1 != $0.01” rather it’s “int 100 = $0.01”, as in the base of the integer is 1/100th a cent. That doesn’t perfectly solve your example case perfectly though admittedly.


There's no one answer, but decimal counts of the smallest unit that needs to be measured is common. Like pennies in the US, or maybe "number of 1/10 pennies" if there's things like gasoline tax.


Say what you like about COBOL, but it got this stuff right.


You can use integers instead of decimal if you're using the smallest unit.


For Python, I prefer decimal.Decimal[1]. When you serialize, you can either convert it to a string (and then have your deserializer know the field type and automatically encode it back into a decimal) OR just agree all numeric values can only be ints or decimals. You can pass parse_float=decimal.Decimal to json.loads[2] to make this easier.

My most obnoxious and spicy programming take is that ints an decimals should be built-in and floats should require imports. I understand why though: Decimal encoding isn't anywhere near as standardized as other numeric types like integers or floating-point numbers.

[1] https://docs.python.org/3/library/decimal.html [2] https://docs.python.org/3/library/json.html


> My most obnoxious and spicy programming take is that ints an decimals should be built-in and floats should require imports

I don't care about making inexact numbers require imports, but the most natural literal formats should produce exact integers, decimals, and/or rationals.


An integer of the smallest denomination. For example, cents for the American dollar. And you probably would want to wrap it in a custom type to simplify displaying it properly, and maybe handle different currencies. If you language has a fixed point type that might also be appropriate, but that's pretty rare, and wouldn't work for currencies that aren't decimal (like the old british pound system).


Do they still use fractional cents (or whatever) in finance?

https://money.howstuffworks.com/personal-finance/financial-p...


What if I'm calculating sales tax? Can't use an integer anymore.


Yes, you can. There are algorithms for rounding up, rounding down, rounding to nearest, and banker's rounding, on the results of integer division. This is a solved problem.


To pile on, here's a copy/paste from when this was asked a few days ago:

Googler, opinions are my own. Over in payments, we use micros regularly, as documented here: https://developers.google.com/standard-payments/reference/gl...

GCP on there other hand has standardized on unit + nano. They use this for money and time. So unit would 1 second or 1 dollar, then the nano field allows more precision. You can see an example here with the unitPrice field: https://cloud.google.com/billing/v1/how-tos/catalog-api#gett...

Copy/paste the GCP doc portion that is relevant here:

> [UNITS] is the whole units of the amount. For example if currencyCode is "USD", then 1 unit is one US dollar.

> [NANOS] is the number of nano (10^-9) units of the amount. The value must be between -999,999,999 and +999,999,999 inclusive. If units is positive, nanos must be positive or zero. If units is zero, nanos can be positive, zero, or negative. If units is negative, nanos must be negative or zero. For example $-1.75 is represented as units=-1 and nanos=-750,000,000.


In its base unit. So cents in USD. Which can be an int64

Or if your language has something specific built in, use that.


> Or if your language has something specific built in, use that.

Unless your language is PostgreSQL's dialect of SQL, apparently. https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use...


It has the same issue that the other suggestion of your parent comment had: it can’t deal with fractions of cents, which is an issue you will most likely run into before you will into floating point rounding issues.


Of course for databases you should use a decimal.


> In its base unit. So cents in USD. Which can be an int64.

Note that if you use cents in the US so that everything is an integer then as long as you do not have to deal with amounts that are outside the range [-$180 trillion, $180 trillion] you can also use double. Double can exactly represent all integer numbers of cents in that range.

This may be faster than int64 on some systems, especially on systems that do not provide int64 either in hardware or in the language runtime so you'd have to do it yourself.


Not necessarily. It depends on the application.


Integer cents or an arbitrary precision decimal type.


Having worked on a POS system, the issue of using cents alone is if you've got something like "11% rebate" and you need to deal with fractional cents.

The arbitrary precision decimal type should be the default answer for currency until it is shown that the requirements no and at no time in the future will ever require fractional units of the smallest denomination.

As an aside, this may be constrained by the systems that the data is persisted into too... the Buffett Overflow is a real thing ( https://news.ycombinator.com/item?id=27044044 ).


A lot of good answers, but they mostly relate to accounting types of problems (which granted, is what you need to do with currency data 99% of the time)

I’d just add that if you are building a price prediction model, floats are probably what you need.


The example code is the start of an expense tracking tool...


Depends what you’re doing. In fact it’s not always wrong to use floats for currency. For accounting you should probably use a fixed-precision decimal type.


If someone asks how to handle money the best answer is integers or fixed precision decimals. There may be a valid case for using floats, but if someone asks they shouldn't be using floats.

Also I'm hard pressed to come up with a case where floats would work. Can you give an example?


> Can you give an example?

The answer is the same as _any_ time you should use floats: where you don't care about answers being exact, either (1) because calculation speed is more important than exactness, or (2) because your inputs or computations involve uncertainty anyway, so it doesn't matter.

This is more likely to be the case in, say, physics than it is in finance, but it's not impossible in the latter. For example, if you are a hedge fund and some model computes "the true price of this financial instrument is 214.55", you certainly want to buy if it's being sold for 200, and certainly don't if it's being sold for 250, but if it's being sold for 214.54, the correct interpretation is that _you aren't sure_.

When people say "you should never use floats for currency", their error is in thinking that the only applications for currency are in accounting, billing, and so on. In those applications, one should indeed use a decimal type, because we do care about the rounding behavior exactly matching human customs.


You can't use a generic decimal type in that case either! You need a special-purpose type that rounds exactly matching the conventions you're following. This is necessarily use-, culture-, and probably currency-specific.


Good answer. I've only ever worked on accounting style financial apps, so I've didn't think of those types of cases.


That's fair, though the example code I mentioned is the start of an expense tracker.


Fair enough -- in that case, you should definitely use either a decimal type or an integer.


Most things in front office use floats in my experience, e.g. derivative pricing, discounting, even compound interest. None of these things are going to be any better with integers or fixed-precision, but maybe harder to write and slower.


Yes, the risk management/instrument pricing part in the "Front Office" uses floats, because the calculations involve compound interest and discount rates.

And the downstream parts for trade confirmation ("Middle Office"), settlement and accounting ("Back Office") used fixed precision. Because they are fundamentally accounting, which involves adding things up and cross-checking totals.

These two parts have a very clear boundary, with strictly defined rounding rules when the floating point risk/trading values get turned into fixed point accounting values.


python has the `decimal` module in the stdlib


Create a Money class, or use one off the shelf. It should store the currency and the amount. There are a few popular ways of storing amounts (integer cents, fixed decimal) but it should not be exposed outside the Money class.

There's plenty of good advice in this subthread for how to represent currency inside your Money abstraction, but whatever you do, keep it hidden. If you pass around numbers as currency values you will be in for a world of pain as your application grows.


Either a fixed-point decimal (i.e. an integer with the ones representing 1/100, 1/1000, etc. of a dollar, or a ratio type if you need arbitrary precision.


> ratio type if you need arbitrary precision.

This is the better default, so I'd ditch the qualifier, personally. At the very least when it comes to the persistent storage of monetary amounts. People often start out thinking that they won't need arbitrary precision until that one little requirement trickles into the backlog...

Arbitrary precision rationals handles all the artithmetic you could reasonably want to do with monetary amounts and it lets you decide where to round at display time (or when generating a final invoice or whatever), so there's no information loss.


> Dumb question, but what is the proper way to handle currency?

In python, for exact applications (not many kinds of modeling, where floats are probably right), decimal.Decimal is usually the right answer, but fractions.Fraction is sometimes more appropriate, and if you are using NumPy or tools dependent on it, using integers (representing decimals multiplied by the right power of 10 to get the minimum unit in the ones position) is probably better.


Yeah, you probably want to use some sort of decimal package for a configurable amount of precision, and then use strings when serializing/storing the values


Every front office finance project I have ever worked on has used floating point, so take the dogma with a grain of salt. It depends entirely on the context.


They probably just accumulate the rounding errors into an account and write it off periodically without even realising why it happens.


No, it's just that we're in the realm of predictions and modelling, not accounting. If you're constructing a curve to forecast 50 years of interest rates from a limited set of instruments, you're already accepting a margin of error orders of magnitude greater than the inaccuracies introduced by floating point.

The models also use transcendental functions which cannot be accurately calculated with fixed point, rationals, integers etc.


Makes sense; I wasn't aware of the meaning of "front office" as a term of art in finance.


It's not like decimal or fixed point does not suffer from rounding errors either. In fact for many calculations, binary floating point gives more accurate answers.

In accounting there are specific rules that require decimal system, so one must be very careful with the floating point if it is used.


And the all suffer from rounding error problems?

I mean, fixed point and a specific type for currency (which also should include the denomination, while we are at it) are not rocket science. Spreadsheets get that right, at least.


Excel uses IEEE-754 floating point, so I don't get what you mean with the spreadsheet comment. It has formatting around this which rounds and adds currency symbols, but it's floating point you're working with.

Rounding error doesn't matter on these types of financial applications. It's the less glamorous accounting work that has to bother with that.

They're not rocket science, but they're unnecessary, and would still be off anyway. Try and calculate compound interest with your fixed point numbers.


Each country has a law or something similar that states how people should calculate over prices.

The usual is to use decimal numbers with fixed precision (the actual precision varies from one country to another), and I don't know of any modern exception. But as late as the 90's there were non-decimal monetary systems around the world, so if you are saving any historic data, you may need something more complex.


Someone already mentioned there's a `decimal` package in Python that's better suited for currency. Back when I was a Java developer we used this: https://docs.oracle.com/javase/7/docs/api/java/math/BigDecim...


The Decimal class is one way if you roll your own. py-moneyed seems to be a well maintained library though I haven’t used it.

Disclaimer: I only work with currency in hobby projects.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: