Single-precision float support used to be enabled via a configure
option, which meant that some Racket installations would support them,
and some would not.

Since zo files are meant to be portable, they could not contain
single-precision floats. So, compilation would promote single literals
to doubles.

This meant that compilation could change the behavior of a program.
Here's an example:

    stamourv@ahuntsic:small-float-test$ cat test2.rkt
    #lang racket
    (define (f x) (displayln (flonum? x)))
    (f (if (with-input-from-string "#t" read) 1.0f0 2.0f0))
    stamourv@ahuntsic:small-float-test$ racket test2.rkt
    #f
    stamourv@ahuntsic:small-float-test$ raco make test2.rkt
    stamourv@ahuntsic:small-float-test$ racket test2.rkt
    #t
    stamourv@ahuntsic:small-float-test$

This example has to be a bit convoluted to defeat constant folding,
which makes the problem disappear:

    stamourv@ahuntsic:small-float-test$ cat test.rkt
    #lang racket
    (flonum? 1.0f0)
    stamourv@ahuntsic:small-float-test$ racket test.rkt
    #f
    stamourv@ahuntsic:small-float-test$ raco make test.rkt
    stamourv@ahuntsic:small-float-test$ racket test.rkt
    #f
    stamourv@ahuntsic:small-float-test$ raco decompile test.rkt
    (begin
      (module test ....
        (#%apply-values |_print-values@(lib "racket/private/modbeg.rkt")| '#f)))
    stamourv@ahuntsic:small-float-test$

This can make for pretty elusive bugs. This gets especially problematic
when you add unsafe float operations to the mix, which can turn these
issues into segfaults.

The solution we picked was to support single-precision floats by default
and to allow them in zos, which makes these discrepancies go away.

I agree that having to handle single floats when reasoning about numbers
complicates things, and it annoys me too. But I still think it's less
problematic than what I describe above.


At Wed, 12 Sep 2012 08:31:29 -0600,
Neil Toronto wrote:
> 
> I ask because I'm tired of worrying about them. More precisely, I'm 
> tired of Typed Racket (rightly) making me worry about them.
> 
> I'm also a little bit tired of this:
> 
> (: foo (case-> (Single-Flonum -> Single-Flonum)
>                 (Flonum -> Flonum)
>                 (Real -> Real)))
> (define (foo x)
>    (cond [(double-flonum? x)  (flfoo x)]
>          [(single-flonum? x)  (real->single-flonum
>                                (flfoo (real->double-flonum x)))]
>          [else  (flfoo (real->double-flonum x))]))

This function already converts rationals to doubles, and it seems
`flfoo' produces doubles too. You could drop the second clause, always
produce doubles, change the type to `(Real -> Flonum)' and leave the
conversion to single to the client. Since the math library always
operates on doubles internally anyway, this would also eliminate
unnecessary conversions to singles between stages of a pipeline.

> They make it easy to write wrong code, because it's easy to use 
> `exact->inexact' when you really should use `real->double-flonum'. Plot, 
> for example, fails to render functions that return single flonums. I'm 
> surprised it doesn't segfault.

Good point, that's a problem.

> I'm sure plot isn't the only one. Every use of `exact->inexact' in the 
> standard library is suspect. If followed by applying an unsafe op, it's 
> wrong.
> 
> Why do we have these things?

I don't know why they were added originally (as an option). In my limited
experience, I don't think I've seen non-test code that uses them.

Vincent
_________________________
  Racket Developers list:
  http://lists.racket-lang.org/dev

Reply via email to