7

Is it possible to store negative zero in imaginary part of C99 complex float?

How I should statically initialize complex constants with signed imaginary part?

I have a small example, but I can't understand, why a and c are same and why -std=c99 changes results.

$ cat zero1.c
int main() {
    float _Complex a;a = 0.0 + (__extension__ 0.0iF);
    float _Complex b;b = 0.0 + (__extension__ -0.0iF);
    float _Complex c;c = -0.0 + (__extension__ 0.0iF);
    float _Complex d;d = -0.0 + (__extension__ -0.0iF);
    printf("a= 0x%016llx\n", *(long long*)(&a));
    printf("b= 0x%016llx\n", *(long long*)(&b));
    printf("c= 0x%016llx\n", *(long long*)(&c));
    printf("d= 0x%016llx\n", *(long long*)(&d));
}

$ gcc-4.5.2 -w -std=c99 zero1.c ; ./a.out
a= 0x0000000000000000
b= 0x0000000000000000
c= 0x0000000000000000
d= 0x0000000080000000

$ gcc-4.5.2 -w zero1.c ; ./a.out
a= 0x0000000000000000
b= 0x8000000000000000
c= 0x0000000000000000
d= 0x8000000080000000

Quotations from C99-TC3 and gcc manuals are welcome.

I cant find anything relevant in C99 (n1256.pdf) nor in http://www.knosof.co.uk/cbook/

apaderno
  • 28,547
  • 16
  • 75
  • 90
