September 13, 2005
Derek Parnell wrote:
> On Mon, 12 Sep 2005 16:59:23 -0700, Walter Bright wrote:
> 
> 
>>"Derek Parnell" <derek@psych.ward> wrote in message
>>news:y1h8ymdxkhwn.tg27eopfhs2k.dlg@40tude.net...
>>
>>>Garbage! Its called 'abstraction'. The term 'signbit' implies that we are
>>>asking for a detail of the implementation rather than the mathematical
>>>concept of 'sign'.
>>
>>One always must be careful when thinking about the "mathematical" concept of
>>sign, because floating point doesn't work that way. There's the issue of
>>negative 0, which is not really a mathematical concept, it's more of a
>>kludge to deal with the finite precision of the floating point format.
>>There's also the sign bit of a NaN number, what does that mean?
> 
> 
> Huh? Okay, it must be me. I'm a simple person with simple ideas. 
> 
> When somebody asks me what's the sign of such-and-such, I just say
> "Negative" if its less than zero, "Positive" if greater than zero and
> "Nothing" if it is zero OR not a number.
> 
> What has that got to do with floating point, conceptually or physically?
> 
> 
>>Any time one wants to test for the signedness of a floating point number,
>>one has to think about "what happens if it is -0" 
> 
> 
> is -0 less than zero? If yes, then its negative.

Ah. It is not less than zero.
The sign bit doesn't correspond exactly to mathematical sign.
It's almost a 'history' bit -- did we get to zero from above or below?
1.0/+infinity = 0.0
1.0/-infinity = -0.0

and
1.0 / 0.0  = +infinity
1.0 / -0.0 = -infinity

Therefore, if
Given x, if we let
y = sign(x) * abs(x)
then with sign as you've described, sign(-0.0)=0 so y = 0.0.
With IEEE, you'll have x==y.
But...
1.0/x = -infinity
1.0/y = +infinity.

so x and y are not the same.
It is important for sign(x)*abs(x) to be the same as x.

If you want to know "is it less than zero", use the built in < operator.


> Yes, signbit() is required to GET AT THE SIGN BIT. But that is not what I'm
> talking about. That is an implementation detail of the particular floating
> point representation system you choose to use. It has nothing to do with
> the simple mathematical question "what is the sign of X?", which may be a
> floating point, fixed point, or integer.

>>A sign() function does not add sufficient value, 
> 
> 
> Says who? You? And how come you get to be the Grand Arbiter of "sufficient
> value"? What empirical measurement did you apply to get to that decision?
> Define 'sufficient' in this context. I would say that value is in the eye
> of the beholder.

>>and it may even serve to obfuscate
>>things because the reader will have to go look up the doc on it to see what
>>it does with -0 and -NaN. std.math should strive to be an orthogonal set of
>>nontrivial building blocks. 
> 
> 
> Why 'nontrivial'? What may be trivial to one person is not so to others.
> Some people regard various sort algorithms as trivial. The library should
> be an enabler.

Have you heard the saying, "every line of code is a liability"? You have to document and maintain it. And library users have to learn all the available functions.
It's a cost benefit thing. Of course it's subjective, and is Walter's opinion, but if you want to persuade him, you need to provide use cases.
The cost is clear. The benefit at this stage is not. When would you use sign() ?
September 13, 2005
On Tue, 13 Sep 2005 08:36:00 +0200, Don Clugston wrote:

> Derek Parnell wrote:

[snip]
>> When somebody asks me what's the sign of such-and-such, I just say "Negative" if its less than zero, "Positive" if greater than zero and "Nothing" if it is zero OR not a number.
>> 

[snip]

>> is -0 less than zero? If yes, then its negative.
> 
> Ah. It is not less than zero.
> The sign bit doesn't correspond exactly to mathematical sign.

I wasn't talking about the sign bit. That's an implementation artifact. I am talking about the mathematical sign.

> It's almost a 'history' bit -- did we get to zero from above or below?
> 1.0/+infinity = 0.0
> 1.0/-infinity = -0.0
> 
> and
> 1.0 / 0.0  = +infinity
> 1.0 / -0.0 = -infinity

So -0.0 is zero that's come from the direction of -infinity.

