1. Problem with floor() function
- Posted by RobertS Dec 31, 2008
- 949 views
floor() seems not always to give the expected results, due to some internal rounding problems?
object x, y x = 10 * 2.35 + 0.5 y = floor(x) puts(1, sprint(x) & " -- " & sprint(y))
In this example, x = 24, but floor(x) is 23.
With 2.55 instead of 2.35, both results correctly are 26.
This is annoying, as it makes rounding unreliable and, BTW, sprintf shows similar (though not identical) inconsistencies:
puts(1, sprintf("%1.1f", 2.35)) puts(1, sprintf("%1.1f", 2.55))
The first line prints 2.3, the second one 2.6
(Euphoria 3.1, Windows XP)
Is there a way to work around this problem, or will it be solved with version 4.0?
Robert Schächter
2. Re: Problem with floor() function
- Posted by CChris Dec 31, 2008
- 980 views
floor() seems not always to give the expected results, due to some internal rounding problems?
object x, y x = 10 * 2.35 + 0.5 y = floor(x) puts(1, sprint(x) & " -- " & sprint(y))
In this example, x = 24, but floor(x) is 23.
With 2.55 instead of 2.35, both results correctly are 26.
This is annoying, as it makes rounding unreliable and, BTW, sprintf shows similar (though not identical) inconsistencies:
puts(1, sprintf("%1.1f", 2.35)) puts(1, sprintf("%1.1f", 2.55))
The first line prints 2.3, the second one 2.6
(Euphoria 3.1, Windows XP)
Is there a way to work around this problem, or will it be solved with version 4.0?
Robert Schächter
The way is to avoid rounding, because it is done by hardware in a way that can be figured, but not easily at all. Instead, use integers, and divide by some factor when appropriate.
object x, y, z z = 100 x = 10 * 235 + 50 -- z*original x y = floor(x/z) -- x/z is original x puts(1, sprint(x/z) & " -- " & sprint(y))
will get you 24 both sides, because all intermediate calculations are made on intgers, thus guaranteed exact as long as magnitudes don't go above 9.0e15, more precisely power(2,53). This is a hardware quirk and is a standard.
CChris
3. Re: Problem with floor() function
- Posted by mattlewis (admin) Dec 31, 2008
- 933 views
floor() seems not always to give the expected results, due to some internal rounding problems?
[snip]
Is there a way to work around this problem, or will it be solved with version 4.0?
This is an artifact of the way computers implement floating point numbers. The internal representation is in binary, meaning that some decimal representations of numbers can only be approximated (2.35 among them), and some approximations are better than others.
Take a look at this code to demonstrate what's going on:
object x, y x = 10 * 2.35 + 0.5 y = floor(x) printf(1, "%0.16f %g %0.16f\n", {2.35,x,x,y})
The workaround for things like this is usually to use some epsilon to determine if the number is 'close enough' to whatever value you're interested in. It's hard to say what might be a good workaround without knowing the context. And due to the 'floating' in floating point, the appropriate epsilon might change given different magnitudes.
Matt
4. Re: Problem with floor() function
- Posted by jimcbrown (admin) Dec 31, 2008
- 964 views
The way is to avoid rounding, because it is done by hardware in a way that can be figured, but not easily at all. Instead, use integers, and divide by some factor when appropriate.
object x, y, z z = 100 x = 10 * 235 + 50 -- z*original x y = floor(x/z) -- x/z is original x puts(1, sprint(x/z) & " -- " & sprint(y))
will get you 24 both sides, because all intermediate calculations are made on intgers, thus guaranteed exact as long as magnitudes don't go above 9.0e15, more precisely power(2,53). This is a hardware quirk and is a standard.
CChris
I tried this in C, and my results were 24. This suggests we may have a bug in the runtime itself, since our results are not identical to the C platform we build on top of.
Furthermore, I tried eu 2.3 and eu 4.0 alpha1 - 2.3 prints out 24 but 4.0 prints out 2.3
This might be acceptable (due to the way math is handled inside the interpreter or something, there may be some expected loss of precision) but we should at least figure out why this changed.
#include <stdio.h> #include <math.h> int main(int argc, char ** argv) { double x, y; x = 10 * 2.35 + 0.5; y = floor(x); printf("%g\n", y); return 0; }
5. Re: Problem with floor() function
- Posted by mattlewis (admin) Dec 31, 2008
- 927 views
I tried this in C, and my results were 24. This suggests we may have a bug in the runtime itself, since our results are not identical to the C platform we build on top of.
Furthermore, I tried eu 2.3 and eu 4.0 alpha1 - 2.3 prints out 24 but 4.0 prints out 2.3
This might be acceptable (due to the way math is handled inside the interpreter or something, there may be some expected loss of precision) but we should at least figure out why this changed.
It looks like C overestimates (I used Watcom), while euphoria underestimates. If you printf 2.35 with a %016f format, you'll get:
c: 2.3500000000000001 eu: 2.3499999999999996The problem appears to be the way in which we build floating point representations. Basically, given a number like '2.35', euphoria does the following:
x = 0 x += 2 x += 3/10 x += 5/100
It's the x += 3/10 that gives us a problem. Interestingly, using scientific notation does not improve the situation, so I suppose I should take another look at the rounding going on there. Take a look at the following to see something interesting:
printf(1, "%0.16f\n", 3/10 + 5/100) -- 0.3500000000000000 printf(1, "%0.16f\n", 2.0 + 0.3 ) -- 2.2999999999999998 printf(1, "%0.16f\n", 2.0 + 0.35 ) -- 2.3500000000000001 printf(1, "%0.16f\n", 0.35 + 2.0) -- 2.3500000000000001 printf(1, "%0.16f\n", 35/100 + 2.0) -- 2.3500000000000001
And just in case you're wondering, C gives the same results. So it looks like we should change the way we scan numbers. The regular case is relatively straightforwardrather than building the fraction part of the mantissa as a fraction, build it with integers and divide at the end. Though we may need to fall back to scientific notation if there are too many digits.
And the scientific parsing will take some additional study to see why it gives the results that it does.
Matt
6. Re: Problem with floor() function
- Posted by mattlewis (admin) Dec 31, 2008
- 951 views
Interestingly, using scientific notation does not improve the situation, so I suppose I should take another look at the rounding going on there.
I think I was wrong. Using scientific notation appears to be fine (in 4.0).
So it looks like we should change the way we scan numbers. The regular case is relatively straightforwardrather than building the fraction part of the mantissa as a fraction, build it with integers and divide at the end. Though we may need to fall back to scientific notation if there are too many digits.
r1295 gets this right now by only doing the division at the end.
Matt
7. Re: Problem with floor() function
- Posted by RobertS Jan 01, 2009
- 945 views
Thank you for your comments and explanations!
Now, to me it seems that sprint does the rounding that turns the binary number back to the intended decimal value?
object x, x1 x = 10 * 2.35 + 0.5 x1 = value(sprint(x)) x = x1[2]
Not very elegant, but this way, x is actually exactly 24.
I need this for a simple rounding function that rounds a floating point number x to n decimal places, consistently rounding up the digit 5. Without the part that deals with negative numbers it now looks like this:
function round(atom x, integer n) object x1 x1 = value(sprint(x * power(10, n) + 0.5)) x = x1[2] x = floor(x) / power(10, n) return x end function
Hope this really works reliably...
Robert Schächter