brazzy / floating-point-gui.de

Website that provides concise answers to common questions about floating-point numbers.
http://floating-point-gui.de/
1.01k stars 165 forks source link

using integers is not bad #39

Closed nkarls closed 6 years ago

nkarls commented 6 years ago

A point to be made in favour of integers is performance. Using libraries providing for arbitrarily accurate decimal objects is horrendously slow, especially in a setting where you have to shovel a live stream of many individual events into a billing system. It may not matter that such objects use far more memory than just the 64bits of a bigint, but it matters a whole lot whether putting a price tag onto an individual event takes 120 or 9800 cpu cycles, both in hardware needed and power cost.

Using integers for monetary amounts is fine for most applications. Don't round, instead format output only when you need to present payable amounts and store the cut for later. You may need something else if you are a very large bank or insurance company, but for any small to medium scale business you'll be perfectly okay. There are commercial rating/billing solutions for mobile telco companies out there storing microcents for dozens of brands with millions of customers, and they've never produced a single call event or invoice that was not priced accurately.

brazzy commented 6 years ago

Using integers for monetary amounts is "fine" only in the sense that you can make it work. But using decimal types is objectively a better solution in regard to development speed, quality and maintainability. Performance concerns may dictate that you cannot use that solution, but unless you have concrete evidence that the monetary calculations are in fact a bottleneck, you are engaging in nothing but premature optimization.

nkarls commented 6 years ago

• Let's use an example to make clear that nothing needs to be re-implemented at all: on billing day, you have a service debt of 1723847200µ¢ and we want you to pay a bill. As you can only ever transfer whole cents, we'll put 17.24$ on your invoice document, so you'll end up with a service credit of 152800µ¢. Your account is not put to zero each month, it rolls on and on. If you leave the service, the rounding error of your final invoice is written off as impossible to collect (or as tip). As for compound interest and the like: you tell your customers when and where you'll be rounding, and you calculate the interest amount only when you have to (when closing a fiscal year or an account) and not every day in between. No decimals there, just the bigint.

• I don't get the point about Zimbabwe. We have customers there, they usually pay in USD, ZAR or EUR and it works just fine. It may be you're referring to some hyper-inflation scenario (which Zimbabwe currently does not suffer from), but that's nothing to prepare software for as it would invalidate any sane calculation model anyway. In case you're about big differences in the worth of different currencies, like Bitcoin vs. the Indonesian Rupiah: this really doesn't matter at all. You have a product that you want to sell at your cost plus profit. Converting your asking price to a foreign currency doesn't need to be exact, you can just equal a Dollar with 9000 Rupiahs and be done with it. So you either do all calculation in your home currency and only convert at the time of invoicing, or you keep multiple prices in different currencies, each of which will relate to your local currency in a way that will be fine. If you need high-speed global currency interchange like in the mobile telcos, you use SDR (special drawing right) as currency to avoid any trouble. In any case: the bigint is fine, there really isn't a problem.

• The other two points are good enough for real life. For the inflexible decimal point: A Big Mac will not suddenly cost a billionth Dollar or a Billion Dollars, and if it does, we're going to have other problems than accounting software to deal with. Plus, long before that would ever happen, the government would kill the Dollar and introduce a new currency so people can use money in a sane way again. And for the "need to migrate the already stored prices": it doesn't exist. If you have a price point of 100000000 for something that costs a Dollar which consists of a hundred Cents, then the price point will stay exactly the same whether the Dollar has 10 or 10000 Cents, because the decimal point isn't going to move and all the sub-units have been in the right place all along. The fourth point especially is a non-issue—you don't have to redesign all the watches just because the minute now has 600 instead of 60 seconds, the minute is still a minute long.

I AM with you on the issue of "just storing cents". That IS bad and there's no excuse for it. You have to use the smallest unit that can ever possibly occur in the craziest scenario you can think of, and then add three more digits because deflation and marketing people who are even crazier than you could think. The MicroCent has proven to be a great reference unit, we've been using it for many years now and sold our platform in a number of countries, hosting many millions of happy phone customers none of which has ever gotten a faulty invoice.

Look, I'm not trying to bash dedicated decimal types, they're great to have and if I'm ever going to build a performance-non-critical application I'll gladly use them. I'm just trying to say that since we left the 32-bit era, integers aren't as bad anymore as they are still painted. They still are no silver bullet and won't ever be, but they certainly aren't bad anymore. If you know the scale of your problem using fixed point arithmetic is fine, and a bigint for your everyday sales business is more than you'll ever need, plus it's storage efficient and blazing fast to do math with.

I've been posting this because I feel there are no "severe drawbacks". There are drawbacks, but especially in rating/billing/accounting none of the four listed on the page are valid nowadays. Using the "proper" type does nothing in terms of "development speed, quality and maintainability", it's the exact same code (two exceptions, but once the library is written there's no need to ever touch it again), just with a different type of variable. (An anecdote for the performance issue: that time we did a feasibility study on replacing our "oh so bad integers" with "the good and proper decimal types" after being advised so by some ridiculously expensive consulting guy. We quickly ran a sed script over the code and database schema, set up a new test environment and gave it a spin. As expected, some more RAM and disk space were used, but that can be bought on the cheap. But hell, the performance. Where our actual engine would munch through a daily load sample in just short of two hours, the "better" version took more than 26 hours. More than a day for a daily load of data. Rolling that out for all the clients and their customers we would have had to expand the data center and spend between one and four million Dollars a year extra on hardware, storage and cooling. By today, I'm sure math libraries will have become a lot faster, and prices will have dropped considerably, but why even bother? It's fine.)