> Therefore, if
> Given x, if we let
> y = sign(x) * abs(x)
> then with sign as you've described, sign(-0.0)=0 so y = 0.0.

What is the value of abs(-0.0)? Is it zero? If it is, then the value of
sign(-0.0) is not relevant.

> With IEEE, you'll have x==y.
> But...
> 1.0/x = -infinity
> 1.0/y = +infinity.
> 
> so x and y are not the same.
> It is important for sign(x)*abs(x) to be the same as x.

Why is that important? Why does sign() have to return -1, 0, or 1? It could return any one of three values, where one informs you that the input is negative, another informs you that it is positive, and the third that it has no mathematical sign.

> If you want to know "is it less than zero", use the built in < operator.

Yes, that is what I would do. I can't see any reason why I would need to
use a sign() function. I am just arguing from a philosophical point of
view.

>> Yes, signbit() is required to GET AT THE SIGN BIT. But that is not what I'm talking about. That is an implementation detail of the particular floating point representation system you choose to use. It has nothing to do with the simple mathematical question "what is the sign of X?", which may be a floating point, fixed point, or integer.
> 
>>>A sign() function does not add sufficient value,
>> 
>> Says who? You? And how come you get to be the Grand Arbiter of "sufficient value"? What empirical measurement did you apply to get to that decision? Define 'sufficient' in this context. I would say that value is in the eye of the beholder.
> 
>>>and it may even serve to obfuscate
>>>things because the reader will have to go look up the doc on it to see what
>>>it does with -0 and -NaN. std.math should strive to be an orthogonal set of
>>>nontrivial building blocks.
>> 
>> Why 'nontrivial'? What may be trivial to one person is not so to others. Some people regard various sort algorithms as trivial. The library should be an enabler.
> 
> Have you heard the saying, "every line of code is a liability"?

No, I haven't. I would hope that every line of code is an asset, even it does have a cost associated with it.

>You have to document and maintain it.

Yep, that's the cost. Pretty small one too.

> And library users have to learn all the available functions.

No they don't. I only learn about the ones I need to use.

> It's a cost benefit thing. Of course it's subjective, and is Walter's opinion, but if you want to persuade him, you need to provide use cases. The cost is clear. The benefit at this stage is not. When would you use sign() ?

No, I can't see myself using it. But once it exists and tested, it's cost is microscopic and it would be appreciated by someone, therefore benefiting both the user and the librarian.

-- 
Derek
(skype: derek.j.parnell)
Melbourne, Australia
13/09/2005 5:13:32 PM
September 13, 2005
>>>is -0 less than zero? If yes, then its negative.
>>
>>Ah. It is not less than zero.
>>The sign bit doesn't correspond exactly to mathematical sign.
> 
> 
> I wasn't talking about the sign bit. That's an implementation artifact. I
> am talking about the mathematical sign.
> 
> 
>>It's almost a 'history' bit -- did we get to zero from above or below?
>>1.0/+infinity = 0.0
>>1.0/-infinity = -0.0
>>
>>and
>>1.0 / 0.0  = +infinity
>>1.0 / -0.0 = -infinity
> 
> 
> So -0.0 is zero that's come from the direction of -infinity.

Exactly.

>>Given x, if we let
>>y = sign(x) * abs(x)
>>then with sign as you've described, sign(-0.0)=0 so y = 0.0.
> 
> 
> What is the value of abs(-0.0)? Is it zero? If it is, then the value of
> sign(-0.0) is not relevant.
> 
> 
>>With IEEE, you'll have x==y.
>>But...
>>1.0/x = -infinity
>>1.0/y = +infinity.
>>
>>so x and y are not the same.
>>It is important for sign(x)*abs(x) to be the same as x.
> 
> 
> Why is that important? Why does sign() have to return -1, 0, or 1? It could
> return any one of three values, where one informs you that the input is
> negative, another informs you that it is positive, and the third that it
> has no mathematical sign.

OK, that would work.
But then you have to look up the docs for sign() to find out what the values are, and it doesn't help with writing or reading code.
And the value of the function drops.

In the extreme case, you could have a library with functions like

real plus57point36(real x) { return x + 57.36; }

Trivial, and useless.

