mpmath icon indicating copy to clipboard operation
mpmath copied to clipboard

Signed zero

Open fredrik-johansson opened this issue 16 years ago • 14 comments

bc.. Should negative zero (-0) be supported? It is virtually supported already; the object 'fnzero' is defined in libmpf.py (though not used anywhere). It just needs to be supported in standard operations, and elsewhere "x == fzero" needs to be replaced with something else so that -0 is not forwarded to places where it shouldn't go.

I'm not personally convinced that signed zero is worth the trouble, but it would be useful for allowing extended compatibility with IEEE 754 semantics, for continuity along branch cuts, etc, and could resolve some issues in interval arithmetic (although it would also cause some more complication).

p. Original issue for "#167":https://github.com/fredrik-johansson/mpmath/issues/167: "http://code.google.com/p/mpmath/issues/detail?id=127":http://code.google.com/p/mpmath/issues/detail?id=127

p. Original author: "https://code.google.com/u/111502149103757882156/":https://code.google.com/u/111502149103757882156/

p. Original owner: "https://code.google.com/u/111502149103757882156/":https://code.google.com/u/111502149103757882156/

fredrik-johansson avatar Feb 04 '09 06:02 fredrik-johansson

bc.. I don't know whether it's useful. It's nice to have. (And when you have the choose between returning +0 or -0, it's trivial to use it.)

p. Original comment: "http://code.google.com/p/mpmath/issues/detail?id=127#c1":http://code.google.com/p/mpmath/issues/detail?id=127#c1

p. Original author: "https://code.google.com/u/[email protected]/":https://code.google.com/u/[email protected]/

vks avatar Feb 04 '09 14:02 vks

@fredrik-johansson, I'm not sure why this issue was closed. Could you please explain why? Do you think there is support for signed zero?

I don't think it's even can be created with a public API:

In [100]: mpmath.mpf(0.0)
Out[100]: mpf('0.0')

In [101]: mpmath.mpf(-0.0)
Out[101]: mpf('0.0')

In [102]: mpmath.mpf((0,0,0,0))
Out[102]: mpf('0.0')

In [103]: mpmath.mpf((1,0,0,0))
Out[103]: mpf('0.0')

In [104]: mpmath.mpf((1,0,0,0))._mpf_
Out[104]: (0, 0, 0, 0)

See https://github.com/sympy/sympy/issues/25486, where it's a real issue.

skirpichev avatar Aug 10 '23 07:08 skirpichev

See sympy/sympy#25486, where it's a real issue.

I would say that the problem there is caused by signed zeros being a problem rather than a solution to anything. Of course there is potential benefit in mpmath being more compatible with IEEE 754 but in general it would be better if signed zeros did not really exist. I can see why the (hardware-focused) IEEE 754 designers included it because it's just so easy when you have that unused sign bit sitting there but really we would all be better off if signed zeros had just been disallowed.

IEEE 754 signed zeros are awkward for various reasons e.g. you have "negative zero" but there is no "positive zero" to be distinguished from "zero zero". You can't really use signed zeros for branch cuts if you can't distinguish the sign in both directions. For mpmath it would probably be more useful to be able to distinguish "exact zero" from "small and possibly zero" before considering signed zeros.

Negative zeros are used in IEEE754 to represent underflow which is not necessarily needed in mpmath since exponents are unbounded although in many cases it would be useful to be able to bound exponents to avoid things that are impossibly slow like mpmath.factorial(100**100)**(100**1000000).

Perhaps in relation to the SymPy issue this should be considered a CPython bug:

In [12]: 1/(-2+0j)
Out[12]: (-0.5-0j)

In [13]: 1/(-2-0j)
Out[13]: (-0.5-0j)

In [14]: 1/(-2+0j).conjugate()
Out[14]: (-0.5-0j)

In [15]: (1/(-2+0j)).conjugate()
Out[15]: (-0.5+0j)

I'm not sure if IEEE 754 has anything to say about this (I don't think it covers complex numbers at all).

oscarbenjamin avatar Aug 10 '23 09:08 oscarbenjamin

there is potential benefit in mpmath being more compatible with IEEE 754

And within different contexts (fp vs mp).

You can't really use signed zeros for branch cuts if you can't distinguish the sign in both directions.

I don't think so. In practice we have continuous (in some direction) function and this shouldn't matter.

Perhaps in relation to the SymPy issue this should be considered a CPython bug

Yes, I feel there is a CPython bug as well. It's impossible to create a signed imaginary component without a string if we have a real component:

In [1]: +0.0
Out[1]: 0.0

In [2]: -0.0
Out[2]: -0.0

