Hacker News new | ask | show | jobs
by kbolino 623 days ago
There are more problems with using floating-point for exact monetary quantities than just the inexact representations of certain quantities which are exact in base 10. For example, integers have all of the following advantages over floats:

Integer arithmetic will never return NaN or infinity.

Integer (a*b)*c will always equal a*(b*c).

Integer (a+b)%n will always equal (a%n+b%n)%n, i.e. low-order bits are always preserved.

IEEE 754 is not bad and shouldn't be feared, but it is not a universal solution to every problem.

It's also not hard to multiply by fractions in fixed-point. You do a widening multiplication by the numerator followed by a narrowing division by the denominator. For percentages and interest rates etc., you can represent them using percentage points, basis points, or even parts-per-million depending on the precision you need.

2 comments

>Integer arithmetic will never return NaN or infinity.

I use C++ and what integer arithmetic will do in situations where floating point returns NaN is undefined behavior.

I prefer the NaN over undefined behavior.

>Integer (ab)c will always equal a(bc).

In every situation where an integer will do that, a floating point will do that as well. Floating point numbers behave like integers for integer values, the only question is what do you do for non-integer values. My argument is that in many if not most cases you can apply the same solution you would have applied using integers to floating points and get an even more robust, flexible, and still high performance solution.

>For percentages and interest rates etc., you can represent them using percentage points, basis points, or even parts-per-million depending on the precision you need.

And this is precisely when people end up reimplementing their own ad-hoc floating point representation. You end up deciding and hardcoding what degree of precision you need to use depending on assumptions you make beforehand and having to switch between different fixed point representations and it just ends up being a matter of time before someone somewhere makes a mistake and mixes two close fixed point representations and ends up causing headaches.

With floating point values, I do hardcode a degree of precision I want to guarantee, which in my case is 6 decimal places, but in certain circumstances I might perform operations or work with data that needs more than 6 decimal places and using floating point values will still accommodate that to a very high degree whereas the fixed arithmetic solution will begin to fail catastrophically.

C++ is no excuse; it has value types and operator overloading. You can write your own types and define your own behavior, or use those already provided by others. Even if you insist on using raw ints (or just want a safety net), there's compiler flags to define that undefined behavior.

Putting everything into floats as integers defeats the purpose of using floats. Obviously you will want some fractions at some point and then you will have to deal with that issue, and the denominator of those fractions being a power of 2 and not a power of 10. Approximation is good enough for some things, but not others. Accounts and ledgers are definitely in the latter category, even if lots of other financial math isn't.

You need always be mindful of your operating precision and scale. Even double-precision floats have finite precision, though this won't be a huge issue until you've compounded the results of many operations. If you use fixed-point and have different denominators all over the place, then it's probably time to break out rational numbers or use the type system to your advantage. You will know the precision and scale of types called BasisPoints or PartsPerMillion or Fixed6 because it's in the name and is automatically handled as part of the operations between types.

>I use C++ and what integer arithmetic will do in situations where floating point returns NaN is undefined behavior. I prefer the NaN over undefined behavior.

Really? IME it's much more difficult to debug where a NaN value came from, since it's irreversible and infectious. And although the standard defines which integer operations should have undefined behavior, usually the compiler just generates code that behaves reasonably. Like, you can take INT_MAX and then increment and decrement it and get INT_MAX back.

(That does mean that you're left with a broken program that works by accident, but hey, the program works.)

Are there cases where float could return a NaN or infinity, where you instead prefer the integer result? That seems a little odd to me.
Most people would love their bank accounts to underflow.
Integer division by zero will raise an exception in most modern languages.

Integer overflow is more problematic. While some languages in some situations will raise exceptions, most don't. While it's easier to detect overflow that has already occurred with floats (though you'll usually have lost low-order bits long before you get infinity), it's easier to avoid overflow in the first place with integers.