A problem with trivial functions is that there are so many of them. Why should only some get library status?

How can you decide which are in, and which are out? I really think you have to be confident that the functions will be useful in multiple programs. Functions which are trivial and rarely used are generally not worth the effort. But there's definitely a gray area where it is very subjective.
September 13, 2005
"Derek Parnell" <derek@psych.ward> wrote in message news:3u4tyzqorpzs$.19mwmyde18c3i.dlg@40tude.net...
> What is the value of abs(-0.0)? Is it zero? If it is, then the value of
> sign(-0.0) is not relevant.

But it is relevant in certain situations. Please refer to http://www.cs.berkeley.edu/~wkahan/JAVAhurt.pdf pages 13 to 15 by Prof. Kahan, who pretty much invented IEEE 754 floating point arithmetic. The sign of 0 is carefully maintained and tracked by the underlying floating point implementation and the math functions.


> No, I can't see myself using it. But once it exists and tested, it's cost is microscopic and it would be appreciated by someone, therefore
benefiting
> both the user and the librarian.

If the library fills up with trivial one liners, it will serve to discourage people from using it. Library functions need to each have a significant purpose. For example, consider the extreme case:

int addTwo(int x) { return x + 2; }

Can we agree that one would look askance at a library containing such functions? I sure would suspect that the author of such a library was trying to pump up the function count for marketing purposes. On the other hand, writing a well behaved, efficient asinh() is not at all easy, and so it makes for an ideal library function. Where's the dividing line? Of course it's a matter of judgement, and to me sign() falls on the wrong side of that line.


September 13, 2005
On Tue, 13 Sep 2005 10:48:09 +0200, Don Clugston wrote:


[snip]

>>>Given x, if we let
>>>y = sign(x) * abs(x)
>>>then with sign as you've described, sign(-0.0)=0 so y = 0.0.
>> 
>> 
>> What is the value of abs(-0.0)? Is it zero? If it is, then the value of
>> sign(-0.0) is not relevant.

Am I writing with invisible ink?

My point was, that if "sign(x) * abs(x) == x" must be true for all values
of x, and if the value of abs(-0.0) is zero, then it is irrelevant *in this
context* what the value of sign(-0.0) is because the result of
sign(-0.0)*abs(-0.0) will always be zero and never -0.0

>>>With IEEE, you'll have x==y.
>>>But...
>>>1.0/x = -infinity
>>>1.0/y = +infinity.
>>>
>>>so x and y are not the same.
>>>It is important for sign(x)*abs(x) to be the same as x.
>> 
>> 
>> Why is that important? Why does sign() have to return -1, 0, or 1? It could return any one of three values, where one informs you that the input is negative, another informs you that it is positive, and the third that it has no mathematical sign.
> 
> OK, that would work.
> But then you have to look up the docs for sign() to find out what the
> values are,

What rubbish! We need to look up the docs for all functions at least once. And what's hard about doing that, especially with the whizz-bang IDEs that modern-day coders are so dependant on.

> and it doesn't help with writing or reading code.
> And the value of the function drops.

Are you trying to be difficult? How is this hard to read ...

  if (sign(x) == Negative)

Is that not self documenting? IMNHSO, that is better than something like ...

  if (sign(x) == -1)

because -1 is MINUS-ONE and not Negative. It might be a symbolic code that used to represent Negative, but in itself, it is not Negative, it's minus-one! Just as my 'Negative' is a symbolic representation of the concept, at least it is self documenting.

> In the extreme case, you could have a library with functions like
> 
> real plus57point36(real x) { return x + 57.36; }
> 
> Trivial, and useless.

Now you are insulting me. Even the hypothetical sign() function would get hugely more usage than this *extreme* example, so what's your point?

> A problem with trivial functions is that there are so many of them. Why should only some get library status? How can you decide which are in, and which are out? I really think you have to be confident that the functions will be useful in multiple programs. Functions which are trivial and rarely used are generally not worth the effort. But there's definitely a gray area where it is very subjective.

Isn't one of the lovely things about 'trivial' routines is that they can be inlined by the compiler and still create legible code by expressing the coders intentions better than spelling out the operation in full every time.