In [3]: 0j
Out[3]: 0j

In [4]: -0j
Out[4]: (-0-0j)

In [5]: 0.5+0.0j
Out[5]: (0.5+0j)

In [6]: 0.5-0.0j
Out[6]: (0.5+0j)

In [7]: complex('0.5+0.0j')
Out[7]: (0.5+0j)

In [8]: complex('0.5-0.0j')
Out[8]: (0.5-0j)

skirpichev avatar Aug 10 '23 09:08 skirpichev

FYI: complex also accepts two arguments, the real and imaginay parts:

In [1]: complex(1, -0.0)
Out[1]: (1-0j)

WarrenWeckesser avatar Aug 10 '23 11:08 WarrenWeckesser

In [12]: 1/(-2+0j)
Out[12]: (-0.5-0j)

In [13]: 1/(-2-0j)
Out[13]: (-0.5-0j)

This is not a bug.

Complex division does not just multiply by conjugates. It does scaling first, to avoid intermediate overflow.

(1,0) / (-2,±0) = -0.5 * (1,0)/(1,∓0) * (1,±0)/(1,±0)

num = -0.5 * (1,0) * (1,±0) = -0.5 * (1,0±0) = -0.5 * (1,0) = (-0.5,-0) den = (1,∓0) * (1,±0) = 1^2 + 0^2 = 1

Of course, we can scale numerator differently.

num = 0.5 * (-1,−0) * (1,±0) = 0.5 * (-1,−0∓0) = 0.5 * (-1,∓0) = (-0.5,∓0)

It cost extra logic to make sure numerator imag part is negative. I don't know if this is worth it.

achan001 avatar Feb 02 '24 23:02 achan001

This is not a bug.

That depends... Most people, probably, recognize -2-0j as a rectangular notation (i.e. as an equivalent of complex(-2, -0.0)), while in the Python this actually means complex(-2, 0) - complex(0.0,0.0) = complex(-2, 0).

See https://discuss.python.org/t/33433

skirpichev avatar Feb 02 '24 23:02 skirpichev

The issue is numerator 1 get promoted to complex (1+0j) Signed zero does not mean exactly zero, just infinitesimally small.

(1+0j) ≡ 1∠(ε), phase angle not exactly zero, but tiny non-negative ε -(1+0j) ≡ 1∠(ε - pi), phase angle not exactly -pi either

(1+0j) / (-2-0j) ≡ (1∠(ε1)) / (2∠(ε2-pi)) ≡ 0.5 ∠((ε1-ε2) + pi)

Result is ambiguous, if sign(ε1-ε2) = 1, phase angle flip to -pi

(1+0j) / (-2-0j) ≡ 0.5 ∠(±pi) = -0.5 ± 0j

The other case is more clear-cut

(1+0j) / (-2+0j) ≡ (1∠(ε1)) / (2∠(pi-ε2)) ≡ 0.5 ∠((ε1+ε2) - pi)

(1+0j) / (-2+0j) = 0.5 ∠(-pi) = -0.5 - 0j

achan001 avatar Feb 02 '24 23:02 achan001

The issue is numerator 1 get promoted to complex (1+0j)

This is the reason we have (1±0j) * 1 = (1+0j)

achan001 avatar Feb 03 '24 16:02 achan001

This is the reason we have (1±0j) * 1 = (1+0j)

No. The reason is that 1-0j=complex(1,0)-complex(0,0)=1+0j. To get complex(1, -0.0) with imaginary literals you should use cumbersome syntax -(-1+0j). See mentioned discussion.

skirpichev avatar Feb 04 '24 00:02 skirpichev

I meant literally (1-0j), whatever ways or syntax to enter it.

>>> -(-1+0j)
(1-0j)
>>> _ * 1
(1+0j)

achan001 avatar Feb 04 '24 01:02 achan001

Here is a draft pr: #768

skirpichev avatar Mar 30 '24 14:03 skirpichev

Diofant tests on the above patch: https://github.com/diofant/diofant/pull/1391 (PASS)

skirpichev avatar Apr 06 '24 03:04 skirpichev

Ok, lets consider some possibilities.

  1. We could add IEEE 754-like signed zeros. This will require incompatible changes, see e.g. https://github.com/mpmath/mpmath/issues/786.
  2. Without signed zeros we could simplify the mpf tuple to just (man, exp), where mantissa man will be signed. This type will be like GMP's mpf, but with unbounded exponent.
  3. We could introduce new context with mpfr-like type (which optionally could be replaced by gmpy2's mpfr), that will follow IEEE 754.

skirpichev avatar Apr 24 '24 15:04 skirpichev