11 ms·
The worst thing that strikes fear into me is seeing floating points used for real world currency. Dear god. So many things can go wrong. I always use unsigned i
by razighter777 1y ago
The worst thing that strikes fear into me is seeing floating points used for real world currency. Dear god. So many things can go wrong. I always use unsigned integers counting number of cents. And if I gotta handle multiple currencies, then I'll use or make a wrapper class.
- knert 1y agoHow do you store negative numbers?
- psychoslave 1y agoMaybe as in accounting, one column for benefits, one for debts?
- MobiusHorizons 1y agoYou use a signed integer type, so you just store a negative number. You can think of fixed point as equivalent to ieee754 floats with a fixed exponent and a two’s complement mantissa instead of a sign bit.
- rcleveng 1y agoWrappers are good even when non dealing with multiple currencies since in many places some transactions are in fractions of cents, so depending on the usecase may need to push that decimal a few places out. I always have a wrapper class to put the logic of converting to whole currency units when and if needed, as well as when requirements change and now you need 4 digits past the decimal instead of 2, etc.
- simonw 1y agoI've been having an interesting challenge relating to this recently. I'm trying to calculate costs for LLM usage, but the amounts of money involved are so tiny. Gemini 1.5 Flash 8B is $0.0375 per million tokens! Should I be running my accounting system on units of 10 billionths of a dollar?
- outurnate 1y agoYou're better off representing values as rationals; a ratio between two different numbers. For example, 0.0375 would be represented as 375 over 10000, or 3 over 80
- simonw 1y agoSounds hard to model in SQLite.
- deleted 1y ago[deleted]
- teaearlgraycold 1y agoTwo columns?
- anthk 1y agoFrom Forth, here's how I'd set the rationals: : gcd begin dup while tuck mod repeat drop ; : lcm 2dup \* abs -rot gcd / ; : reduce 2dup gcd tuck / >r / r> ; : q+ rot 2dup \* >r rot \* -rot \* + r> reduce ; : q- swap negate swap q+ ; : q\* rot \* >r \* r> reduce ; : q/ >r \* swap r> \* swap reduce ; Example: to compute 70 * 0.25 = 35/2 70 1 1 4 q* reduce .s 35 2 ok On stack managing words like 2dup, rot and such, these are easily grasped under either Google/DDG or any Forth with the words "see" and/or "help". as a hint, q- swaps the top two numbers in the stack, (which compose a rational), makes the last one negative and then turns back its position. And then it calls q+. So, 2/5 - 3/2 = 2/5 + -3/2.
- marcosdumay 1y agoAccounting happens on the unities people pay, not the ones that generate expenses. But you probably should run your billing in fixed point or floating decimals with a billionth of a dollar precision, yes. Either that or you should consolidate the expenses into larger bunches.
- scott_w 1y agoFixed point Decimal is your friend here. I’m guessing you buy tokens in increments of 1,000,000 so it isn’t too much of an issue to account for. You can then normalise in your accounting so 1,000,000 is just “1 unit,” or you can just account in increments of 1,000,000 but that does start looking weird (but might be necessary!)
- osigurdson 1y agoWouldn't it be better to use a decimal type?
- MobiusHorizons 1y agoThis is what’s called a fixed point decimal type. If you need variable precision, then a decimal type might be a good idea, but fixed point removes a lot of potential foot guns if the constraints work for you.
- osigurdson 1y agoI meant fixed point decimal type (like C#) 128 bit. I don't understand why the parent commenter (top voted comment?) used unsigned integers to track individual cents. Why roll your own decimal type? Using arbitrary precision doesn't make sense if the data needs to be stored in a database (for most situations at least). Regardless, infinite precision is magical thinking anyway: try adding Pi to your bank account without loss of precision.
- MobiusHorizons 1y agothe C# decimal type is not fixed point, its a floating point implementation, but just uses a base 10 exponent instead of a base 2 one like IEE754 floats. Fixed point is a general technique that is commonly done with machine integers when the necessary precision is known at compile time. It is frequently used on embedded devices that don't have a floating point unit to avoid slow software based floating point implementations. Limiting the precision to $0.01 makes sense if you only do addition or subtraction. Precision of $0.001 (Tenths of a cent also called mils) may be necessary when calculating taxes or applying other percentages although this is typically called out in the relevant laws or regulations.
- osigurdson 1y agoGood to know. In a scientific domain so haven't used it previously.
- nurettin 1y agoI inherited systems that trade real world money using f64. They work surprisingly well, and the errors and bugs are almost never due to rounding. Those that are also have easy fixes. So I'm always baffled by this "expert opinion" of using integers for cents. It is pretty much up there with "never use python pickle it is unsafe" and "never use http, even if the program will never leave the subnet".
- dataangel 1y agoyou can't accurately represent 10 cents with floats, 0.1 is not directly representable. same with 1 cent, 0.01. Seems like if you do and significant math on prices you should run into rounding issues pretty quickly?
- adgjlsfhk1 1y agono. Float64 has 16 digits of precision. Therefore even if you're dealing with trillions of dollars, you have accuracy down to the thousandth of a cent.
- cstrahan 1y agoYou might want to re-study this topic. The decimal number 0.1 has an infinitely repeating binary fraction. Consider how 1/3 in decimal is 0.33333… If you truncate that to some finite prefix, you no longer have 1/3. Now let’s suppose we know, in some context, that we’ll only ever have a finite number of digits — let’s say 5 digits after the decimal point. Then, if someone asks “what fraction is equivalent to 0.33333?”, then it is reasonable to reply with “1/3”. That might sound like we’re lying, but remember that we agreed that, in this context of discussion, we have a finite number of digits — so the value 1/3 outside of this context has no way of being represented faithfully inside this context, so we can only assume that the person is asking about the nearest approximation of “1/3 as it means outside this context”. If the person asking feels lied to, that’s on them for not keeping the base assumptions straight. So back to floating point, and the case of 0.1 represented as 64 bit floating point number. In base 2, the decimal number 0.1 looks like 0.0001100110011… (the 0011 being repeated infinitely). But we don’t have an infinite number of digits. The finite truncation of that is the closest we can get to the decimal number 0.1, and by the same rationale as earlier (where I said that equating 1/3 with 0.33333 is reasonable), your programming language will likely parse “0.1” as a f64 and print it back out as such. However, if you try something like (a=0.1; a+a+a) you’ll likely be surprised at what you find.
- scott_w 1y agoFor far too many years I had inherited a billing system that used floats for all calculations then rounded up or down. Also doing some calculations in JS and mirroring them on the Python backend, so “just switch to Decimal” wasn’t an easy change to make…
- layer8 1y agoWait until you learn that Excel calculates everything using floating-point, and doesn't even fully observe IEEE 754. https://learn.microsoft.com/en-us/office/troubleshoot/excel/floating-point-arithmetic-inaccurate-result https://learn.microsoft.com/en-us/office/troubleshoot/excel/... (It nevertheless happens to work just fine for most of what Excel is used for.)
- jksflkjl3jk3 1y agoFloating point math shouldn't be that scary. The rules are well defined in standards, and for many domains are the only realistic option for performance reasons. I've spent most of my career writing trading systems that have executed 100's of billions of dollars worth of trades, and have never had any floating point related bugs. Using some kind of fixed point math would be entirely inappropriate for most HFT or scientific computing applications.
- eddd-ddde 1y agoHow do you handle the lack of commutativity? I've always wondered about the practical implications.
- jcranmer 1y agoFloating-point is completely commutative (ignoring NaN payloads). It's the associativity law that it fails to uphold.
- jakevoytko 1y agoI asked an ex-Bloomberg coder this question once after he told me he used floating points to represent currency all the time, and his response was along the lines of “unless you have blindingly-obvious problems like doing operations on near-zero numbers against very large numbers, these calculations are off by small amounts on their least-significant digits. Why would you waste the time or the electricity dealing with a discrepancy that’s not even worth the money to fix?”
- BeetleB 1y agoNitpick: FP arithmetic is commutative. It's not associative.
- kolbe 1y agoAll your price field messages are sent to the exchange and back via fixed point, so you are using fixed point for at least some of the process (unless you're targeting those few crypto exchanges that use fp prices). If you need to be extremely fast (like fpga fast), you don't waste compute transforming their fixed point representation into floating.
- pie_flavor 1y agoOne of the things I always appreciate about the crypto community is that you do not have to ask what numeric type is being used for money, it is always 8-digit fixed-point. No floating-point rounding errors to be found anywhere.
- immibis 1y agoCorrection: Bitcoin is 8-digit fixed-point. But Lightning is 10, IIRC. Other currencies have different conventions. Still, it's fixed within a given system and always fixed-point. As far as I'm aware, there are no floating-point cryptocurrencies at all, because it would be an obvious exploit vector - keep withdrawing 0.000000001 units from your account that has 1.0 units.
- Athas 1y agoHow does this avoid rounding error? Division and multiplication and still result in nonrepresentable numbers, right?
- pie_flavor 1y agoIt is not hard to remember what int division is about, when your types are ints in code. It also comes up almost never, and isn't what floating-point rounding error means. You aren't multiplying money 99% of the time, and when you are, you don't care about exacting precision (e.g. 20% discount). Floating-point rounding error, on the other hand, is about how 0.1 + 0.2 != 0.3.
- jcranmer 1y agoI've found fear of the use of floating-point in finance to be a good litmus test for how knowledgeable people are about floating-point. Because as far as I can tell, finance people almost exclusively uses (binary) floating-point [1], whereas a lot of floating-point FUD focuses on how disastrous it is for finance. And honestly, it's a bit baffling to me why so many people seem to think that floating-point is disastrous. My best guess for the latter proposition is that people are reacting to the default float printing logic of languages like Java, which display a float as the shortest base-10 number that would correctly round to that value, which extremely exaggerates the effect of being off by a few ULPs. By contrast, C-style printf specifies the number of decimal digits to round to, so all the numbers that are off by a few ULPs are still correct. [1] I'm not entirely sure about the COBOL mainframe applications, given that COBOL itself predates binary floating-point. I know that modern COBOL does have some support for IEEE 754, but that tells me very little about what the applications running around in COBOL do with it.
- pgwhalen 1y agoI agree overall but my take is that it shows more ignorance about the domain of finance (or a particular subdomain) than it does about floating-point ignorance. It’s really more of a concern in accounting, when monetary amounts are concrete and represent real money movement between distinct parties. A ton of financial software systems (HFT, trading in general) deal with money in a more abstract way in most of their code, and the particular kinds of imprecision that FP introduces doesn’t result in bad business outcomes that outweigh its convenience and other benefits.
- munch117 1y agoFP does not introduce imprecision. Quite the contrary: The continuous rounding (or truncation) triggered by using scaled integers is what introduces imprecision. Whereas exponent scaling in floating point ensures that all the bits in the mantissa are put to good use. It's a trade-off between precision and predictability. Floating point provides the former. Scaled integers provide the latter.
- 1y ago
- deleted 1y ago[deleted]