-- 
Derek Parnell
Melbourne, Australia
13/09/2005 9:22:35 PM
September 13, 2005
> 
> Am I writing with invisible ink?
> 
> My point was, that if "sign(x) * abs(x) == x" must be true for all values
> of x, and if the value of abs(-0.0) is zero, then it is irrelevant *in this
> context* what the value of sign(-0.0) is because the result of
> sign(-0.0)*abs(-0.0) will always be zero and never -0.0

AHA!  Here's the heart of the matter.
That is what you'd expect, intuitively. But intuition is misleading in this case. Indeed, abs(-0.0) returns 0.0.
But -1 * 0.0 returns -0.0
whereas 1 * 0.0 returns 0.0
It does make a difference what sign(-0.0) returns.

I think this is why we have this disagreement. sign(x) returning -1, 0, or 1 is intuitive, but unfortunately it's wrong for computer mathematics.

There is a standard mathematical function sign(), AKA signum(), which returns -1, 0, or 1, depending on the sign. You'd normally use it in an expression, multiplying by something else.
Eg:
    sign(x) * log(abs(x))

An important feature of this function is that sign(x)*abs(x) returns x.
Unfortunately, you cannot satisfy this identity without violating your intuition (sign(0) can't return 0).
And since sign(0) can't return 0, then another identity is violated:
if (a==b) implies sign(a)==sign(b)
will be violated if a is 0.0 and b is -0.0. Both are zero, yet they have opposite signs.

> Are you trying to be difficult? How is this hard to read ...
> 
>   if (sign(x) == Negative)
> 
> Is that not self documenting? IMNHSO, that is better than something like
>   if (sign(x) == -1) 
> because -1 is MINUS-ONE and not Negative. It might be a symbolic code that
> used to represent Negative, but in itself, it is not Negative, it's
> minus-one! Just as my 'Negative' is a symbolic representation of the
> concept, at least it is self documenting.

 >
> 
>>In the extreme case, you could have a library with functions like
>>
>>real plus57point36(real x) { return x + 57.36; }
>>
>>Trivial, and useless.
> 
> 
> Now you are insulting me. Even the hypothetical sign() function would get
> hugely more usage than this *extreme* example, so what's your point?

I'm not intending to insult you. The point is to establish that you have to draw the line somewhere. Once we agree that there is a line, we can discuss where it lies, and that's likely to be much more fruitful.

>>A problem with trivial functions is that there are so many of them. Why should only some get library status? How can you decide which are in, and which are out? I really think you have to be confident that the functions will be useful in multiple programs. Functions which are trivial and rarely used are generally not worth the effort. But there's definitely a gray area where it is very subjective.
> 
> 
> Isn't one of the lovely things about 'trivial' routines is that they can be
> inlined by the compiler and still create legible code by expressing the
> coders intentions better than spelling out the operation in full every
> time. 

 if (sign(x) == Negative)

is less legible and longer than

 if (x<0)

and it is clear what the latter does when presented with a NAN or a negative 0.

If you could write an intuitive and correct sign() function, I would agree that it belongs in a library. But it's just not possible.
September 13, 2005
"Don Clugston" <dac@nospam.com.au> wrote in message news:dg6o3m$1ekn$1@digitaldaemon.com...
> >
> > Am I writing with invisible ink?
> >
> > My point was, that if "sign(x) * abs(x) == x" must be true for all
values
> > of x, and if the value of abs(-0.0) is zero, then it is irrelevant *in
this
> > context* what the value of sign(-0.0) is because the result of
> > sign(-0.0)*abs(-0.0) will always be zero and never -0.0
>
> AHA!  Here's the heart of the matter.
> That is what you'd expect, intuitively. But intuition is misleading in
> this case. Indeed, abs(-0.0) returns 0.0.
> But -1 * 0.0 returns -0.0
> whereas 1 * 0.0 returns 0.0
> It does make a difference what sign(-0.0) returns.

The following also holds with floating point math:

0 * 0 => 0
0 * -0 => -0
-0 * 0 => -0
-0 * -0 => 0

For one case where the sign of 0 is critical, consider the atan2 function:

    atan2(0, -0) => pi
    atan2(-0, -0) => -pi

pow() is also sensitive to the sign of 0 for x when y is a negative odd integer - it will return plus or minus infinity.


September 14, 2005
Walter Bright wrote:
> "Derek Parnell" <derek@psych.ward> wrote in message
> news:y1h8ymdxkhwn.tg27eopfhs2k.dlg@40tude.net...
>> Garbage! Its called 'abstraction'. The term 'signbit' implies that we are
>> asking for a detail of the implementation rather than the mathematical
>> concept of 'sign'.
> 
> ...
> Any time one wants to test for the signedness of a floating point number,
> one has to think about "what happens if it is -0" and "what happens if it is
> a NaN". Making a function called "sign()" can't resolve the problem. It'll
> pretend to, and that'll lead to bugs. If one wants to go beyond (a<0), using
> signbit() is the right approach, because it causes one to have to think
> about the other cases.
> 
...
> 
> 

One could have sign() raise an exception if the concept of sign didn't really apply, and require that those cases be handled with signbit().  Or one could return +1, 0, or -1, with zero being both 0 & -0 (and either raise an exception for Nan's or shoehorn those into the 0 value also).

