discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

Floating point error challenge

JB
Jordan Brown
Sat, Aug 13, 2022 5:11 PM

I was writing something up about floating point error and its effect on
ranges.

As you might know, the theoretical problem is that fractional arithmetic
is imprecise and so in a range like [ 0 : 0.1 : 1 ], the system might
calculate that there are 9.999999999999999 steps, say "oops, sorry,
can't quite fit that last one", and give you 0.0, 0.1, ... 0.9, never
reaching 1.

But I haven't been able to find an example of such a range, one where
OpenSCAD generates something other than the "obvious" answer.  In
particular, I haven't found a range that has the "wrong" number of results.

Can anybody find a range that doesn't have the obvious number of results?

Heck, I haven't been able to find one that doesn't end precisely at
the end point.  Can anybody find one of those?

I was writing something up about floating point error and its effect on ranges. As you might know, the theoretical problem is that fractional arithmetic is imprecise and so in a range like [ 0 : 0.1 : 1 ], the system might calculate that there are 9.999999999999999 steps, say "oops, sorry, can't quite fit that last one", and give you 0.0, 0.1, ... 0.9, never reaching 1. But I haven't been able to find an example of such a range, one where OpenSCAD generates something other than the "obvious" answer.  In particular, I haven't found a range that has the "wrong" number of results. Can anybody find a range that doesn't have the obvious number of results? Heck, I haven't been able to find one that doesn't end *precisely* at the end point.  Can anybody find one of those?
RW
Rogier Wolff
Sat, Aug 13, 2022 5:47 PM

On Sat, Aug 13, 2022 at 10:11:57AM -0700, Jordan Brown wrote:

I was writing something up about floating point error and its effect on
ranges.

As you might know, the theoretical problem is that fractional arithmetic
is imprecise and so in a range like [ 0 : 0.1 : 1 ], the system might
calculate that there are 9.999999999999999 steps, say "oops, sorry,
can't quite fit that last one", and give you 0.0, 0.1, ... 0.9, never
reaching 1.

But I haven't been able to find an example of such a range, one where
OpenSCAD generates something other than the "obvious" answer.  In
particular, I haven't found a range that has the "wrong" number of results.

Can anybody find a range that doesn't have the obvious number of results?

Try loops of 7 iterations of 1/5th and 1/6th.

I made a spreadsheet that has 1, 2, 3... across the top,
1, 2, 3 ... down a column and in the grid I calculate
(1/column_header) * row_header - (row_header/column_header)
(I put the first half in a cell by itself to prevent the spreadsheet
from getting too smart).

the column 5 and 6, row 7 results differ in signedness. (and neither
is zero).

Roger. 

--
** R.E.Wolff@BitWizard.nl ** https://www.BitWizard.nl/ ** +31-15-2049110 **
**    Delftechpark 11 2628 XJ  Delft, The Netherlands.  KVK: 27239233    **
f equals m times a. When your f is steady, and your m is going down
your a is going up.  -- Chris Hadfield about flying up the space shuttle.

On Sat, Aug 13, 2022 at 10:11:57AM -0700, Jordan Brown wrote: > I was writing something up about floating point error and its effect on > ranges. > > As you might know, the theoretical problem is that fractional arithmetic > is imprecise and so in a range like [ 0 : 0.1 : 1 ], the system might > calculate that there are 9.999999999999999 steps, say "oops, sorry, > can't quite fit that last one", and give you 0.0, 0.1, ... 0.9, never > reaching 1. > > But I haven't been able to find an example of such a range, one where > OpenSCAD generates something other than the "obvious" answer.  In > particular, I haven't found a range that has the "wrong" number of results. > > Can anybody find a range that doesn't have the obvious number of results? Try loops of 7 iterations of 1/5th and 1/6th. I made a spreadsheet that has 1, 2, 3... across the top, 1, 2, 3 ... down a column and in the grid I calculate (1/column_header) * row_header - (row_header/column_header) (I put the first half in a cell by itself to prevent the spreadsheet from getting too smart). the column 5 and 6, row 7 results differ in signedness. (and neither is zero). Roger. -- ** R.E.Wolff@BitWizard.nl ** https://www.BitWizard.nl/ ** +31-15-2049110 ** ** Delftechpark 11 2628 XJ Delft, The Netherlands. KVK: 27239233 ** f equals m times a. When your f is steady, and your m is going down your a is going up. -- Chris Hadfield about flying up the space shuttle.
TP
Torsten Paul
Sat, Aug 13, 2022 6:20 PM

