Closed ewildgoose closed 8 years ago
The whole point of binary floats is that, once you write it as a human input, you will likely have loss of precision.
it leads to surprises if you have fed in some decimal numbers and expect to perform arithmetic as most are taught at school
Yes, it does. But that's true for all float operations.
iex> 0.1 + 0.1 + 0.1
0.30000000000000004
I understand that for some computations the rounding errors won't affect your final value, but nonetheless I wouldn't advise others to rely on it.
So, here is a thought. What if we make an assumption that it's really no more correct to pick any one of those decimal values to be our representative decimal than any other. All values in that range get mapped to the same double value, so why make any one of them more special than any other?
Please tell me a standard, paper or any material that says this is an OK assumption to make. Because IEEE754 tells exactly how to round every after operation and which of those floats should be picked. We cannot simply assume something different because we feel like it.
If we assume that the input is generated by HUMAN input, THEN we can argue that some inputs are more likely than others
If you want to keep HUMAN input (meaning decimal), do not use binary floating points.
So arguably this still meets the rules of IEEE754, the only difference is our interpretation of the guidance "to infinite precision".
Arguably is not enough. Please give me provably or a place in the spec that defines using binary floating points as decimals for operations with low precision. Something that specifies which constraints we need to hold so your big assumptions won't break. Otherwise we are introducing tools for developers to shoot their own foot, specially if they believe they can use binary floating points as equivalent to decimals.
Finally, if it's agreed that there are genuine competing requirements for functions, and that therefore there is ambiguity in the naming of these functions, then I propose:
Sorry, it is not agreed. Binary floating point computations should act on floating points and not assume they were HUMAN input. That's a big assumption to make (as you said). If you want to use floats for HUMAN input, because you are aware of the risks, then please go ahead, but it is not something we should encourage in Elixir. If you prefer, create a library or a separate package that acts as you wish, but I don't see it being part of Elixir.
Thank you.
Look, you have completely twisted my words... I said very clearly that you have approx 17 sig figures of accuracy with Double. So my whole point is that a big chunk of interesting stuff can be done inside that space with NO loss of precision.
Computers always (usually) work in a finite precision, so you don't magically fix this by working in decimal, all you do is shift the problem computations to different numbers. eg working in decimal then 1.0/6.0 + 1.0/6.0 incurs a similar loss of precision. EVERYTHING becomes a loss of precision, if you want to be very pedantic, you simply compute an error bound as your computation continues.
There is a whole branch of mathematics dedicated to this. Quick googling suggests these might be a good explanation:
So I reject your suggestion that I am wrong to work in either base2 OR base10. BUT, I do agree that for repeated calculations, using finite precision arithmetic, some care is needed to ensure you do not accumulate significant error REGARDLESS of what base you work in.
I also don't understand your wider rejection? My government wants me to work out VAT (sales tax) on stuff I sell and the answer to that is clearly defined. A perfectly acceptable way to do this is using finite precision arithmetic, caveat that I respect the limits of accuracy that this gives me (should be fine for many situations). However, I have no option to tell the government that their computation is wrong under IEEE "Rules" and that I can't comply with their rules!!
You asked for justification that "this is an OK assumption". Well, I don't see how you can get a better justification than "I added up some decimal numbers that were accurate to 2 decimal places", I guarantee you that the answer will make sense to be a decimal rounded to 2 decimal places! (obviously caveat quite silly numbers of computations that will blow up the computation, eg assuming adding up numbers under a billion, ie 10^6, then approx you need 10^8 accuracy for 2dp, so we should be able to do around 10^6 additions without losing precision - I have done this off the cuff, hope it's approx correct...)
However, another justification would be all the papers you already linked to, eg: "Printing Floating-Point Numbers Quickly and Accurately" - aka Dragon4
Other "examples by implementation" would be:
I honestly don't know how to clarify this? I actually feel this is just so obvious that I'm lacking the words to clarify? (note I am NOT rejecting that there ARE other interpretations AS WELL!) Try this analogy:
In summary:
Neither is any more right. However, I can show both options to my 8 year old son and he will definitely pick one of these options as being "more correct" for doing his maths homework...
I don't have any problem with you rejecting this feature request. But I guess please understand why I (and others) might get hung up your rejecting that the actual requirement exists (given that it's arguably the most natural form of arithmetic us humans recognise)...
Again, thanks for your consideration this far!
Look, you have completely twisted my words...
Oops, I didn't mean to do that. To be clear, what I mean is that, you should not write 5.675 and expect it to be accurately represented as 5.675 when using binary float numbers. You can, however, accurately represent that using a decimal. Since you are talking about human input, a decimal floating point will most likely be the best format to represent human input, so my recommendation is to pick that.
I also don't understand your wider rejection?
Elixir doesn't need to solve all problems as part of its core. To be quite honest, if I had total flexibility on this, I would have defaulted Elixir to return a fraction on 1 / 2
rather than a float, similar to Clojure. However, given pattern matching and guards work with floats and Erlang follows IEEE754, that's what we will do too. It doesn't mean you need to throw Elixir out of the window, you still can:
However, another justification would be all the papers you already linked to, eg: "Printing Floating-Point Numbers Quickly and Accurately" - aka Dragon4
Sorry but this is not true. The papers do not talk about doing rounding after a free-form conversion, which is what you are proposing. Sure, the implementation in Java and possibly others are doing the rounding after the free-conversion but I did not see it being implied or advocated by the papers.
I don't have any problem with you rejecting this feature request. But I guess please understand why I (and others) might get hung up your rejecting that the actual requirement exists (given that it's arguably the most natural form of arithmetic us humans recognise)...
I am not rejecting the requirements. As I said in the previous reply, If you want to use floats for decimal computations because you are aware of the trade-offs, then go ahead, but it is not an idea we should promote in Elixir. Quite the opposite, I would promote using decimals. So don't expect it to be part of the language.
There is a new implementation of Float.round, which is implemented according to IEEE754 rules. Approximately this means that the definition of the function is sensible if you are doing lots of IEEE754 mathematics and expect the round function to assume you are operating on a full double value. However, it leads to surprises if you have fed in some decimal numbers and expect to perform arithmetic as most are taught at school...
eg, the truncate function is now implemented as:
floor(1.5, 1) == 1.4
but sometimes it's useful to have a function which works something like:floor(1.5,1) == 1.5
Just to confirm understanding of the motivation. It's completely clear that one can use Decimal for all computations and achieve the expected results. However, it's not necessary to do this as double will compute to around 17 sig figures of accuracy, and for a whole class of problems this is completely adequate. The main challenge (assuming you do not accidentally run out of sig figures) is simply to ensure that one correctly converts from the world of approximate double values to an appropriate decimal value at the far end of the calculations
Assuming the motivation is clear (if it's not, please lets clarify that?), lets take a quick look at the reason the naive solution goes wrong.
You can write any decimal as a number in the form X * 10^Y, and likewise you could write a base 2 number as X * 2^Y. eg:
3498523 is written as 3.498523×10^6
and0.05 is written as 1.6×2^−5
However, in the same way that "1 / 3" cannot be expressed as an exact number in decimal, we have the same problem in base2. So converting many decimals to base2 will leave us with a number quantised to something REALLY CLOSE to the original number, but very slightly different, eg
1.45 gets stored as a value, which when converted back to decimal would be:
1.44999999999999995559107901499373838305473327636719
However, watch closely. Just looking at the above number, you can clearly see that it MUST be just junk which follows the run of 99999 digits! So of course from this we can see that there are a whole range of decimals represented between our two doubles:
1.44999999999999995559107901499373838305473327636719
and1.45000000000000017763568394002504646778106689453125
So, here is a thought. What if we make an assumption that it's really no more correct to pick any one of those decimal values to be our representative decimal than any other. All values in that range get mapped to the same double value, so why make any one of them more special than any other?
Also, the motivation for doing this is you can very clearly see that we have a huge sensitivity to rounding, depending which of these values we pick, eg the first value should round towards 1.4 in IEEE754 world, but in fact, IF the input were the top value, then we would clearly have expected to round closer to 1.5 (I'm assuming round to nearest, pick different inputs, but same end result if you want a round to even)
So, here is the big assumption:
So, IFF this assumption matches your expectation, then it may make sense to implement some functions (with provocative names!) in a different manner.
The definition of any operation under these assumptions would simply be:
So arguably this still meets the rules of IEEE754, the only difference is our interpretation of the guidance "to infinite precision". ie in the same way that it implies that one should consider the nature of transcendental functions and implement special care, here we would implement it to mean that we can compute a more "accurate" input value (albeit this is based on a heuristic, so care must be used when choosing to use this function)
For avoidance of doubt, the output of any such function should match the output of the Decimal library. However, there may exist alternative implementations which implement the same output, but more efficiently (faster). I offer the following as an idea for a baseline (I also have a slower implementation in a separate library on github)
Finally, if it's agreed that there are genuine competing requirements for functions, and that therefore there is ambiguity in the naming of these functions, then I propose:
If this proposal is accepted then we gain two functions:
Float.round_ieee(5.5675, 3) == 5.567
andFloat.round_decimal(5.5675,3) == 5.568
Thanks for listening