[racket] Inconsistency of `in-range` and numerical issues

From: Konrad Hinsen (konrad.hinsen at fastmail.net)
Date: Wed Feb 25 03:04:14 EST 2015

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 

  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 

  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.


Posted on the users mailing list.