Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C99: can imaginary part of complex be a negative zero

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/

like image 814
osgx Avatar asked May 04 '11 14:05

osgx


3 Answers

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.

like image 156
Stephen Canon Avatar answered Sep 28 '22 01:09

Stephen Canon


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.

like image 30
J. C. Salomon Avatar answered Sep 28 '22 02:09

J. C. Salomon


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.

like image 44
David R Tribble Avatar answered Sep 28 '22 03:09

David R Tribble