On 13.08.22 19:11, Jordan Brown wrote:

Heck, I haven't been able to find one that doesn't end precisely
at the end point.  Can anybody find one of those?

That is very intentional by means of this PR:

https://github.com/openscad/openscad/pull/3253

ciao,
Torsten.

On 13.08.22 19:11, Jordan Brown wrote: > Heck, I haven't been able to find one that doesn't end *precisely* > at the end point.  Can anybody find one of those? That is very intentional by means of this PR: https://github.com/openscad/openscad/pull/3253 ciao, Torsten.
JB
Jordan Brown
Sat, Aug 13, 2022 10:43 PM

On 8/13/2022 11:20 AM, Torsten Paul wrote:

On 13.08.22 19:11, Jordan Brown wrote:

Heck, I haven't been able to find one that doesn't end precisely
at the end point.  Can anybody find one of those?

That is very intentional by means of this PR:

https://github.com/openscad/openscad/pull/3253

Yes, I noticed that it does base+i*step rather than accumulating, and I
agree that that's a big win.

But even then, there's no guarantee that 0.1 * 10 will exactly equal 1.

I just tried what I should have tried first, which is brute force
checking numbers looking for ones where 1/n*n does not equal 1.  The
first one I found was 49.  (After that, 98, 103, 107, 161, 187,196, 197.)

And indeed:

a = [for (i=[ 0: 1/49 : 1 ]) i];
echo(a=a);
echo(len=len(a));
echo(last=a[len(a)-1]);
echo(equal=a[len(a)-1]==1);
echo(diff=a[len(a)-1]-1);

ECHO: a = [0, 0.0204082, [blah blah blah], 0.979592, 1]ECHO: len =
50ECHO: last = 1ECHO: equal = falseECHO: diff = -1.11022e-16
On 8/13/2022 11:20 AM, Torsten Paul wrote: > On 13.08.22 19:11, Jordan Brown wrote: >> Heck, I haven't been able to find one that doesn't end *precisely* >> at the end point.  Can anybody find one of those? > > That is very intentional by means of this PR: > > https://github.com/openscad/openscad/pull/3253 Yes, I noticed that it does base+i*step rather than accumulating, and I agree that that's a big win. But even then, there's no guarantee that 0.1 * 10 will exactly equal 1. I just tried what I should have tried first, which is brute force checking numbers looking for ones where 1/n*n does not equal 1.  The first one I found was 49.  (After that, 98, 103, 107, 161, 187,196, 197.) And indeed: a = [for (i=[ 0: 1/49 : 1 ]) i]; echo(a=a); echo(len=len(a)); echo(last=a[len(a)-1]); echo(equal=a[len(a)-1]==1); echo(diff=a[len(a)-1]-1); ECHO: a = [0, 0.0204082, [blah blah blah], 0.979592, 1]ECHO: len = 50ECHO: last = 1ECHO: equal = falseECHO: diff = -1.11022e-16
JB
Jordan Brown
Sat, Aug 13, 2022 11:19 PM

On 8/13/2022 10:47 AM, Rogier Wolff wrote:

Try loops of 7 iterations of 1/5th and 1/6th.

I don't understand what you mean, in terms of an OpenSCAD range
specification.


I tried brute forcing the first challenge (getting the wrong number of
results), and for [ 0 : 1/n : 1 ], for n from 1 to 1000, I always got
the right number of entries (n+1).  But of course that doesn't mean that
some other random combination wouldn't be a problem.

