if (walletBalance - sumOfWithdrawals < 0) { throw new Error('overdrawn); }
Try that code in Javascript where walletBalance = 0.3 and the sumOfWithdrawals = 0.2 + 0.1.Point being there are tons of operations in the financial world where you check things against 0, or want to ensure that a breakdown of smaller transactions equals a larger amount. Those all can fail with floating points but succeed with decimals.
IEEE 64 bit floats are accurate to just past 15 decimal digits. For ordinary monetary amounts, the exact figure in cents is approximated with ridiculous precision. If you rub two pennies together, you are likely causing more of a difference in the amount of copper than the IEEE 64 bit float causes in the value.
You have to do many, many additions and multiplications before you get a result which has accumulated so much error that it is now closer to the wrong penny. E.g. if you don't deal with dollar amounts more than 7 figures, you have about 8 places past the decimal point; you need something like a 6 place error before the penny is affected.
You can counteract this problem by correcting intermediate results to that floating-point value which is closest to exact penny result. In other words, throughout your calculation, you truncate away the difference between the result, and the best approximation of the dollar and cent value.
Within this framework, you can implement all required rounding rules, too. You can take a floating-point result representing a fraction of a penny and round it according to banker's rule to the penny.
Of course, you can't just ignore the issue and just blindly use floating-point for money in a serious accounting system; but that's a strawman version of using floating-point for money.
Also, Microsoft Excel uses floating point. See here:
https://learn.microsoft.com/en-us/office/troubleshoot/excel/...
Vast armies of people rely on Excel for financial calculations.
> You can counteract this problem by correcting intermediate results to that floating-point value which is closest to exact penny result
The argument seems to be "floats are okay, as long as you're careful", but forgetting to round the number in between a large number of operations is a probable mistake.
Using decimals would make such a mistake impossible.
Decimals are software libs; what could go wrong?
More people use the floating-point instructions of a popular CPU than any given decimal library.
If you're starting from scratch, it's probably a lot less work to write (test and debug) a Money class based on floating-point, whose overloaded math operations do the required rounding (so that code using the class cannot forget) than to make a Money class based on decimal arithmetic.
(The last time I wrote an accounting system, I made a Money class based on integers. It could be retargeted to other representations easily. I could make the change and compare the ledger totals and other reports to see if there is a difference.)
Perhaps, but how many of the former are in a position to notice accuracy issues? I have more faith in a reputable decimal library than your average FPU, frankly.
Bugs in libraries do exist, but it's much easier to fix a bug in one place, than to track down every single line where floating point operations could misbehave.
> IEEE 64 bit floats
That should be IEEE 64-bit floating-point. The name `float` is a 32-bit floating-point.
> are accurate to just past 15 decimal digits.
FOR SOME RANGE. They're called floating-point because they can modify how much scaling is devoted to either side of the decimal point. The more scale it has to devote to representing whole portion, the lower its precision in the fractional portion. At some point, a 64-bit floating-point value cannot represent any decimal digits. Past around 2^53, a `double` 64-bit floating-point value cannot even represent every whole number, because at that point the available precision is 2 or more.
If I say "64 bit float", it's obviously not that one.
Here is CLISP:
[1]> (type-of 3.0)
SINGLE-FLOAT
[2]> (type-of 3.0d0)
DOUBLE-FLOAT
Python3: >>> type(3.0)
<class 'float'>
>>> type(3.0e300)
<class 'float'>
Looks like it's calling the 64 bit ones just float.Rust has f32 and f64. Some historic languages have used type names like REAL and DOUBLE and others.
"IEEE 64 bit float" is almost unambiguous; it could be the binary one or the decimal one.
IEEE uses identifiers like binary64, decimal32, if you want to be pedantic, and not float and double.
> FOR SOME RANGE ...
Your comment is seems to be based on the wrong idea of what the number of digits means. An IEEE 64 bit binary float stores 15 significant digits without loss. In that C language that gives us the float type, the preprocessor constant DBL_DIG has a value of 15 (on IEEE floating point platforms).
This is a 15 digit number using E notation (in terms of digits of precision / significant figures):
1.23456789012345E-20
So is this; it is not a 16 digit number: 123456789012345.0
And so is this isn't a 17 digit one: 12345678901234500.0 (same as 1.23456789012345E16)
You have to write the number in exponentatial notation, and chop the trailing zeros. Then count the remaining number number of digits in the mantissa part.I think it's only for subnormal numbers that the 15 rule breaks down; but I might have mentioned that. These are special representations close to zero beyond what is reachable with the regular exponent and mantissa, which have the benefit of certain desirable behaviors in underflow situations.