Hacker News new | ask | show | jobs
by antonyme 2539 days ago
Very few developers truly understand floating point representation. Most think of it as base-10, and put in horrific kludges and workarounds when they discover it doesn't work as they (wrongly) expected. I shudder to think how many e-commerce sites use `float` for financial transactions!

So as far as I'm concerned, whatever performance cost these alternate methods may have, it would be well worth it to avoid the pitfalls of IEEE floats. Intel chips have had BCD support in machine code; I'm surprised nobody has made a decent fixed point lib that is widely used already.

9 comments

Replacing all the IEEE 754 hardware with posits won't fix this, though.

If you don't care about performance, then the actual solution has no dependency on hardware:

1. Replace the default format for numbers with a decimal point in suitably high level languages with a infinite precision format.

2. Teach people using other languages about floating point and how they may want to use integers instead.

The end. No multi-generation hardware transition required.

IMO, IEEE 754 is an exceptionally good format. It has real problems, but they aren't widely known to people unfamiliar with floats (e.g. 1.0 + 2.0 != 3.0 isn't one of them).

> 1. Replace the default format for numbers with a decimal point in suitably high level languages with a infinite precision format.

Unlimited precision in any radix point based format does not solve representation error. If you don't understand why:

  - How many decimal places does it take to represent 1/3 (infinite, AKA out of memory)

  - Now how many places does it take to represent 1/3 in base3? (1)
If you are truly only working with rational numbers and only using the four basic arithmetic operations, then only a variable precision fractional representation (i.e a numerator and demonstrator, which is indifferent to underlying base) will be able to store any number without error (if it fits in memory). Of course if you are using transcendental functions or want to use irrational numbers e.g PI then by definition there is no numerical solution to avoid error in any finite system.
I'm pretty sure that 1.0 + 2.0 == 3.0 in IEEE 754. :) Now, 0.1 ...
For reference to what they're talking about, the helpful https://0.30000000000000004.com/
One of the great qualities of IEEE 754 is its ability to represent many integers and operate on them without rounding errors.
Yep, sorry, meant to put 0.1 ...

Thanks!

They are not the same thing, but they are close enough most of the time.

The issue with floating point arrises when comparing very close numbers.

Great points. I agree - the performance hit is simply the cost of being accurate and having predictable behaviour.

I'm not suggesting we replace all our current HW with chips that implement posits (let's fix branch prediction first!!). More that FP should be opt-in for most HLLs.

In the paper "Do Developers Understand IEEE Floating Point" the authors surveys phd student and faculty members from computer science and found out that your observation is true: most people don't know how fp works.

They have the survey online at [1] in case you want to see how much you know about fp behavior.

[1] http://presciencelab.org/float

That was a waste of time: 6 pages of questions, and instead of "grading" it and letting me know where I stand on FP, it says "Thanks for the gift of your time".
Likewise!

FYI the actual paper is https://ieeexplore.ieee.org/document/8425212 and you can get the PDF here: http://pdinda.org/Papers/ipdps18.pdf

Interestingly, they got one of their own answers wrong.

The question is:

    if a and b are numbers, it is always the case that (a + b) == (b + a)
and their notes on it:

    Is a simple statement involving the commutativity over addition true for floating point? Generally, floating point arithmetic follows the same commutativity laws as real number arithmetic.
They make it clear in their notes that "are numbers" includes infinities but not NaNs. Now consider the case where a = inf and b = -inf. Then inf + (-inf) is NaN, and (-inf) + inf is NaN, and NaN != NaN.

    >>> a = float('inf')
    >>> b = float('-inf')
    >>> a + b == b + a
    False
Nice catch.

> They make it clear in their notes that "are numbers" includes infinities but not NaNs.

This is definitely not very clear on the form, thought.

Sorry for tricking you into giving real data for the researchers LOL.

Another comment pointed out to the paper with the answers.

A great resource to learn about the FP corner cases is the great Random ASCII blog:

https://randomascii.wordpress.com/category/floating-point/

Considering that back in the 1980's we figured out that you should use some kind of 'integer' or 'bcd' based type for financial calculations; it is astonishing that by almost 2020 people are making these same mistakes over and over again.

A 64-bit integer is big enough to express the US National Debt in Argentine Pesos.

> it is astonishing that by almost 2020 people are making these same mistakes over and over again.

It's actually logical: the number of developers doubles roughly every 5 years. It means that half of the developers have less than 5 years of experience. If they don't teach you this in school (university), you will have to learn from someone who knows, but chances are the other developers are as clueless as you.

OMG! The Eternal Eternal September.
It's not enough to represent one US cent in original Zimbabwean dollars though as of 2009. Although those 'first' dollars hadn't been legal for quite some time, the currency having gone through three rounds of massive devaluation and collapse, totalling something like 1x10^30 by then.
> Most think of it as base-10 [...] I'm surprised nobody has made a decent fixed point lib that is widely used already.

