Discussion:
[racket] Inconsistency of `in-range` and numerical issues
Laurent
2015-02-24 15:41:45 UTC
Permalink
I've discovered a rather troubling behaviour when using `in-range` with
floating point numbers, which I think is worth knowing in case you hadn't
consider the issue before:

On my machine, I get the following:

(length (for/list ([i (in-range .1 .7 .1)]) i)) ; 6
(length (for/list ([i (in-range .1 .8 .1)]) i)) ; 8 (!)

But:
(length (for/list ([i (in-range 1/10 7/10 1/10)]) i)) ; 6
(length (for/list ([i (in-range 1/10 8/10 1/10)]) i)) ; 7


Would it be a good idea to safe-guard these kinds of cases directly in
`in-range`?
Laurent
2015-02-24 16:04:12 UTC
Permalink
One such safe guard could be:
(length (for/list ([i (in-range (inexact->exact .1) (inexact->exact .8)
(inexact->exact .1))]) i)) ; 7

But of course that slows things down.

Btw, the reason it outputs 8 in the bad case is because on my machine:
(for/sum ([i 8]) .1) ; 0.7999999999999999

which is then below the .8 threshold.
Post by Laurent
I've discovered a rather troubling behaviour when using `in-range` with
floating point numbers, which I think is worth knowing in case you hadn't
(length (for/list ([i (in-range .1 .7 .1)]) i)) ; 6
(length (for/list ([i (in-range .1 .8 .1)]) i)) ; 8 (!)
(length (for/list ([i (in-range 1/10 7/10 1/10)]) i)) ; 6
(length (for/list ([i (in-range 1/10 8/10 1/10)]) i)) ; 7
Would it be a good idea to safe-guard these kinds of cases directly in
`in-range`?
Konrad Hinsen
2015-02-24 18:13:51 UTC
Permalink
Post by Laurent
(for/sum ([i 8]) .1) ; 0.7999999999999999
which is then below the .8 threshold.
It's not just your machine. 0.1 = 1/10 does not have an exact
representation as a binary floating-point number. It gets rounded to the
nearest binary float value, which is smaller than 0.1. This is the
correct behavior according to IEEE 754.

Konrad.
____________________
Racket Users list:
http://lists.racket-lang.org/users
Laurent
2015-02-24 18:29:38 UTC
Permalink
Thanks for you detailed explanation Konrad.

I'm surprised I didn't encounter this problem before–or most probably the
last time was just so long ago that I don't remember it. A wakeup call for
floating-point issues I suppose. Fortunately Racket offers rational numbers!
Post by Konrad Hinsen
Post by Laurent
(for/sum ([i 8]) .1) ; 0.7999999999999999
which is then below the .8 threshold.
It's not just your machine. 0.1 = 1/10 does not have an exact
representation as a binary floating-point number. It gets rounded to the
nearest binary float value, which is smaller than 0.1. This is the correct
behavior according to IEEE 754.
Konrad.
Steve Graham
2015-02-24 22:01:45 UTC
Permalink
While I don't doubt the facts presented below, it just seems wrong, notwithstanding what the standard states.
MUMPS (http://en.wikipedia.org/wiki/MUMPS), my workday language for 30-some years, would never think of acting in such a manner:
s sum=0 f i=1:1:8 s sum=sum+.1                                               
                                                                               
w sum                                                                        
.8                                                                             

I gather, though, that such behavior in other languages is typical, correct?
Steve


From: Konrad Hinsen <***@fastmail.net>
To: Laurent <***@gmail.com>; users <***@racket-lang.org>
Sent: Tuesday, February 24, 2015 10:13 AM
Subject: Re: [racket] Inconsistency of `in-range` and numerical issues
Post by Laurent
(for/sum ([i 8]) .1) ; 0.7999999999999999
which is then below the .8 threshold.
It's not just your machine. 0.1 = 1/10 does not have an exact
representation as a binary floating-point number. It gets rounded to the
nearest binary float value, which is smaller than 0.1. This is the
correct behavior according to IEEE 754.

Konrad.


____________________
  Racket Users list:
  http://lists.racket-lang.org/users
Konrad Hinsen
2015-02-25 08:04:14 UTC
Permalink
Post by Steve Graham
While I don't doubt the facts presented below, it just seems wrong,
notwithstanding what the standard states.
You are not alone in considering that there is something wrong with how
IEEE 754 floating-point operations work. Floats are indeed
counter-intuitive in many respects, rounding isn't even the worst of them.
On the other hand, there are good reasons why floats behave the way
they do, and I haven't seen any proposal that would be clearly better.

My personal point of view is that floats are useful in certain
situations but that they are overused. Many applications would be better
served with rationals or fixed-point numbers (which are just scaled
integers). The problem is that most programming languages support
neither of these.
Post by Steve Graham
MUMPS (http://en.wikipedia.org/wiki/MUMPS), my workday language for
I don't know MUMPS, but the example you show leaves one of the following
possibilities:

1) ".1" in MUMPS is not interpreted as a IEEE 754 binary float, but in
some other way, e.g. as a rational or a decimal float number.

2) MUMPS rounds the output after conversion to decimal in order to
hide the problem.

