discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

Floating point error challenge

CM
Curt McDowell
Thu, Aug 18, 2022 11:50 PM

On 8/14/2022 8:47 AM, Jordan Brown wrote:

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

IMO, this is the most correct way to do it. Inaccuracy is intrinsic to
the floating point representation. Nobody in their right mind compares
floating point numbers for general equality. I wouldn't be satisfied
that a specific implementation of an OpenSCAD compiler/runtime
encourages that by including a std::nextafter() hack. There are probably
already forks of OpenSCAD and other implementations from scratch that don't.

Excluding the final value is by far the most common case. I write [0 : 1
/ 360 : 0.99999], or rarely [0 : 359], or even [1 : 360] but that's an
unnatural order. Although OpenSCAD doesn't have an integral type, one
can at least be confident that all implementations would perfectly
represent modest integers (IEEE).

This issue is somewhat related to the coincident face problem of CSG. In
every program I find myself defining a variable

/$eps = 1e-6;/

and using it to write things like

/[0 : 1 / 360 : 1 - $eps]/

and

/translate([0, 0, bottom_cube_height - $eps])//
//    top_cube();/

Curt McDowell

On 8/14/2022 8:47 AM, Jordan Brown wrote: > [*] That is, something like "for (i = [0:n]) { val = i*max/n; ... }" > instead of "for (val = [0:max/n:max]) ...". IMO, this is the most correct way to do it. Inaccuracy is intrinsic to the floating point representation. Nobody in their right mind compares floating point numbers for general equality. I wouldn't be satisfied that a specific implementation of an OpenSCAD compiler/runtime encourages that by including a std::nextafter() hack. There are probably already forks of OpenSCAD and other implementations from scratch that don't. Excluding the final value is by far the most common case. I write [0 : 1 / 360 : 0.99999], or rarely [0 : 359], or even [1 : 360] but that's an unnatural order. Although OpenSCAD doesn't have an integral type, one can at least be confident that all implementations would perfectly represent modest integers (IEEE). This issue is somewhat related to the coincident face problem of CSG. In every program I find myself defining a variable /$eps = 1e-6;/ and using it to write things like /[0 : 1 / 360 : 1 - $eps]/ and /translate([0, 0, bottom_cube_height - $eps])// //    top_cube();/ Curt McDowell
JB
Jordan Brown
Fri, Aug 19, 2022 12:34 AM

On 8/18/2022 4:50 PM, Curt McDowell wrote:

On 8/14/2022 8:47 AM, Jordan Brown wrote:

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

IMO, this is the most correct way to do it.

Well, maybe.  But if nobody can actually demonstrate a problem with
using fractional increments, it's voodoo.  Voodoo is harmful because it
obscures the author's real intent.

That was the essence of the challenge:  to see whether anybody could
demonstrate a real case where it helped.

Inaccuracy is intrinsic to the floating point representation.

Yes, but not nearly to the extent that many people think.  It's
important for equality and truncation, and not a lot more.

Nobody in their right mind compares floating point numbers for general
equality.

Note that the issue here would be truncation, not equality. 
(Internally, it does something akin to floor((max-min)/step) to get the
number of steps.)  But it's a strongly related problem.

Excluding the final value is by far the most common case. I write [0 :
1 / 360 : 0.99999], or rarely [0 : 359], or even [1 : 360] but that's
an unnatural order.

Deliberately excluding the final value (e.g. to walk across fence
segments rather than fenceposts) is a good reason for the "step by
index" scheme, but doesn't seem related to the question of floating
point accuracy.

And note that if what you really have are the min, max, and step, using
the [0:nsteps-1] scheme doesn't solve the problem for you, because to
get nsteps you had to do the floor((max-min)/step) yourself, and your
calculation is just as vulnerable as OpenSCAD's internal one.  The
difference is that you know the tradeoffs and can round or otherwise
fudge as required.  (You can, for instance, know that you expect the
result to be an integer and so round instead of flooring.)

This issue is somewhat related to the coincident face problem of CSG.
In every program I find myself defining a variable

 /$eps = 1e-6;/

and using it to write things like

 /[0 : 1 / 360 : 1 - $eps]/

and

 /translate([0, 0, bottom_cube_height - $eps])//
 //    top_cube();/

Somewhat, though I am not convinced that that's a floating point issue.

For differences and intersections, which is the major place I see those
issues, I just use +1 or, when required, +2.  It's less typing, it's
immediately visible with #, and since I basically never use such
constants in that kind of expression it's idiomatically clear.

On 8/18/2022 4:50 PM, Curt McDowell wrote: > On 8/14/2022 8:47 AM, Jordan Brown wrote: >> [*] That is, something like "for (i = [0:n]) { val = i*max/n; ... }" >> instead of "for (val = [0:max/n:max]) ...". > > IMO, this is the most correct way to do it. > Well, maybe.  But if nobody can actually demonstrate a problem with using fractional increments, it's voodoo.  Voodoo is harmful because it obscures the author's real intent. That was the essence of the challenge:  to see whether anybody could demonstrate a real case where it helped. > Inaccuracy is intrinsic to the floating point representation. > Yes, but not nearly to the extent that many people think.  It's important for equality and truncation, and not a lot more. > Nobody in their right mind compares floating point numbers for general > equality. > Note that the issue here would be truncation, not equality.  (Internally, it does something akin to floor((max-min)/step) to get the number of steps.)  But it's a strongly related problem. > Excluding the final value is by far the most common case. I write [0 : > 1 / 360 : 0.99999], or rarely [0 : 359], or even [1 : 360] but that's > an unnatural order. Deliberately excluding the final value (e.g. to walk across fence segments rather than fenceposts) is a good reason for the "step by index" scheme, but doesn't seem related to the question of floating point accuracy. And note that if what you really have are the min, max, and step, using the [0:nsteps-1] scheme doesn't solve the problem for you, because to get nsteps you had to do the floor((max-min)/step) yourself, and your calculation is just as vulnerable as OpenSCAD's internal one.  The difference is that you know the tradeoffs and can round or otherwise fudge as required.  (You can, for instance, know that you expect the result to be an integer and so round instead of flooring.) > This issue is somewhat related to the coincident face problem of CSG. > In every program I find myself defining a variable > > /$eps = 1e-6;/ > > and using it to write things like > > /[0 : 1 / 360 : 1 - $eps]/ > > and > > /translate([0, 0, bottom_cube_height - $eps])// > //    top_cube();/ > Somewhat, though I am not convinced that that's a floating point issue. For differences and intersections, which is the major place I see those issues, I just use +1 or, when required, +2.  It's less typing, it's immediately visible with #, and since I basically never use such constants in that kind of expression it's idiomatically clear.