From: Raymond Toy
Subject: Type specifiers and signed zero
Date: 
Message-ID: <4nafe6tayq.fsf@rtp.ericsson.se>
Recently there was some discussion on the CMUCL mailing list about how 
type specifiers are supposed to work with signed zeros.

According to the Hyperspec, it seems that arithmetic comparisons are
used for testing interval subtypes.  Thus, if I have a type
(single-float 0.0 1.0), the Hyperspec would imply that -0.0 is a
member of this type because -0.0 = 0.0, as specified by IEEE FP
arithmetic (I think).

Now consider the code:

(defun tst (x)
  (declare (type (single-float 0.0 1.0) x))
  (sqrt x))

For a smart compiler like CMUCL, we can actually call the C sqrt
function or use the FP fsqrt instruction depending on the
architecture, because the compiler can prove that sqrt is always a
single-float in this case.

However, (tst -0.0) would be accepted and should return #c(-0.0 0.0).
I believe this is the correct value for (sqrt -0.0), as given in
Kahan's paper, referenced in the Hyperspec.

The fact that -0.0 is accepted means that the compiler cannot optimize 
this case and must use the generic sqrt function.  If you want it to
be optimized, you would have to write the declaration as (single-float 
(0.0) 1.0).

Was the intent of the Hyperspec really to say -0.0 is a member of
(single-float 0.0 1.0)?  In most cases, it doesn't matter, but for
functions with branch points at 0, it makes a difference.

Ray

From: Barry Margolin
Subject: Re: Type specifiers and signed zero
Date: 
Message-ID: <66sq0j$cpg@tools.bbnplanet.com>
In article <··············@rtp.ericsson.se>,
Raymond Toy  <···@rtp.ericsson.se> wrote:
>Was the intent of the Hyperspec really to say -0.0 is a member of
>(single-float 0.0 1.0)?  In most cases, it doesn't matter, but for
>functions with branch points at 0, it makes a difference.