Solution 1) is OK, solution 2) isn't. It just makes it harder to
recognize and understand the problem. But many languages choose this
solution, unfortunately.


Racket (and Scheme in general) is hard to beat when it comes to numbers
because of the wide range of number types that are provided. One way to
fix Laurent's example is to request an "exact" interpretation of the
decimal point:

(length (for/list ([i (in-range #e.1 #e.7 #e.1)]) i)) ; 6
(length (for/list ([i (in-range #e.1 #e.8 #e.1)]) i)) ; 7

This code uses rationals rather than floats. You can even tell Racket to
consider the decimal point a notation for rations by default, without
the #e prefix, using the parameter read-decimal-as-inexact.
Unfortunately this is tricky because the parameter must be set at read
time, so you can't just set it in a module where you use numbers.

If anything could be (or could have been) improved in Racket, it's two
points:

1) read-decimal-as-inexact could be #f by default, preferring
exactness over efficiency by default.

2) in-range could be defined with an integer step-number argument
rather than a float step-size.
Post by Steve Graham
I gather, though, that such behavior in other languages is typical, correct?
Most languages offer IEEE 754 binary floats but not rationals nor
decimal floats. Some languages try to hide round-off errors when
printing results, which I think is a bad idea because it actually
introduces a second source of error.

Konrad.

____________________
Racket Users list:
http://lists.racket-lang.org/users
Hendrik Boom
2015-02-25 13:30:02 UTC
Permalink
Post by Konrad Hinsen
If anything could be (or could have been) improved in Racket, it's
1) read-decimal-as-inexact could be #f by default, preferring
exactness over efficiency by default.
2) in-range could be defined with an integer step-number argument
rather than a float step-size.
I concur.
Post by Konrad Hinsen
Post by Steve Graham
I gather, though, that such behavior in other languages is typical, correct?
Most languages offer IEEE 754 binary floats but not rationals nor
decimal floats. Some languages try to hide round-off errors when
printing results, which I think is a bad idea because it actually
introduces a second source of error.
It's very tempting in language design to solve a common problem in a
way that leads to deep. obscure, and deadly traps elsewhhere.

There's nothing that can replace intimate understanding of one's basic
tools.

--hendrik
____________________
Racket Users list:
http://lists.racket-lang.org/users
Alexander D. Knauth
2015-02-25 22:14:42 UTC
Permalink
Post by Konrad Hinsen
(length (for/list ([i (in-range #e.1 #e.7 #e.1)]) i)) ; 6
(length (for/list ([i (in-range #e.1 #e.8 #e.1)]) i)) ; 7
This code uses rationals rather than floats. You can even tell Racket to consider the decimal point a notation for rations by default, without the #e prefix, using the parameter read-decimal-as-inexact. Unfortunately this is tricky because the parameter must be set at read time, so you can't just set it in a module where you use numbers.
1) read-decimal-as-inexact could be #f by default, preferring exactness over efficiency by default.
I just made a meta-language that does this:
https://github.com/AlexKnauth/exact-decimal-lang
The teaching languages also do this, but this can be used with any language.



____________________
Racket Users list:
http://lists.racket-lang.org/users
Konrad Hinsen
2015-02-26 08:48:01 UTC
Permalink
Post by Alexander D. Knauth
Post by Konrad Hinsen
1) read-decimal-as-inexact could be #f by default, preferring exactness over efficiency by default.
https://github.com/AlexKnauth/exact-decimal-lang
The teaching languages also do this, but this can be used with any language.
That looks nice, thanks! I have found an immediate use for this in a
Racket file that defines a big database of numerical parameters. I
could remove all the #e and make the data much more readable.

Konrad.
____________________
Racket Users list:
http://lists.racket-lang.org/users
Andrew Kent
2015-02-24 18:05:34 UTC
Permalink
Racket:
(+ .1 .1 .1 .1 .1 .1 .1 .1)
0.7999999999999999
Post by Laurent
.1 + .1 + .1 + .1 + .1 + .1 + .1 + .1
0.7999999999999999

Looks consistent to me =)
Post by Laurent
I've discovered a rather troubling behaviour when using `in-range` with
floating point numbers, which I think is worth knowing in case you hadn't
(length (for/list ([i (in-range .1 .7 .1)]) i)) ; 6
(length (for/list ([i (in-range .1 .8 .1)]) i)) ; 8 (!)
(length (for/list ([i (in-range 1/10 7/10 1/10)]) i)) ; 6
(length (for/list ([i (in-range 1/10 8/10 1/10)]) i)) ; 7
Would it be a good idea to safe-guard these kinds of cases directly in
`in-range`?
____________________
http://lists.racket-lang.org/users
Matthias Felleisen
2015-02-24 18:15:02 UTC
Permalink
That's the saddest argument in support of anything Racket I have ever seen (even with the smiley).
Post by Andrew Kent
(+ .1 .1 .1 .1 .1 .1 .1 .1)
0.7999999999999999
.1 + .1 + .1 + .1 + .1 + .1 + .1 + .1
0.7999999999999999
Looks consistent to me =)
(length (for/list ([i (in-range .1 .7 .1)]) i)) ; 6
(length (for/list ([i (in-range .1 .8 .1)]) i)) ; 8 (!)
(length (for/list ([i (in-range 1/10 7/10 1/10)]) i)) ; 6
(length (for/list ([i (in-range 1/10 8/10 1/10)]) i)) ; 7
Would it be a good idea to safe-guard these kinds of cases directly in `in-range`?
____________________
http://lists.racket-lang.org/users
____________________
http://lists.racket-lang.org/users
____________________
Racket Users list:
http://lists.racket-lang.org/users
Andrew Kent
2015-02-24 18:25:34 UTC
Permalink
I just grabbed two separate witnesses showing the result of the relevant
floating point math - I apologize for any offense.
Post by Matthias Felleisen
That's the saddest argument in support of anything Racket I have ever seen
(even with the smiley).
Post by Andrew Kent
(+ .1 .1 .1 .1 .1 .1 .1 .1)
0.7999999999999999
.1 + .1 + .1 + .1 + .1 + .1 + .1 + .1
0.7999999999999999
Looks consistent to me =)
I've discovered a rather troubling behaviour when using `in-range` with
floating point numbers, which I think is worth knowing in case you hadn't
Post by Andrew Kent
(length (for/list ([i (in-range .1 .7 .1)]) i)) ; 6
(length (for/list ([i (in-range .1 .8 .1)]) i)) ; 8 (!)
(length (for/list ([i (in-range 1/10 7/10 1/10)]) i)) ; 6
(length (for/list ([i (in-range 1/10 8/10 1/10)]) i)) ; 7
Would it be a good idea to safe-guard these kinds of cases directly in
`in-range`?
Post by Andrew Kent
____________________
http://lists.racket-lang.org/users
____________________
http://lists.racket-lang.org/users
Matthias Felleisen
2015-02-24 18:31:36 UTC
Permalink
I am not offended. I am saddened.
I just grabbed two separate witnesses showing the result of the relevant floating point math - I apologize for any offense.
That's the saddest argument in support of anything Racket I have ever seen (even with the smiley).
Post by Andrew Kent
(+ .1 .1 .1 .1 .1 .1 .1 .1)
0.7999999999999999
.1 + .1 + .1 + .1 + .1 + .1 + .1 + .1
0.7999999999999999
Looks consistent to me =)
(length (for/list ([i (in-range .1 .7 .1)]) i)) ; 6
(length (for/list ([i (in-range .1 .8 .1)]) i)) ; 8 (!)
(length (for/list ([i (in-range 1/10 7/10 1/10)]) i)) ; 6
(length (for/list ([i (in-range 1/10 8/10 1/10)]) i)) ; 7
Would it be a good idea to safe-guard these kinds of cases directly in `in-range`?
____________________
http://lists.racket-lang.org/users
____________________
http://lists.racket-lang.org/users
____________________
Racket Users list:
http://lists.racket-lang.org/users
Konrad Hinsen
2015-02-24 18:11:30 UTC
Permalink
Post by Laurent
I've discovered a rather troubling behaviour when using `in-range` with
floating point numbers, which I think is worth knowing in case you
(length (for/list ([i (in-range .1 .7 .1)]) i)) ; 6
(length (for/list ([i (in-range .1 .8 .1)]) i)) ; 8 (!)
(length (for/list ([i (in-range 1/10 7/10 1/10)]) i)) ; 6
(length (for/list ([i (in-range 1/10 8/10 1/10)]) i)) ; 7
Would it be a good idea to safe-guard these kinds of cases directly in
`in-range`?
The problem is an old one that already troubled programmers in the age
of Fortran. I suspect there is no reasonable safe-guard, with
"reasonable" meaning that it does what people expect in all situations.

The only way to stay out of trouble is to avoid loops defined by an
accumulating floating-point value. This means either don't use floats
(write the loop over integers or rationals and convert to floats when
using the loop index), or don't use accumulation (define your range by
two points and the number of subdivisions, rather than the width of
subintervals).

Konrad.

____________________
Racket Users list:
http://lists.racket-lang.org/users
Neil Toronto
2015-02-26 14:13:33 UTC
Permalink
Post by Konrad Hinsen
Post by Laurent
I've discovered a rather troubling behaviour when using `in-range` with
floating point numbers, which I think is worth knowing in case you
(length (for/list ([i (in-range .1 .7 .1)]) i)) ; 6
(length (for/list ([i (in-range .1 .8 .1)]) i)) ; 8 (!)
(length (for/list ([i (in-range 1/10 7/10 1/10)]) i)) ; 6
(length (for/list ([i (in-range 1/10 8/10 1/10)]) i)) ; 7
Would it be a good idea to safe-guard these kinds of cases directly in
`in-range`?
The problem is an old one that already troubled programmers in the age
of Fortran. I suspect there is no reasonable safe-guard, with
"reasonable" meaning that it does what people expect in all situations.
The only way to stay out of trouble is to avoid loops defined by an
accumulating floating-point value. This means either don't use floats
(write the loop over integers or rationals and convert to floats when
using the loop index), or don't use accumulation (define your range by
two points and the number of subdivisions, rather than the width of
subintervals).
I should have chimed in to support this two days ago, but this is
exactly the right answer. Here's what Konrad means by his first
alternative (write the loop over integers or rationals):

(length
(for*/list ([i (in-range 1 8 1)]
[i (in-value (* i 0.1))])
i))

The second alternative is a little harder to get right because of
fencepost errors [1]. Fortunately, Racket has a library function for it.
Post by Konrad Hinsen
(require (only-in plot/utils linear-seq))
(linear-seq 0.0 1.0 4)
'(0.0 0.3333333333333333 0.6666666666666666 1.0)
Post by Konrad Hinsen
(linear-seq 0.0 1.0 4 #:start? #f)
'(0.14285714285714285 0.42857142857142855 0.7142857142857142 1.0)
Post by Konrad Hinsen
(linear-seq 0.0 1.0 4 #:start? #f #:end? #f)
'(0.125 0.375 0.625 0.875)

I should really move this function into `math/base`.

If you must use a flonum step size, do something like this:

(define (flonum-range start end step)
(define n (exact-ceiling (/ (- end start) step)))
(for/list ([i (in-range 0 n)])
(+ start (* i step))))

To get points with 0.5 ulp error each, which is the best you can do, add
(require math/flonum) to your program and change the loop body to (flfma
step (fl i) start).

Arguably, `in-range` should do something like the above when given
floating-point step lengths. I don't know how feasible that is, though.

Neil ⊥

[1] http://en.wikipedia.org/wiki/Off-by-one_error#Fencepost_error

____________________
Eric Dong
2015-02-26 16:38:54 UTC
Permalink
I feel like swapping out flonums by default to rationals by default would
cause unexpected slowness in a large number of programs though. Would it be
possible to make it a reader extension like at-exp is currently? So I can
say "#lang exact-decimals racket", and the reader would read the decimals
as rationals?
Post by Laurent
I've discovered a rather troubling behaviour when using `in-range` with
Post by Laurent
floating point numbers, which I think is worth knowing in case you
(length (for/list ([i (in-range .1 .7 .1)]) i)) ; 6
(length (for/list ([i (in-range .1 .8 .1)]) i)) ; 8 (!)
(length (for/list ([i (in-range 1/10 7/10 1/10)]) i)) ; 6
(length (for/list ([i (in-range 1/10 8/10 1/10)]) i)) ; 7
Would it be a good idea to safe-guard these kinds of cases directly in
`in-range`?
The problem is an old one that already troubled programmers in the age
of Fortran. I suspect there is no reasonable safe-guard, with
"reasonable" meaning that it does what people expect in all situations.
The only way to stay out of trouble is to avoid loops defined by an
accumulating floating-point value. This means either don't use floats
(write the loop over integers or rationals and convert to floats when
using the loop index), or don't use accumulation (define your range by
two points and the number of subdivisions, rather than the width of
subintervals).
I should have chimed in to support this two days ago, but this is exactly
the right answer. Here's what Konrad means by his first alternative (write
(length
(for*/list ([i (in-range 1 8 1)]
[i (in-value (* i 0.1))])
i))
The second alternative is a little harder to get right because of
fencepost errors [1]. Fortunately, Racket has a library function for it.
Post by Laurent
(require (only-in plot/utils linear-seq))
(linear-seq 0.0 1.0 4)
'(0.0 0.3333333333333333 0.6666666666666666 1.0)
Post by Laurent
(linear-seq 0.0 1.0 4 #:start? #f)
'(0.14285714285714285 0.42857142857142855 0.7142857142857142 1.0)
Post by Laurent
(linear-seq 0.0 1.0 4 #:start? #f #:end? #f)
'(0.125 0.375 0.625 0.875)
I should really move this function into `math/base`.
(define (flonum-range start end step)
(define n (exact-ceiling (/ (- end start) step)))
(for/list ([i (in-range 0 n)])
(+ start (* i step))))
To get points with 0.5 ulp error each, which is the best you can do, add
(require math/flonum) to your program and change the loop body to (flfma
step (fl i) start).
Arguably, `in-range` should do something like the above when given
floating-point step lengths. I don't know how feasible that is, though.
Neil ⊥
[1] http://en.wikipedia.org/wiki/Off-by-one_error#Fencepost_error
____________________
http://lists.racket-lang.org/users
Alexis King
2015-02-26 19:54:35 UTC
Permalink
I think that’s precisely what Alexander’s exact-decimal-lang does. It’s a meta-language just like at-exp.
I feel like swapping out flonums by default to rationals by default would cause unexpected slowness in a large number of programs though. Would it be possible to make it a reader extension like at-exp is currently? So I can say "#lang exact-decimals racket", and the reader would read the decimals as rationals?
I've discovered a rather troubling behaviour when using `in-range` with
floating point numbers, which I think is worth knowing in case you
(length (for/list ([i (in-range .1 .7 .1)]) i)) ; 6
(length (for/list ([i (in-range .1 .8 .1)]) i)) ; 8 (!)
(length (for/list ([i (in-range 1/10 7/10 1/10)]) i)) ; 6
(length (for/list ([i (in-range 1/10 8/10 1/10)]) i)) ; 7
Would it be a good idea to safe-guard these kinds of cases directly in
`in-range`?
The problem is an old one that already troubled programmers in the age
of Fortran. I suspect there is no reasonable safe-guard, with
"reasonable" meaning that it does what people expect in all situations.
The only way to stay out of trouble is to avoid loops defined by an
accumulating floating-point value. This means either don't use floats
(write the loop over integers or rationals and convert to floats when
using the loop index), or don't use accumulation (define your range by
two points and the number of subdivisions, rather than the width of
subintervals).
(length
(for*/list ([i (in-range 1 8 1)]
[i (in-value (* i 0.1))])
i))
Post by Konrad Hinsen
(require (only-in plot/utils linear-seq))
(linear-seq 0.0 1.0 4)
'(0.0 0.3333333333333333 0.6666666666666666 1.0)
Post by Konrad Hinsen
(linear-seq 0.0 1.0 4 #:start? #f)
'(0.14285714285714285 0.42857142857142855 0.7142857142857142 1.0)
Post by Konrad Hinsen
(linear-seq 0.0 1.0 4 #:start? #f #:end? #f)
'(0.125 0.375 0.625 0.875)
I should really move this function into `math/base`.
(define (flonum-range start end step)
(define n (exact-ceiling (/ (- end start) step)))
(for/list ([i (in-range 0 n)])
(+ start (* i step))))
To get points with 0.5 ulp error each, which is the best you can do, add (require math/flonum) to your program and change the loop body to (flfma step (fl i) start).
Arguably, `in-range` should do something like the above when given floating-point step lengths. I don't know how feasible that is, though.
Neil ⊥
[1] http://en.wikipedia.org/wiki/Off-by-one_error#Fencepost_error <http://en.wikipedia.org/wiki/Off-by-one_error#Fencepost_error>
____________________
http://lists.racket-lang.org/users <http://lists.racket-lang.org/users>
____________________
http://lists.racket-lang.org/users
Jos Koot
2015-02-26 19:39:16 UTC
Permalink
IIRC, Fortran (70 or 90, I don't remember) already recognized the problem.
In Racket notation and for simplicity ignoring the fact that a Fortran-loop
includes the finish and that the step may be negative:

(for ((f (in-range float-start float-finish float-step))) body)

was translated as sometining like:

(let ((begin float-begin) (finish float-finish) (step float-step))
(for ((integer (in-range 0 (inexact->exact (floor-or-ceiling (/ (- finish
start) step))))))
(let ((f (+ begin (* integer step)))) body)))

Jos

-----Original Message-----
From: users [mailto:users-***@racket-lang.org] On Behalf Of Neil Toronto
Sent: jueves, 26 de febrero de 2015 15:14
To: ***@racket-lang.org
Subject: Re: [racket] Inconsistency of `in-range` and numerical issues
Post by Konrad Hinsen
Post by Laurent
I've discovered a rather troubling behaviour when using `in-range` with
floating point numbers, which I think is worth knowing in case you
(length (for/list ([i (in-range .1 .7 .1)]) i)) ; 6
(length (for/list ([i (in-range .1 .8 .1)]) i)) ; 8 (!)
(length (for/list ([i (in-range 1/10 7/10 1/10)]) i)) ; 6
(length (for/list ([i (in-range 1/10 8/10 1/10)]) i)) ; 7
Would it be a good idea to safe-guard these kinds of cases directly in
`in-range`?
The problem is an old one that already troubled programmers in the age
of Fortran. I suspect there is no reasonable safe-guard, with
"reasonable" meaning that it does what people expect in all situations.
The only way to stay out of trouble is to avoid loops defined by an
accumulating floating-point value. This means either don't use floats
(write the loop over integers or rationals and convert to floats when
using the loop index), or don't use accumulation (define your range by
two points and the number of subdivisions, rather than the width of
subintervals).
I should have chimed in to support this two days ago, but this is
exactly the right answer. Here's what Konrad means by his first
alternative (write the loop over integers or rationals):

(length
(for*/list ([i (in-range 1 8 1)]
[i (in-value (* i 0.1))])
i))

The second alternative is a little harder to get right because of
fencepost errors [1]. Fortunately, Racket has a library function for it.
Post by Konrad Hinsen
(require (only-in plot/utils linear-seq))
(linear-seq 0.0 1.0 4)
'(0.0 0.3333333333333333 0.6666666666666666 1.0)
Post by Konrad Hinsen
(linear-seq 0.0 1.0 4 #:start? #f)
'(0.14285714285714285 0.42857142857142855 0.7142857142857142 1.0)
Post by Konrad Hinsen
(linear-seq 0.0 1.0 4 #:start? #f #:end? #f)
'(0.125 0.375 0.625 0.875)

I should really move this function into `math/base`.

If you must use a flonum step size, do something like this:

(define (flonum-range start end step)
(define n (exact-ceiling (/ (- end start) step)))
(for/list ([i (in-range 0 n)])
(+ start (* i step))))

To get points with 0.5 ulp error each, which is the best you can do, add
(require math/flonum) to your program and change the loop body to (flfma
step (fl i) start).

Arguably, `in-range` should do something like the above when given
floating-point step lengths. I don't know how feasible that is, though.

Neil $B"](B

[1] http://en.wikipedia.org/wiki/Off-by-one_error#Fencepost_error

____________________
Racket Users list:
http://lists.racket-lang.org/users
Konrad Hinsen
2015-02-27 08:00:47 UTC
Permalink
Post by Neil Toronto
The second alternative is a little harder to get right because of
fencepost errors [1]. Fortunately, Racket has a library function for it.
Post by Konrad Hinsen
(require (only-in plot/utils linear-seq))
(linear-seq 0.0 1.0 4)
'(0.0 0.3333333333333333 0.6666666666666666 1.0)
That's the best solution in my opinion.
Post by Neil Toronto
I should really move this function into `math/base`.
Yes!
Post by Neil Toronto
(define (flonum-range start end step)
(define n (exact-ceiling (/ (- end start) step)))
(for/list ([i (in-range 0 n)])
(+ start (* i step))))
To get points with 0.5 ulp error each, which is the best you can do, add
(require math/flonum) to your program and change the loop body to (flfma
step (fl i) start).
Arguably, `in-range` should do something like the above when given
floating-point step lengths. I don't know how feasible that is, though.
It would obviously break backward compatibility. And it would complicate
the rather simple mental model one can have of in-range.

More fundamentally, I am not sure it would be much of an advantage.
Think of the two kinds of users:

1) Those who are not aware of the problem. They will still fall into the
trap, only a bit later, and the cause of the problem will be even more
hidden. It's a bit like languages that do decimal rounding of float output.

2) Those who are aware of the problem. They would use linear-seq, but
might prefer the modified in-range in specific situations for compactness.

I suspect group 1) is the bigger one, so the most important measure is
to promote linear-seq instead of in-range for floats.

Konrad.

____________________
Racket Users list:
http://lists.racket-lang.org/users
Robby Findler
2015-02-27 13:33:02 UTC
Permalink
Should we consider making in-range signal an error for floats? I guess
backwards compatibility says "no" but maybe in a future racket? With a note
in the error message that points people in the right direction?

Robby
Post by Neil Toronto
The second alternative is a little harder to get right because of
Post by Neil Toronto
fencepost errors [1]. Fortunately, Racket has a library function for it.
Post by Konrad Hinsen
(require (only-in plot/utils linear-seq))
(linear-seq 0.0 1.0 4)
'(0.0 0.3333333333333333 0.6666666666666666 1.0)
That's the best solution in my opinion.
I should really move this function into `math/base`.
Yes!
Post by Neil Toronto
(define (flonum-range start end step)
(define n (exact-ceiling (/ (- end start) step)))
(for/list ([i (in-range 0 n)])
(+ start (* i step))))
To get points with 0.5 ulp error each, which is the best you can do, add
(require math/flonum) to your program and change the loop body to (flfma
step (fl i) start).
Arguably, `in-range` should do something like the above when given
floating-point step lengths. I don't know how feasible that is, though.
It would obviously break backward compatibility. And it would complicate
the rather simple mental model one can have of in-range.
More fundamentally, I am not sure it would be much of an advantage. Think
1) Those who are not aware of the problem. They will still fall into the
trap, only a bit later, and the cause of the problem will be even more
hidden. It's a bit like languages that do decimal rounding of float output.
2) Those who are aware of the problem. They would use linear-seq, but
might prefer the modified in-range in specific situations for compactness.
I suspect group 1) is the bigger one, so the most important measure is to
promote linear-seq instead of in-range for floats.
Konrad.
____________________
http://lists.racket-lang.org/users
Sam Tobin-Hochstadt
2015-02-27 13:39:11 UTC
Permalink
At a minimum, we could (a) log a warning and (b) put a note in the
docs suggesting that people use Neil's library for floats.

Sam

On Fri, Feb 27, 2015 at 8:33 AM, Robby Findler
Post by Robby Findler
Should we consider making in-range signal an error for floats? I guess
backwards compatibility says "no" but maybe in a future racket? With a note
in the error message that points people in the right direction?
Robby
Post by Konrad Hinsen
Post by Neil Toronto
The second alternative is a little harder to get right because of
fencepost errors [1]. Fortunately, Racket has a library function for it.
Post by Konrad Hinsen
(require (only-in plot/utils linear-seq))
(linear-seq 0.0 1.0 4)
'(0.0 0.3333333333333333 0.6666666666666666 1.0)
That's the best solution in my opinion.
Post by Neil Toronto
I should really move this function into `math/base`.
Yes!
Post by Neil Toronto
(define (flonum-range start end step)
(define n (exact-ceiling (/ (- end start) step)))
(for/list ([i (in-range 0 n)])
(+ start (* i step))))
To get points with 0.5 ulp error each, which is the best you can do, add
(require math/flonum) to your program and change the loop body to (flfma
step (fl i) start).
Arguably, `in-range` should do something like the above when given
floating-point step lengths. I don't know how feasible that is, though.
It would obviously break backward compatibility. And it would complicate
the rather simple mental model one can have of in-range.
More fundamentally, I am not sure it would be much of an advantage. Think
1) Those who are not aware of the problem. They will still fall into the
trap, only a bit later, and the cause of the problem will be even more
hidden. It's a bit like languages that do decimal rounding of float output.
2) Those who are aware of the problem. They would use linear-seq, but
might prefer the modified in-range in specific situations for compactness.
I suspect group 1) is the bigger one, so the most important measure is to
promote linear-seq instead of in-range for floats.
Konrad.
____________________
http://lists.racket-lang.org/users
____________________
http://lists.racket-lang.org/users
____________________
Racket Users list:
http://lists.racket-lang.org/users
Jens Axel Søgaard
2015-02-27 13:43:45 UTC
Permalink
In addition to Sam's suggestion: add an in-float-range.

/Jens Axel
Post by Sam Tobin-Hochstadt
At a minimum, we could (a) log a warning and (b) put a note in the
docs suggesting that people use Neil's library for floats.
Sam
On Fri, Feb 27, 2015 at 8:33 AM, Robby Findler
Post by Robby Findler
Should we consider making in-range signal an error for floats? I guess
backwards compatibility says "no" but maybe in a future racket? With a note
in the error message that points people in the right direction?
Robby
Post by Konrad Hinsen
Post by Neil Toronto
The second alternative is a little harder to get right because of
fencepost errors [1]. Fortunately, Racket has a library function for it.
Post by Konrad Hinsen
(require (only-in plot/utils linear-seq))
(linear-seq 0.0 1.0 4)
'(0.0 0.3333333333333333 0.6666666666666666 1.0)
That's the best solution in my opinion.
Post by Neil Toronto
I should really move this function into `math/base`.
Yes!
Post by Neil Toronto
(define (flonum-range start end step)
(define n (exact-ceiling (/ (- end start) step)))
(for/list ([i (in-range 0 n)])
(+ start (* i step))))
To get points with 0.5 ulp error each, which is the best you can do, add
(require math/flonum) to your program and change the loop body to (flfma
step (fl i) start).
Arguably, `in-range` should do something like the above when given
floating-point step lengths. I don't know how feasible that is, though.
It would obviously break backward compatibility. And it would complicate
the rather simple mental model one can have of in-range.
More fundamentally, I am not sure it would be much of an advantage. Think
1) Those who are not aware of the problem. They will still fall into the
trap, only a bit later, and the cause of the problem will be even more
hidden. It's a bit like languages that do decimal rounding of float output.
2) Those who are aware of the problem. They would use linear-seq, but
might prefer the modified in-range in specific situations for compactness.
I suspect group 1) is the bigger one, so the most important measure is to
promote linear-seq instead of in-range for floats.
Konrad.
____________________
http://lists.racket-lang.org/users
____________________
http://lists.racket-lang.org/users
____________________
http://lists.racket-lang.org/users
--
--
Jens Axel Søgaard

____________________
Racket Users list:
http://lists.racket-lang.org
Konrad Hinsen
2015-02-27 14:37:32 UTC
Permalink
Should we consider making in-range signal an error for floats? I
guess backwards compatibility says "no" but maybe in a future
racket? With a note in the error message that points people in the
right direction?
In a future Racket or perhaps in a specialized language (teaching
language, DSL, ...) specifically aimed at numerical tasks. I believe
that Racket has an enormous unexploited potential there.

Some ideas:

- A teaching and research language for numerical problems, with
first-class syntactical support for rationals, floats, bigfloats,
and (dreaming...) exact reals.

- A DSL for numerical optimization, which eliminates all constructs
that can reduce efficiency.

- A DSL restricted to computations that can be offloaded to a GPU.
At a minimum, we could (a) log a warning and (b) put a note in the
docs suggesting that people use Neil's library for floats.
That sounds good for immediate action.

Konrad.

____________________
Racket Users list:
http://lists.racket-lang.org/users
Matthias Felleisen
2015-02-27 20:01:24 UTC
Permalink
Post by Konrad Hinsen
- A teaching and research language for numerical problems, with
first-class syntactical support for rationals, floats, bigfloats,
and (dreaming...) exact reals.
I am sure Neil is working on this. -- Matthias :-)
____________________
Racket Users list:
http://lists.racket-lang.org/users

Loading...