echo("start");
for (n=[1:1000]) {
    a = [ for (i=[0:1/n:1]) i];
    if (len(a) != n+1) {
        echo(n);
    }
}
echo("end");

yields nothing.

On 8/13/2022 10:47 AM, Rogier Wolff wrote: > Try loops of 7 iterations of 1/5th and 1/6th. I don't understand what you mean, in terms of an OpenSCAD range specification. --- I tried brute forcing the first challenge (getting the wrong number of results), and for [ 0 : 1/n : 1 ], for n from 1 to 1000, I always got the right number of entries (n+1).  But of course that doesn't mean that some other random combination wouldn't be a problem. echo("start"); for (n=[1:1000]) { a = [ for (i=[0:1/n:1]) i]; if (len(a) != n+1) { echo(n); } } echo("end"); yields nothing.
HL
Hans L
Sun, Aug 14, 2022 12:50 AM

On Sat, Aug 13, 2022 at 5:44 PM Jordan Brown openscad@jordan.maileater.net
wrote:

On 8/13/2022 11:20 AM, Torsten Paul wrote:

On 13.08.22 19:11, Jordan Brown wrote:

Heck, I haven't been able to find one that doesn't end precisely
at the end point.  Can anybody find one of those?

That is very intentional by means of this PR:

https://github.com/openscad/openscad/pull/3253

Yes, I noticed that it does base+i*step rather than accumulating, and I
agree that that's a big win.

Hello I'm the author of that change, and there's another trick done in that
PR to help ensure the correct number of values is always reached.

You can see in the lines of code highlighted here:
https://github.com/openscad/openscad/pull/3253/files#diff-2ecb150c1fd88f2ab8aaf603ccee701e18d113bdb931ffc3aa4eb528243a3a38R1028-R1031

