开发者

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= 0x80000000开发者_开发技巧80000000

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/


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.


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.


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.


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


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