Earlier quoted context omitted.
Assuming you want your money to actually add up correctly, then floats are always the wrong choice. If you’re not interested in accurate accounting, the. Sure floats are fine, but when you’re working with money, accurate accounting tends to be the expectation.
If you're the one settling the books at your bank, sure. If you just need to display the price of something, a float+money formatter is mostly fine.
Floats Are Weird
41–50 of 120 posts
Re: Floats Are Weird
#42Earlier quoted context omitted.
This gets repeated a lot, and I don't disagree. But I find odd that doubles would be so unsuitable for monetary (and other similar) arithmetic; in principle you have 15 significant digits which should be more than enough, and precise control how the results are rounded. And all the basic arithmetic should return correctly rounded values to the last ULP. So it is weird that those tools are still not good enough and it…
The problems of floats are not the number of significant digits, it’s the imprecision of the representation (floats don’t just cut off at the end), that these imprecisions compound, and that float operations are not commutative. At the end of the day, 0.1 + 0.2 != 0.3 is a fact you have to live with. X87 does not really factor into it, if anything in your view of the world x87 floats would be better since x86-EP is 8…
But 0.1 + 0.2 == 0.3 — if you round both sides to 2 (or however many you need) decimal points before comparing them.
Re: Floats Are Weird
#43For this specific case, use Python's expm1, see https://docs.python.org/3/library/math.html#math.expm1 and history of expm1 at https://en.wikipedia.org/wiki/Exponential_function#expm1 def f(x): return math.expm1(x)/x The expm1(x) means "exp(x) minus 1". >>> f(1e-15) 1.0000000000000007 The technique described gives 1.0000000000000004 which is 1 step smaller than the value computed via expm1(): >>> math.nextafter(f(1e-…
To be precise, it’s C’s `expm1`. Like other things in math.h, it’s just adopted as is to most popular languages, even including PHP and JS.
It became part of BSD 4.3 due to Kahan and one of his students. The documentation at https://archive.org/details/unix-programmers-reference-manua... mentions "expm1" was available in HP BASIC, and mentions the Apple C compiler at the time used "exp1", which you can verify from the Apple Numerics Manual Second Edition 1988 at https://archive.org/details/apple-numerics-manual-second-edi...
That makes it BASIC's expm1 before it was C's. ;)
While yes, Python's expm1 uses the platform libm's expm1 through C, Python's math module will avoid using the vendor libm for cases where the vendor implementation is known to have numerical issues.
For one such example, on macOS the following:
#include
#include
int main(void) {
double x = -1.0;
x = nextafter(x, 0.0); // -0.9999999999999999
printf("lgamma(%.19f) -> %.17f\n", x, lgamma(x));
return 0;
}
reports lgamma(-0.9999999999999998890) -> 36.25168935623486988
while Python, using its own lgamma() implementation: import math
x = math.nextafter(-1.0, 0.0)
print(f"lgamma({x}) -> {math.lgamma(x)}")
reports: lgamma(-0.9999999999999999) -> 36.7368005696771
showing that Python's lgamma is not a pass-through to C's.FWIW, WolframAlpha says the value is 36.73680056... so Python's is more accurate.
EDIT: Python's math.expm1 was added in version 3.2 back in February 20th, 2011, and did not depend on C's expm1. Instead, it used Kahan's method directly:
/* Mathematically, expm1(x) = exp(x) - 1. The expm1 function is designed
to avoid the significant loss of precision that arises from direct
evaluation of the expression exp(x) - 1, for x near 0. */
double
_Py_expm1(double x)
{
/* For abs(x) >= log(2), it's safe to evaluate exp(x) - 1 directly; this
also works fine for infinities and nans.
For smaller x, we can use a method due to Kahan that achieves close to
full accuracy.
*/
if (fabs(x) Re: Floats Are Weird
#44Earlier quoted context omitted.
Addition is one of those things that does work pretty predictably and error free with floats. The problem with 0.30000000000000004 etc is usually that the things you are adding are not what you expect (float(0.1) != 0.1), i.e. the difficulty usually is string float conversions rather than float arithmetic itself.
string->float conversion is conversion from accounting realm to computer abstraction. If the conversion is not accurate and the results can not be converted back into accounting realm without artificial artifacts the use of such computation is problematic.
Re: Floats Are Weird
#45It's clear that even experienced programmers do NOT understand how they work, how they don't work, and the pile of edge cases.
Use something saner, like binary coded decimal, larger types, and use floats as a last and very approximate resort.
Re: Floats Are Weird
#46I decided years ago that the next time I hear someone suggesting we use floats / doubles to represent money amounts, I am going to punch them in the face.
We don’t store the data in floats or anything resembling it, however.
Re: Floats Are Weird
#47My current approach is that I try to guess (or ask) how a customer understands or checks a statement/invoice. The customer usually takes a calculator and enters the rounded numbers they see. I then try to do all programmatic calculations exactly the same way: Operation - round - operation - round etc, with rounding-steps at exactly the same places where these values will be visible to the customer, and with the same number of decimal places (can vary). Sometimes that is not possible of course, which is usually solved with a final row called 'rounding errors in the customer's favor').
For my (smaller) systems I find floats sufficient and easier to work with compared to the alternatives, which tend to make code very verbose and annoying to read.
Re: Floats Are Weird
#48That's a neat trick, "cancelling" out catastrophic cancellation. Of course it doesn't work anymore once x is smaller than machine epsilon, whereas expm1(x) / x will continue to work. In general herbie is pretty good at suggesting the right functions / rearrangements. For this example, it finds expm1: https://herbie.uwplse.org/demo/378a0682ec4d3e735c790a58fa97e...
But the expression you get if you disable "numerics" ruleset (which includes expm1) is pretty wild also:
(if (<= (exp x) 4504410275303423/4503599627370496) (+ 1 (+ (* 1/24 (pow x 3)) (* x (+ 1/2 (* x 1/6))))) (/ (+ (exp x) -1) (log (exp x)))))
Re: Floats Are Weird
#49Earlier quoted context omitted.
It’s not (or shouldn’t be), it’s simply a result of math, as the article explains in length.
Many libm implementations don't have an accurate `log` or `exp` routine, so there does exist a risk. (Of course, it's also true that many of them also special-case `log(x) ~= x - 1` and `exp(x) ~= x + 1` for small enough `x`.)
Re: Floats Are Weird
#50Earlier quoted context omitted.
The problems of floats are not the number of significant digits, it’s the imprecision of the representation (floats don’t just cut off at the end), that these imprecisions compound, and that float operations are not commutative. At the end of the day, 0.1 + 0.2 != 0.3 is a fact you have to live with. X87 does not really factor into it, if anything in your view of the world x87 floats would be better since x86-EP is 8…
> At the end of the day, 0.1 + 0.2 != 0.3 is a fact you have to live with That is the one example that floats around a lot, but its also imho not very good one. '0.1', '0.2', and '0.3' are not floating point values, so the premise is flawed. 0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 != 0.299999999999999988897769753748434595763683319091796875 i…
No, it’s the entire point. None of the values we deal with day to day are binary floating point, and certainly not currencies. So this sort of representational approximations is a major and constant issue of using floats.
> Also `round(0.1 + 0.2, 15) == 0.3` is true (in python), so being conscious about rounding things appropriately goes long way.
See above, rounding off and collecting error after every arithmetic operation is not the expected norm and what developers are taught.
> And I imagine that correct rounding is relevant in monetary calculations no matter what sort of numbers you are using
While that is true, it does not normally need to be done after every arithmetic operation, especially not after additions of already rounded off values.