It pre-calculates the total number of values in the range, based on:  (end

But even then, there's no guarantee that 0.1 * 10 will exactly equal 1.

I just tried what I should have tried first, which is brute force checking
numbers looking for ones where 1/n*n does not equal 1.  The first one I
found was 49.  (After that, 98, 103, 107, 161, 187,196, 197.)

Hmm, this is true that the final value is not necessarily equivalent to the
end of the range, I hadn't thought much about it at the time.

But also consider that the step might not always be a perfect divisor for
the span of (start - end)  (e.g  [0 : 3/4 : 2 ] )
so it wouldn't be as simple as checking if we're on the final value and
just spitting out the given end_value.

Maybe we could do some more trickery with std::nextafter and/or similar
functions to check if the calculated final value is just nearly equal to
the given end value, and swap it out if true.

On Sat, Aug 13, 2022 at 5:44 PM Jordan Brown <openscad@jordan.maileater.net> wrote: > On 8/13/2022 11:20 AM, Torsten Paul wrote: > > On 13.08.22 19:11, Jordan Brown wrote: > > Heck, I haven't been able to find one that doesn't end *precisely* > at the end point. Can anybody find one of those? > > > That is very intentional by means of this PR: > > https://github.com/openscad/openscad/pull/3253 > > > Yes, I noticed that it does base+i*step rather than accumulating, and I > agree that that's a big win. > > Hello I'm the author of that change, and there's another trick done in that PR to help ensure the correct number of values is always reached. You can see in the lines of code highlighted here: https://github.com/openscad/openscad/pull/3253/files#diff-2ecb150c1fd88f2ab8aaf603ccee701e18d113bdb931ffc3aa4eb528243a3a38R1028-R1031 It pre-calculates the total number of values in the range, based on: (end - start) / step Since the resulting floating point value could be just under some whole number by a least significant bit, we increment that LSB by one, using https://en.cppreference.com/w/cpp/numeric/math/nextafter Finally, *that* value is converted to a uint But even then, there's no guarantee that 0.1 * 10 will exactly equal 1. > > I just tried what I should have tried first, which is brute force checking > numbers looking for ones where 1/n*n does not equal 1. The first one I > found was 49. (After that, 98, 103, 107, 161, 187,196, 197.) > > Hmm, this is true that the final value is not necessarily equivalent to the end of the range, I hadn't thought much about it at the time. But also consider that the step might not always be a perfect divisor for the span of (start - end) (e.g [0 : 3/4 : 2 ] ) so it wouldn't be as simple as checking if we're on the final value and just spitting out the given end_value. Maybe we could do some more trickery with std::nextafter and/or similar functions to check if the calculated final value is *just nearly* equal to the given end value, and swap it out if true.
RW
Rogier Wolff
Sun, Aug 14, 2022 8:08 AM

On Sat, Aug 13, 2022 at 04:19:20PM -0700, Jordan Brown wrote:

On 8/13/2022 10:47 AM, Rogier Wolff wrote:

Try loops of 7 iterations of 1/5th and 1/6th.

I don't understand what you mean, in terms of an OpenSCAD range
specification.

As I said: I did it in a spreadsheet. From the spreadsheet I was able
to find two candidates of which one surely would go wrong if openscad
would do it the obvious way. But we now know that in 2020 someone made
openscad to it the non-obvious way eliminating this problem.

Roger. 

--
** R.E.Wolff@BitWizard.nl ** https://www.BitWizard.nl/ ** +31-15-2049110 **
**    Delftechpark 11 2628 XJ  Delft, The Netherlands.  KVK: 27239233    **
f equals m times a. When your f is steady, and your m is going down
your a is going up.  -- Chris Hadfield about flying up the space shuttle.

On Sat, Aug 13, 2022 at 04:19:20PM -0700, Jordan Brown wrote: > On 8/13/2022 10:47 AM, Rogier Wolff wrote: > > Try loops of 7 iterations of 1/5th and 1/6th. > > I don't understand what you mean, in terms of an OpenSCAD range > specification. As I said: I did it in a spreadsheet. From the spreadsheet I was able to find two candidates of which one surely would go wrong if openscad would do it the obvious way. But we now know that in 2020 someone made openscad to it the non-obvious way eliminating this problem. Roger. -- ** R.E.Wolff@BitWizard.nl ** https://www.BitWizard.nl/ ** +31-15-2049110 ** ** Delftechpark 11 2628 XJ Delft, The Netherlands. KVK: 27239233 ** f equals m times a. When your f is steady, and your m is going down your a is going up. -- Chris Hadfield about flying up the space shuttle.
JB
Jordan Brown
Sun, Aug 14, 2022 3:47 PM

On 8/13/2022 5:50 PM, Hans L wrote:

Hello I'm the author of that change, and there's another trick done in
that PR to help ensure the correct number of values is always reached.

Yes, I saw that.  I don't know enough low-level floating point math to
have an opinion on whether it's sufficient.  The fact that I wasn't able
to demonstrate a problem is certainly encouraging.

Hmm, this is true that the final value is not necessarily equivalent
to the end of the range, I hadn't thought much about it at the time.

Most of the time, it shouldn't matter.

But also consider that the step might not always be a perfect divisor
for the span of (start - end)   (e.g  [0 : 3/4 : 2 ] )
so it wouldn't be as simple as checking if we're on the final value
and just spitting out the given end_value.

Yep.

Maybe we could do some more trickery with std::nextafter and/or
similar functions to check if the calculated final value is just
nearly
equal to the given end value, and swap it out if true.

Whether it matters will depend on what the caller does with the values. 
It might not be just the end point that matters - there is similarly no
guarantee that intermediate values that mathematically should hit
particular values will hit exactly those values.  (E.g., in [0 : 1/49 :
2], the middle value should be exactly 1, and isn't.)

If it was considered to be an issue, probably the rightest answer would
be to have all equality checks be very slightly fuzzy.

Until somebody can demonstrate a practical problem, I wouldn't worry
about it.


Let me be clear:  I'm not complaining.  Tiny errors are inherent in
finite-precision arithmetic.  I was pleasantly surprised that I wasn't
able to find more of them.  I was about to advise somebody to use
integer steps in the "for" and scale to the desired value in later
processing[*], because rumors said that was better, but when I wasn't
able to demonstrate any problems I had to rethink that advice.

[*] That is, something like "for (i = [0:n]) { val = i*max/n; ... }"
instead of "for (val = [0:max/n:max]) ...".

If nobody can demonstrate a case where it doesn't get the obvious number
of results, I don't see any reason for that kind of advice.  (It still
sometimes leads to more obvious expressions, but that's a different
rationale.)  Hence the challenge, to see if anybody could find such a
problematic case.

On 8/13/2022 5:50 PM, Hans L wrote: > Hello I'm the author of that change, and there's another trick done in > that PR to help ensure the correct number of values is always reached. Yes, I saw that.  I don't know enough low-level floating point math to have an opinion on whether it's sufficient.  The fact that I wasn't able to demonstrate a problem is certainly encouraging. > Hmm, this is true that the final value is not necessarily equivalent > to the end of the range, I hadn't thought much about it at the time. Most of the time, it shouldn't matter. > But also consider that the step might not always be a perfect divisor > for the span of (start - end)   (e.g  [0 : 3/4 : 2 ] ) > so it wouldn't be as simple as checking if we're on the final value > and just spitting out the given end_value. Yep. > Maybe we could do some more trickery with std::nextafter and/or > similar functions to check if the calculated final value is *just > nearly* equal to the given end value, and swap it out if true. Whether it matters will depend on what the caller does with the values.  It might not be just the end point that matters - there is similarly no guarantee that intermediate values that mathematically should hit particular values will hit exactly those values.  (E.g., in [0 : 1/49 : 2], the middle value should be exactly 1, and isn't.) If it was considered to be an issue, probably the rightest answer would be to have all equality checks be very slightly fuzzy. Until somebody can demonstrate a practical problem, I wouldn't worry about it. --- Let me be clear:  I'm not complaining.  Tiny errors are inherent in finite-precision arithmetic.  I was pleasantly surprised that I wasn't able to find more of them.  I was about to advise somebody to use integer steps in the "for" and scale to the desired value in later processing[*], because rumors said that was better, but when I wasn't able to demonstrate any problems I had to rethink that advice. [*] That is, something like "for (i = [0:n]) { val = i*max/n; ... }" instead of "for (val = [0:max/n:max]) ...". If nobody can demonstrate a case where it doesn't get the obvious number of results, I don't see any reason for that kind of advice.  (It still sometimes leads to more obvious expressions, but that's a different rationale.)  Hence the challenge, to see if anybody could find such a problematic case.
FH
Father Horton
Sun, Aug 14, 2022 3:57 PM

Let me be clear:  I'm not complaining.  Tiny errors are inherent in
finite-precision arithmetic.  I was pleasantly surprised that I wasn't able
to find more of them.  I was about to advise somebody to use integer steps
in the "for" and scale to the desired value in later processing[*], because
rumors said that was better, but when I wasn't able to demonstrate any
problems I had to rethink that advice.

I still do that, but I'm old school. Heck, I once programmed in FORTRAN IV.

> > Let me be clear: I'm not complaining. Tiny errors are inherent in > finite-precision arithmetic. I was pleasantly surprised that I wasn't able > to find more of them. I was about to advise somebody to use integer steps > in the "for" and scale to the desired value in later processing[*], because > rumors said that was better, but when I wasn't able to demonstrate any > problems I had to rethink that advice. > I still do that, but I'm old school. Heck, I once programmed in FORTRAN IV.
WF
William F. Adams
Mon, Aug 15, 2022 12:00 AM

Would it be feasible to implement something like the fraction module for Python in OpenSCAD?

https://docs.python.org/3/library/fractions.html

(I think that's the right link --- it was mentioned in a discussion of a text interface where splitting the screen was an option and the developer found that using this module allowed splits to occur as expected)

William

Would it be feasible to implement something like the fraction module for Python in OpenSCAD? https://docs.python.org/3/library/fractions.html (I think that's the right link --- it was mentioned in a discussion of a text interface where splitting the screen was an option and the developer found that using this module allowed splits to occur as expected) William