[racket] Inconsistency of `in-range` and numerical issues
On 24/02/2015 23:01, Steve Graham wrote:
> 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.
> MUMPS (http://en.wikipedia.org/wiki/MUMPS), my workday language for
> 30-some years, would never think of acting in such a manner:
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.
> 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.