Note that fixed radix point does not solve the common issues with representing rational base 10 fractions. A base10 fixed radix solution would, but so would IEEE754's decimal64 spec, which would eliminate representation error when working exclusively in the context of base10 e.g finance, but these are not found in common hardware and do not help reduce propagation of error due to compounding with limited precision in any base.

The use of binary in the numerator is not the problem with using floats for financial math, it is the use of powers of 2 in the denominator.

The numerator is just an integer and integers are just integers and the base doesn't matter. But if the exponent is base 2, then you can have 1/2, 1/4, 1/8 on the base but not 1/5 or 1/10.

Rational numbers are a good way to do many financial calculations (extra points for doing something useful with negative denominators!), since many financial calculations are specified with particular denominators (per cent, per mille, basis points; halves, sixths, twelfths, twenty-sixths, fifty-seconds of a basis point, etc.).

However, as soon as you start doing anything interesting, you have limited precision as a matter of course.

If there's one thing I've always really appreciated about Groovy, it's that it used BigDecimal as the default for fractions, because 9 times out of 10, you need accuracy more than you need high performance and large exponents (and if you do need high performance, you wouldn't be using Groovy anyway).

Sadly most languages don't support something like that out of the box.

> Very few developers truly understand floating point representation.

Where would one go to better understand how floating points are represented?

“What every computer scientist should know about floating point” by David Goldberg. Readily available free online.
Thanks!
Sorry, but you actually sounds like one those people who dosen't really know how FP work.

> I shudder to think how many e-commerce sites use `float` for financial transactions!

The float IEEE-754 represent up to 9 decimal digits (or 23 binary digits) with precision. The double, represent 17 decimal digits. The error upper bound is (0.00000000000000001)/2 per operation. Likely irrelevant for most e-commerce.

Also, the database stores in currency values using fixed point.

> Intel chips have had BCD support in machine code

BCD is floating point encoding not fixed point. AFAIK, only Intel supports it and very precariously.

> I'm surprised nobody has made a decent fixed point lib that is widely used already.

Nonsense. If you do any scientific computation you have likely have Boost, GMP, MPFR installed in your system. They support arbitrary precision arithmetic with integer (aka fixed point), rational and floating point.

> Sorry, but you actually sounds like one those people who dosen't really know how FP work.

LOL, sure ok. Worked on banking systems for 2 years and been doing scientific computing for many more. Pretty comfortable with fixed and floating point.

> [error bounds] Likely irrelevant for most e-commerce.

Those bounds are theoretical, and there are plenty of occasions I have come across in the past when rounding errors were observed. It was forbidden in the bank to use floating point! We went to enormous lengths to ensure numerical accuracy and stability across systems.

I think this article has a pretty good explanation:

https://dzone.com/articles/never-use-float-and-double-for-mo...

> Nonsense. If you do any scientific computation you have likely have Boost, GMP, MPFR installed in your system. They support arbitrary precision arithmetic with integer (aka fixed point), rational and floating point.

Yes, absolutely right; I have used several of those 3rd party libs myself, as well as hand-rolling fixed point code (esp for embedded systems). I didn't write what I intended. I meant to say that very few languages have first-class fixed point in their standard library. So long as the simple `float` is available as a POD, people will (mis-)use it.

I think in a general purpose HLL, a fixed decimal type should be the default, and you should have to opt in to IEEE-754 floating point.

First, I would like to apologize for my attitude on the original post, my choice of words was very inappropriate for this forum. Second. I would like to thank you for bringing another point of view.

I'm talking about the average joe e-commerce. The precision requirement of Financial institution and large e-commerce are more strict than most website. For the average e-commerce the extra cost of using a decimal arithmetic framework might not be reasonable. Of course, software engineers should use currency types when they are available.

> https://dzone.com/articles/never-use-float-and-double-for-mo...

The example used by the website is misleading. They print a value with all the significant digits. In reality, e-commerces are only concerned with two digits after the decimal point. I run the same program in C with one billion (-1) iterations and the printed value with two decimal digits was exact. It was the the as it would be if I used decimal arithmetic.

> I think in a general purpose HLL, a fixed decimal type should be the default, and you should have to opt in to IEEE-754 floating point.

Most modern languages have some support to rational, fixed point and currency arithmetic. Nowadays, even GCC's C supports Fixed Point arithmetic [1].

The hardware implementation of floating point arithmetic is concerned about efficiency hardware and support for a lot of uses cases. The ranges and accuracy needed in different application varies widely. The BCD encoding is very inefficient hardware-wise compared to binary encoding.

In summary, I do agree people with working currencies should use the proper currency type, but I also think using IEEE 754 is usually fine, specially in the front-end. Also, I don't think the trade offs of changing from binary arithmetic to decimal arithmetic are not worth it for most people and hardware system.

[1] https://gcc.gnu.org/onlinedocs/gcc-4.9.0/gcc/Fixed-Point.htm...