There are reasonable ways to handle it, but there are different requirements for signbit() and sign().  signbit() specifically addresses the internal representation.
September 14, 2005
> One could have sign() raise an exception if the concept of sign didn't really apply, and require that those cases be handled with signbit().  Or one could return +1, 0, or -1, with zero being both 0 & -0 (and either raise an exception for Nan's or shoehorn those into the 0 value also).

That's a really good point. I'd forgotten that sign() itself could return a negative zero. It should return 0 & -0 for +-NAN.

Well done, guys, you've convinced me. When defined in that way,
sign() has obvious uses, and is surprisingly subtle and easy to get wrong.

real sign(real x) {
   return x>0 ? 1 : x<0 ? -1 : copysign(0, x);
}

behaves intuitively, and satisfies
sign(x)*abs(x) is the same as x, for all x,
even when x=-0.0, +-NAN.

Now we have something (IMHO) well worthy of inclusion.

> There are reasonable ways to handle it, but there are different requirements for signbit() and sign().  signbit() specifically addresses the internal representation.
September 14, 2005
Don Clugston wrote:
>> One could have sign() raise an exception if the concept of sign didn't really apply, and require that those cases be handled with signbit().  Or one could return +1, 0, or -1, with zero being both 0 & -0 (and either raise an exception for Nan's or shoehorn those into the 0 value also).
> 
> 
> That's a really good point. I'd forgotten that sign() itself could return a negative zero. It should return 0 & -0 for +-NAN.
> 
> Well done, guys, you've convinced me. When defined in that way,
> sign() has obvious uses, and is surprisingly subtle and easy to get wrong.
> 
> real sign(real x) {
>    return x>0 ? 1 : x<0 ? -1 : copysign(0, x);
> }
> 
> behaves intuitively, and satisfies
> sign(x)*abs(x) is the same as x, for all x,
> even when x=-0.0, +-NAN.

Actually, more accurate and simpler is to return +-NAN when presented with a NAN.
This also satisfies the identity, because -NAN * NAN = -NAN.
So actually there are six different possible return values from this function!
It can be done with only a single branch.
How about it, Walter?
-----------------------------------

/** The mathematical sign() or signum() function.
    Returns 1 if x is positive, -1 if x is negative,
            0 if x is zero, real.nan if x is a nan.
    Preserves sign of +-0 and +-nan.
*/
real sign(real x)
{
   return x<>0 ? copysign(1, x) : x;
}

unittest {
assert( sign(-2.5) == -1 );
assert( isNegZero(sign(-0.0)) );
assert( isPosZero(sign(0.0)) );
assert( sign(real.infinity)==1 );
assert( isPosNan(sign(real.nan)) );
assert( isNegNan(sign(-real.nan)) );
}

// internal only

bit isPosNan(real x)
{
   return isnan(x) && !signbit(x);
}

bit isNegNan(real x)
{
   return isnan(x) && signbit(x);
}

// for completeness

int sign(int x)
{
  return x>0 ? 1 : x<0 ? -1 : x;
}

long sign(long x)
{
  return x>0 ? 1 : x<0 ? -1 : x;
}