The intent of the HyperSpec is to say what the ANSI specification says, so
I assume your question is really about the intent of the ANSI spec, and by
extension X3J13 and the original CL designers (since I think this aspect
hasn't changed since the original CLtL).

In general, an object x is of type (single-float min max) if it's of type
single-float and (<= min x max) is true.  -0.0 causes a number of warts due
to the requirement that it behave like 0.0 *almost* all the time.  So it
behaves like 0.0 when passed to <=, but not when passed to SQRT.  As you
point out, this means that functions with branch cuts at 0 must handle it
specially.

-- 
Barry Margolin, ······@bbnplanet.com
GTE Internetworking, Powered by BBN, Cambridge, MA
Support the anti-spam movement; see <http://www.cauce.org/>
Please don't send technical questions directly to me, post them to newsgroups.
From: Raymond Toy
Subject: Re: Type specifiers and signed zero
Date: 
Message-ID: <4n7m97u0ko.fsf@rtp.ericsson.se>
>>>>> "Barry" == Barry Margolin <······@bbnplanet.com> writes:

    Barry> In general, an object x is of type (single-float min max)
    Barry> if it's of type single-float and (<= min x max) is true.
    Barry> -0.0 causes a number of warts due to the requirement that
    Barry> it behave like 0.0 *almost* all the time.  So it behaves
    Barry> like 0.0 when passed to <=, but not when passed to SQRT.
    Barry> As you point out, this means that functions with branch
    Barry> cuts at 0 must handle it specially.

For fast numeric work, this is rather bad since that means the
compiler is not allowed to optimize

	(sqrt (the (single-float 0.0) x))

to call the C sqrt function.  You'd have to replace 0.0 with (0.0),
and, then, 0.0 would not be legal.  For sqrt this is bad.  For log,
this isn't since a case can be made for log being undefined for 0.0.
There's also a problem with phase, if the compiler is smart enough to
know that phase can only return 0 and +/- pi.

I think for type specifiers the distinction between -0.0 and 0.0 is
very useful.  

Ray
From: Kent M Pitman
Subject: Re: Type specifiers and signed zero
Date: 
Message-ID: <sfw7m97wqhq.fsf@world.std.com>
Raymond Toy <···@rtp.ericsson.se> writes:

> For fast numeric work, this is rather bad since that means the
> compiler is not allowed to optimize
> 
> 	(sqrt (the (single-float 0.0) x))
> 
> to call the C sqrt function.  You'd have to replace 0.0 with (0.0),
> and, then, 0.0 would not be legal. 

Well, you could write 
  (sqrt (the (or (eql 0.0) (single-float (0.0))) x))
and wonder if the compiler understood.

CMU Python might.  Others probably would not.

- - - -
We (on the CL design committee) used to get such funny bug reports
from the CMU Python crowd.  Thoughtful really--but perplexing.  Really
got to the heart of the nature of types in CL. My favorite was the one
about whether the type
 (member 1.0f0 #.(+ 1.0f0 single-float-epsilon))
was type equivalent to
 (single-float 1.0f0 #.(+ 1.0f0 single-float-epsilon))
That is, is the type space of single-floats intentionally enumerable or
are we supposed to pretend there are other single-floats in between even
though they cannot be distinct.
From: Raymond Toy
Subject: Re: Type specifiers and signed zero
Date: 
Message-ID: <4n3ejvtvf2.fsf@rtp.ericsson.se>
>>>>> "Kent" == Kent M Pitman <······@world.std.com> writes:

    Kent> Raymond Toy <···@rtp.ericsson.se> writes:
    >> For fast numeric work, this is rather bad since that means the
    >> compiler is not allowed to optimize
    >> 
    >> (sqrt (the (single-float 0.0) x))
    >> 
    >> to call the C sqrt function.  You'd have to replace 0.0 with (0.0),
    >> and, then, 0.0 would not be legal. 

    Kent> Well, you could write 
    Kent>   (sqrt (the (or (eql 0.0) (single-float (0.0))) x))
    Kent> and wonder if the compiler understood.

    Kent> CMU Python might.  Others probably would not.

Cute!  I was most interested in Python.  If you have the version from
yesterday or so, this works; earlier versions weren't this smart.
Unfortunately, it returns the result type as (single-float 0.0), which
might not be quite right since this includes -0.0 and is a hair wider
than it should be, and would certainly "fail" if we did another sqrt.
This ought to be fixed.

But, if x is -0.0, you get a run-time error saying x is the wrong
type.  Neat.


    Kent> We (on the CL design committee) used to get such funny bug
    Kent> reports from the CMU Python crowd.  Thoughtful really--but
    Kent> perplexing.  Really got to the heart of the nature of types
    Kent> in CL. My favorite was the one about whether the type
    Kent>  (member 1.0f0 #.(+ 1.0f0 single-float-epsilon))
    Kent> was type equivalent to
    Kent>  (single-float 1.0f0 #.(+ 1.0f0 single-float-epsilon))
    Kent> That is, is the type space of single-floats intentionally
    Kent> enumerable or are we supposed to pretend there are other
    Kent> single-floats in between even though they cannot be
    Kent> distinct.

Neat.  When float propagation was added a few months ago to Python,
there was a similar question about how to handle open intervals like
(single-float (0.0)).  Rob MacLachlan suggested converting this to
(single-float #.single-float-epsilon).  But the former is much
prettier than the latter.  It would have made the implementation much
easier if we had done the latter though.  This is what happens to
integer types: open bounds are converted to closed bounds.

Ray
From: Raymond Toy
Subject: Re: Type specifiers and signed zero
Date: 
Message-ID: <4n3ejpwal4.fsf@rtp.ericsson.se>
>>>>> "Raymond" == Raymond Toy <···@rtp.ericsson.se> writes:

    Raymond> Was the intent of the Hyperspec really to say -0.0 is a member of
    Raymond> (single-float 0.0 1.0)?  In most cases, it doesn't matter, but for
    Raymond> functions with branch points at 0, it makes a difference.

Thanks to everyone who responded to this message.  However, this last
question is still up in the air.  Is -0.0 supposed to be a member of
(single-float 0.0 1.0)?  My feeling is that it is not, although the
Hyperspec seems to say that it is.  I think this was an easy oversight 
on the part of the CL committee.

Ray