The Math.Sign function with x = -0.

Merged to the master branch
User avatar
Robert
Posts: 1024
Joined: Sat Sep 28, 2013 11:04 am
Location: Edinburgh, Scotland

Re: The Math.Sign function with x = -0.

Post by Robert »

Josef Templ wrote:For me this (ie Strings.RealToString) is the same kind of bug as in Math.Sign. If they use it (ie -0.), they will expect to see what they get.
I am happy to discuss this, but not here. I think it might distract from discussing the contents of System/Math here.
Most people will never notice the difference (between -0. & +0.) because they don't use negative zeros.
If they have ever multiplied -5. by zero they have used minus zero, even if they did not realise it!

I shall summarise this comment (maybe incorrectly) as "Do we need to bother about -0.?", and initially give a political answer.
The experts who devised the IEEE standard arithmetic, and developed the floating point chips, clearly believed it was worth considerable effort to support. As we use this arithmetic I think we should also try to support it, where practical, unless we also become expert and give reasoned arguments why it is inappropriate.

If you want a technical answer I recommend starting by reading the papers by Professor Kahan (https://en.wikipedia.org/wiki/William_Kahan):
1 - "Much Ado About Nothing's Sign Bit" https://people.freebsd.org/~das/kahan86branch.pdf
2 - "How Java's Floating-Point Hurts Everyone Everywhere" https://people.eecs.berkeley.edu/~wkahan/JAVAhurt.pdf

I will probably post an actual example using BlackBox later.

Back to the topic of this thread; the value of the provided functions at x = -0.0. The first reference above gives clear guidance:
(That is why, in IEEE style arithmetic, s - t and - (t-s) are numerically equal but not necessarily indistinguishable.) Implementations of elementary transcendental functions like sin(z) and tan(z) and their inverses and hyperbolic analogs, though not specified by the IEEE standards, are expected to follow similar rules; if f(0) = 0 < f '(0), then the implementation of f(z) is expected to reproduce the sign of z as well as its value at z = ±O.
So the functions Sinh, Tanh, ArcSinh, & ArcTanh are also all in error and need to be corrected.
User avatar
Josef Templ
Posts: 2047
Joined: Tue Sep 17, 2013 6:50 am

Re: The Math.Sign function with x = -0.

Post by Josef Templ »

Robert wrote:If they have ever multiplied -5. by zero they have used minus zero, even if they did not realise it!
This is convincing.
Strings.RealToString should not be changed.

- Josef
User avatar
Robert
Posts: 1024
Joined: Sat Sep 28, 2013 11:04 am
Location: Edinburgh, Scotland

Re: The Math.Sign function with x = -0.

Post by Robert »

Robert wrote:I will probably post an actual example using BlackBox later.
This example is "Borda's Mouthpiece", a classical two-dimensional fluid dynamics problem.

A pipe extends infinitely from the left of x = 0, and an incompressible fluid flows into an infinite reservoir. Where are the flow lines?

One solution approach would be to use a large finite element package, do a LOT of arithmetic, and find an approximate answer of hard to establish accuracy.

This approach is to use some sophisticated mathematics (complex conformal maps) to transform the problem to one with a very easy solution. This can then be plotted, exactly, using a very short and simple application procedure to evaluate a simple mathematical expression.

See the first picture below.

However this only works if the complex number library has been carefully written with regard to the sign of zero. Almost all complex number libraries (including all FORTRAN ones) mishandle it, leading to the second picture below.

Even if the application programmer notices this error (which might be well hidden in some applications) it is very hard and inconvenient to code around it at the top application level.

In summary: correct use of zero's sign can, in some circumstances, be a very significant help to the application programmer.

For more details, refer to Kahan's paper: "How Java's Floating-Point Hurts Everyone Everywhere" https://people.eecs.berkeley.edu/~wkahan/JAVAhurt.pdf (or ask me!).
Correct use of +/- 0
Correct use of +/- 0
BordaGood.png (5.81 KiB) Viewed 4495 times
Careless use of +/- 0
Careless use of +/- 0
BordaBad.png (5.9 KiB) Viewed 4495 times
User avatar
Josef Templ
Posts: 2047
Joined: Tue Sep 17, 2013 6:50 am

Re: The Math.Sign function with x = -0.

Post by Josef Templ »

Very nice example.

Does it mean that the problem is solved by our planned fix for Math.Sign(), Sinh(), TanH(), etc.
or do you also need to change the NaN behavior for that?

To me it looks like NaN is not the problem here, right?

- Josef
User avatar
Robert
Posts: 1024
Joined: Sat Sep 28, 2013 11:04 am
Location: Edinburgh, Scotland

Re: The Math.Sign function with x = -0.

Post by Robert »

Josef Templ wrote:Very nice example. To me it looks like NaN is not the problem here, right?
Correct; there are no NaNs used.

It was just meant to be an example of using signed zero in general; none of the functions you mentioned are used.

Actually the only two functions used by the application programmer are Ln & Sqrt; internally complex Ln also uses real ArcTan2.
(Internally my complex Sqrt uses an IsNeg function; this could be simplified if CopySign became available.)

In fact the problem above is actually caused by the '+' function. I'll post an explanation later.
User avatar
Robert
Posts: 1024
Joined: Sat Sep 28, 2013 11:04 am
Location: Edinburgh, Scotland

Re: The Math.Sign function with x = -0.

Post by Robert »

Robert wrote:
Josef Templ wrote:Very nice example. To me it looks like NaN is not the problem here, right?
In fact the problem above is actually caused by the '+' function. I'll post an explanation later.
I'm writing this mainly for my own benefit.

What are these mysterious signed zeros?

I've seen explanations that suggest they are used to approximate non-zero numbers too small to otherwise represent. I don't like that explanation; numbers are just numbers, with neither resolution or accuracy. But, of course, computer arithmetic is different to mathematical arithmetic as in includes rounding and other artifacts.

Number formats have an associated resolution. Numbers, may, in a given context have an associated accuracy specific to that context. The numbers INTEGER 3, SHORTREAL 3.0, and REAL 3.0 all are the same; they don't have different accuracies, three is simply three.

The way to think about +0.0 & −0.0 are as packages that both contain the number 0.0, and an additional bit of information (or dis-information) that the application programmer may, optionally, exploit.


What are they used for?

When we use a function we are often also interested in closely related functions such as its inverse or reciprocal. Sometimes one of these functions has a discontinuity at x = 0. The limit of F(x) as x tends to 0 from below may exist, call it F−. The limit of F(x) as x tends to 0 from above may also exist, call it F+. How do we define F(0)? The mathematician has to make a choice; common choices are to leave F(0) undefined, define it as F−, define it as F+, define it as (F− + F+) / 2, or ...

But the programmer has a further choice; their subroutine can return F− when asked for the limit from below, and return F+ when asked for the limit from above. These requests can be signalled by calling F(x) with x = −0 or +0 respectively.

A simple example; 1 / (−0) returns −INF as that is the limit of 1 / x as x tends up to 0, and the same for 1 / (+0) as x tends to 0 from above.
−0 & 0 are not different numbers, they are ways of asking for different limits at the same point, namely at zero.


Examples of this process with real functions seem to be rare; it is much more common with complex functions. But, in programming, complex functions are built with real functions, so it has to be built into the real arithmetic system.


A (simple) complex example

Consider squaring and then square-rooting z, with z somewhere in the right half plane (ie with positive real part). (Complex) squaring involves squaring the magnitude and doubling the argument (angle) of z; square-rooting involves square-rooting the magnitude and halving the angle; so it brings us back to z.

But for z on the imaginary axis doubling the angle gets us onto the negative real axis, and essentially we do not know if the square-root of −1 is +i or −i. But if we know we are approaching the axis from above (real part of z is +0) continuity says we want the answer +i, and if we are approaching from below (real part of z is −0) continuity says we want the answer −i. This enables us to extend the squaring & square-root functions to include the imaginary axis, not just the strictly right half plane. This is both useful and convenient as the Borda example shows.


Radial flow - pre-Borda

In this picture imagine water in the right half plane, air in the left, both under some pressure, and a tiny hole at the origin. Both fluids flow radially into this sink hole - the water flow lines are shown. Now insert two barriers (shown in blue) with air on the left and water on the right. They do not affect the flow.

(This picture is easy to plot!)−
Radial flow
Radial flow
PreBorda.png (4.69 KiB) Viewed 4476 times
Flow lines, Borda's mouthpiece

Distort this picture by replacing z by
F(z) ≡ 1 + g(z) + Ln (g(z)) where g(z) ≡ z² + z √(1 + z²).
Borda's mouthpiece
Borda's mouthpiece
Borda.png (5.25 KiB) Viewed 4476 times
This rotates the barriers to be horizontal (the edges of a pipe extending infinitely to the left), and moves the sink hole also to ( −∞ + 0 i).

Since F is a conformal map (angle preserving) the distorted picture also represents flow lines, now the flow coming out of the pipe into an infinite reservoir.

It is extremely convenient to draw not just the flow lines strictly within the flow, but also those defining the edges. These are the vertical grey lines in the first picture, those on the imaginary axis. These require √(z²) to be computed correctly for both z = +i & z = −i, as in the simple complex example. Thus signed zeros are used to indicate the direction we need the √ function to be continuous.


Why do most libraries, and some languages, fail for this example?

The explanation lies in about the simplest part of G, namely the expression √(1 + z²).

Consider, as examples, the red and green crosses in the pictures.

For them we (should) have (red then green)

Code: Select all

     z:             0  −  2 i                    0  +  2 i
    z²:           − 4  −  0 i                  − 4  +  0 i 
  (1 + z²):       − 3  −  0 i                  − 3  +  0 i 
√(1 + z²).         0  −  1.73205 i           0  +  1.73205 i
However many libraries cannot add the real number 1! Instead they type cast it into the complex number (1 + 0 i) first, then do a complex add. This is not only slower, it is incorrect. Since 0 + (−0) = 0 + (+0) = 0 both spots have (1 + z²) computed as − 4 + 0 i, and the flow lines adjacent to the bottom pipe are misplotted on the top pipe.


Why does only zero have this continuity hint?

1 - The most common elementary functions in sub-routine libraries are things like Ln, ArcSin, ArcCos, ArcTan, ArcCsc, ArcSec, ArcCot, ArcSinh, ArcCosh, ArcTanh, ArcSch, ArcSech, ArcCoth, and these all have their branch cuts (places of discontinuity) defined on the complex axes. So the direction of continuity requested can be signalled by the sign of a zero real part, or the sign of a zero imaginary part.

Functions with branch cuts elsewhere are simply very rare.

2 - If one wished to exploit this technique on a function H (x) that had is discontiuity at, say, 5 one could simply define it in terms of another function H'(x) ≡ H(x − 5), and now H' does have its discontinuity at zero; problem solved.
User avatar
DGDanforth
Posts: 1061
Joined: Tue Sep 17, 2013 1:16 am
Location: Palo Alto, California, USA
Contact:

Re: The Math.Sign function with x = -0.

Post by DGDanforth »

I am (mostly) with Robert on this issue.
Conceptually there is no such thing as -0. There is only one zero, 0.
The sign of zero is 0.

Implementation should not affect that concept.
Post Reply