osgx
  • 90,338
  • 53
  • 357
  • 513
  • it is potentially relevant that without a -std= option gcc will operate as if you had specified -std=gnu89 or -std=gnu90 (they're the same thing) so that may be relevant here since the _Complex and _Imaginary types are from C99 – Spudd86 May 04 '11 at 21:10
  • Spudd86, no, the `gcc -std=gnu99` was the same as just `gcc`, as I remember – osgx May 05 '11 at 00:47

5 Answers5

3

If an implementation conforms to Annex G and implements the _Imaginary types, then the expression

b = 0.0 + (__extension__ -0.0iF)

is evaluated as (double)0.0 + (double _Imaginary)(-0.0i) according to the rules in G.5.2, and yields 0.0 - 0.0i.

If the implementation does not provide an _Imaginary type (which is allowed), or otherwise does not conform to Annex G (also allowed), then this expression is typically evaluated as:

  (double _Complex)(0.0 + 0.0i) + (double _complex)(0.0 - 0.0i)
= (double _Complex)((0.0 + 0.0) + (0.0 - 0.0)i)

Because 0.0 - 0.0 is positive zero in IEEE-754 default rounding, the signbit is lost.

Moral of the story: if you care about the sign of zero, don't use arithmetic in complex initializers. Since you're using GCC, you can do this instead:

__real__ c =  0.0f;
__imag__ c = -0.0f;

In my experience, this works back to at least gcc-4.0 or so (maybe farther).

As to why the behavior was triggered by -std=c99, my best guess is the following: the version of GCC that you're using implements an _Imaginary type that is not fully conformant with C99; when you specify -std=c99, support for _Imaginary is turned off, and you fall back on a conformant _Complex implementation that works as I described above. This is only a guess however; if you're really curious, I would encourage you to file a bug and see what the maintainers say. Actually, I would encourage you to file a bug anyway. Always file a bug.

Stephen Canon
  • 103,815
  • 19
  • 183
  • 269
3

Does _Imaginary_I * -0.0 work better than (__extension__ -0.0iF)?

The upcoming C1x standard will include the CMPLX macros, which “act as if the implementation supported imaginary types and the definitions were:
#define CMPLX(x, y) ((double complex)((double)(x) + _Imaginary_I * (double)(y))).”

See N1570, §7.3.9.3.

J. C. Salomon
  • 4,143
  • 2
  • 29
  • 38
1

It has to do with IEEE floating-point behavior as specified by the ISO C standard, which is more strict about negative zeros. Compiling in a more native form allows the compiler to optimize, and thus disregard stricter rules, about such things.

Addendum

I don't remember the details, but this is discussed in depth in Appendix F of the ISO C99 standard. PDF available at: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf .

Retracted

Sorry, I remembered wrong. The ISO C standard apparently does not dictate anything about negative zeros. It is probably has to do with how strict the IEEE FP operations are.

David R Tribble
  • 11,918
  • 5
  • 42
  • 52
  • Annex F of n1256 says nothing about negative zero in imaginary part of complex. – osgx May 04 '11 at 14:59
  • Sorry, I thought I remembered that it did. It's probably more a operation of strict IEEE behavior, then. – David R Tribble May 04 '11 at 15:10
  • Loadmaster, is there a freely available ieee754 pdf (e.g. draft)? – osgx May 04 '11 at 15:41
  • Sorry, I can't locate one. Someone with more powerful Google-fu than me probably can. – David R Tribble May 04 '11 at 16:02
  • IEEE 754 doesn't help because it doesn't cover complex values nor complex arithmetic. – janneb May 04 '11 at 16:39
  • 1
    Correct; IEEE-754 does not cover complex arithmetic (neither the original 1985 standard nor the revised 2008 standard). C99 Annex G (especially pertaining to the sign of zero) is based mostly around the ideas set forth in Kahan's paper: "Branch Cuts in the Complex Plane; or: Much Ado About Nothing's Sign Bit" (not available online as far as I am aware, sadly). Annex G is, however, not normative. – Stephen Canon May 04 '11 at 17:43
  • See for the text of 754-1985 and a draft (version 1.2.5) of 754-2008; there were some important changes made before the final version (1.9.0) though. – J. C. Salomon May 05 '11 at 17:57
1

Using

gcc version 4.7.0 20110504 (experimental) (GCC)

on Target: x86_64-unknown-linux-gnu

Both with and without -std=c99 prints

a= 0x0000000000000000
b= 0x8000000000000000
c= 0x0000000000000000
d= 0x8000000080000000

So I suspect this is a bug in 4.5.2 that has since been fixed. Perhaps a search in the GCC bugzilla and/or mailing lists will turn up something?

EDIT The remaining mystery is where does the sign of the real part of c go?

EDIT2 The sign of the real part of c is lost because the initializer contains an addition so the expression is evaluated as type float _Complex, hence

-0.0 + (__extension__ 0.0iF) = (-0.0, 0.0) + (0.0, 0.0) = (0.0, 0.0)

as -0.0 + 0.0 is 0.0, unless the rounding mode is round towards negative infinity.

Hence, to generate the literal (-0, 0) you need something like

float _Complex c2 = -(0.0 - (__extension__ 0.0iF));

See also PR 24581

janneb
  • 36,249
  • 2
  • 81
  • 97
  • no, *main mystery* is to find normative documents about right (**standard**) **behavior**. Also, links to gcc bus/maillists are welcome, both for changes in 4.7.0 and in ~4.5 (gcc < 4.4 didn't save sign of imaginary part, so there must be several patches). I did some (little) search in maillist, but find nothing. – osgx May 04 '11 at 15:39
  • According to C99 (n1256 Annex G) complex values have the same semantics for signed zeros as the real constituent parts in isolation. Thus it seems clear it's a bug in GCC. – janneb May 04 '11 at 15:47
  • 1
    ... that is, Annex G contains nothing which would suggest that the implementation is allowed to handle signed zeros differently in complex variables than in real variables. – janneb May 04 '11 at 15:48
  • After some further digging, there is no bug in current GCC. I've edited my post accordingly. – janneb May 04 '11 at 17:15
  • what is the right way to initialize _complex variable with `(+0 -0.0i)`? – osgx May 05 '11 at 11:10
0

From Annex J (Portability Issues):

J.1 Unspecified behavior

  1. The following are unspecified:
    […]
    — Whether […] a negative zero becomes a normal zero when stored in an object (6.2.6.2).

This is going to make what you want just that more complicated.

J. C. Salomon
  • 4,143
  • 2
  • 29
  • 38
  • I tested my compilers (5+) and every compiler is able to store a plain +0 or -0 in any part of complex, both imaginary and real. So this, 6.2.6.2 is for optional support of signed zero, and this support is here in every tested compiler. – osgx May 05 '11 at 21:58