-
it looks like making sure the macro is defined is not up to GCC, and it sounds like the { compiler{gcc-v8, gcc-v9, gcc-v10} , platform{linux/x86-32bit, linux/mips32r2be-32bit, linux/ppc32be-32bit, linux/ppc64be-32bit} combination doesn't support it.
- compiling complex ... complex.c: In function 'hh2':
complex.c:72:12: warning: implicit declaration of function 'CMPLX' [-Wimplicit-function-declaration]
72 | cplx = CMPLX(real, imag);
| ^~~~~
complex.c:74:44: warning: implicit declaration of function 'cargf' [-Wimplicit-function-declaration]
74 | printf("Phase Angle = %.1f radians\n", cargf(cplx));
| ^~~~~
complex.c:74:44: warning: incompatible implicit declaration of built-in function 'cargf'
complex.c:11:1: note: include '<complex.h>' or provide a declaration of 'cargf'
10 | #include <stdio.h>
+++ |+#include <complex.h>
11 |
But that's really weird :-//
-std=c99 -Wall -Wextra
-std=c11 -Wall -Wextra
Tried c99/c11, same result.
#include <complex.h>
#include <tgmath.h>
#include <math.h>
#include <stdio.h>
typedef _Complex float zf_t;
typedef _Complex double zd_t;
typedef _Complex long double zld_t;
void hh2()
{
double real;
double imag;
zd_t cplx;
real = 1.3;
imag = 4.9;
cplx = CMPLX(real, imag);
printf("Phase Angle = %.1f radians\n", cargf(cplx));
}
...
-
Native or cross compile?
It works on my Fedora 34 box with -m32 producing a valid i686 binary.
-
Native or cross compile?
Native on all the mentioned platforms.
-
Which C library and C library version?
CMPLX() is C11, not C99, so use -std=c11 only.
Try including <math.h> before <complex.h>. Because of the interdependencies, that might matter even though it shouldn't.
-
Which C library and C library version?
Which Linux distribution as well, there are not many that are natively 32 bit any longer, at least on Intel.
-
Which C library and C library version?
/usr/lib/libc.{a,so} -> glibc
/usr/lib/libm.{a,so} -> glibc
glibc-v2.32-r5
libtool-v2.4.6-r6 (just recompiled)
I rebuilt libtool, and gcc-v10 with the last patch-set
CMPLX() is C11, not C99, so use -std=c11 only.
ok
Try including <math.h> before <complex.h>. Because of the interdependencies, that might matter even though it shouldn't.
I commented "tgmath" because it causes some issues
#include <math.h>
#include <complex.h>
//#include <tgmath.h>
#include <stdio.h>
now Gcc-v10 compiles, even if ...
sizeof(_Complex float) = 8
sizeof(_Complex double) = 16
sizeof(_Complex long double) = 24
I = 0.0+1.0i
I * I = -1.0+0.0i
pow(I, 0) = 1.0+0.0i
pow(I, 1) = 0.0+0.0i
pow(I, 2) = 0.0+0.0i
pow(I, 3) = 0.0+0.0i
exp(I*PI) = 1.0+0.0i
(1+2i)*(1-2i) = 5.0+0.0i
Phase Angle = 1.3 radians
(tested on Linux/x86-32bit)
It seems something is bad: pow(I,2) shouldn't be zero :-//
typedef _Complex float zf_t;
typedef _Complex double zd_t;
typedef _Complex long double zld_t;
void hh1()
{
zd_t z1;
zd_t z2;
zd_t z3;
zd_t z4;
zd_t z5;
double PI;
z1 = I; // imaginary unit
printf("I = %.1f%+.1fi\n", creal(z1), cimag(z1));
z1 = I * I; // imaginary unit squared
printf("I * I = %.1f%+.1fi\n", creal(z1), cimag(z1));
z2 = 1 * I;
z2 = pow(z2, 0);
printf("pow(I, 0) = %.1f%+.1fi\n", creal(z2), cimag(z2));
z2 = 1 * I;
z2 = pow(z2, 1);
printf("pow(I, 1) = %.1f%+.1fi\n", creal(z2), cimag(z2));
z2 = 1 * I;
z2 = pow(z2, 2); // imaginary unit squared
printf("pow(I, 2) = %.1f%+.1fi\n", creal(z2), cimag(z2));
z2 = 1 * I;
z2 = pow(z2, 3);
printf("pow(I, 3) = %.1f%+.1fi\n", creal(z2), cimag(z2));
PI = acos(-1);
z3 = exp(I * PI); // Euler's formula
printf("exp(I*PI) = %.1f%+.1fi\n", creal(z3), cimag(z3));
z4 = 1+2*I;
z5 = 1-2*I; // conjugates
printf("(1+2i)*(1-2i) = %.1f%+.1fi\n", creal(z4*z5), cimag(z4*z5));
}
edit:
So, there was something wrong with libtool! You have to be careful when you cook gcc >=v10
-
pow(I, 0) = 1.0+0.0i: correct
pow(I, 1) = 0.0+0.0i: wrong
pow(I, 2) = 0.0+0.0i: wrong
pow(I, 3) = 0.0+0.0i: wrong
with
#include <math.h>
#include <complex.h>
#include <tgmath.h> // do I have to comment this?!?
#include <stdio.h>
there is this issue
- compiling cplx ... In file included from cplx.c:9:
cplx.c: In function 'hh1':
cplx.c:55:10: error: duplicate type-generic parameter type for function argument 2 of '__builtin_tgmath'
55 | z2 = pow(z2, 0);
| ^~~
cplx.c:59:10: error: duplicate type-generic parameter type for function argument 2 of '__builtin_tgmath'
59 | z2 = pow(z2, 1);
| ^~~
cplx.c:63:10: error: duplicate type-generic parameter type for function argument 2 of '__builtin_tgmath'
63 | z2 = pow(z2, 2); // imaginary unit squared
| ^~~
cplx.c:67:10: error: duplicate type-generic parameter type for function argument 2 of '__builtin_tgmath'
67 | z2 = pow(z2, 3);
| ^~~
cplx.c:70:10: error: duplicate type-generic parameter type for function argument 2 of '__builtin_tgmath'
70 | PI = acos(-1);
| ^~~~
cplx.c:72:10: error: duplicate type-generic parameter type for function argument 2 of '__builtin_tgmath'
72 | z3 = exp(I * PI); // Euler's formula
| ^~~
make: *** [Makefile:77: obj/macmini-intel-i686/cplx.o] Error 1
-
Which Linux distribution as well, there are not many that are natively 32 bit any longer, at least on Intel.
Gentoo/Catalyst, so they are autobuilt stage0-4
I am going to also build for ARM(1)-64bit and HPPA2-32bit
(1) ARMv7-gnueabihf (hardware fp)
-
Yes, this macro appeared in C11, it was not defined in C99, so that's the minimum revision you need to use.
With that said - I tried with the latest GCC (11.2), and the macro is indeed not supported. Even with std=c2x.
So out of curiosity, I did a search in all header files from all GCC compilers I have on my workstation, with a variety of versions. None have the 'CMPLX' macro defined in any of their header files.
I have no explanation for this, apart from just a missing macro in GCC's complex.h. Didn't check for Clang yet. Reading the standard (both C11 and the draft of C2x), there is no mention of it being possibly optional.
Note that the suggested way of achieving the same (while actually being more readable IMHO, since closer to math notation) before this macro appeared was to use the 'I' macro from complex.h (which is normally defined as '_Complex_I'), such as:
double complex x = 1 + 2*I;
which I tend to prefer anyway.
-
With that said - I tried with the latest GCC (11.2), and the macro is indeed not supported. Even with std=c2x.
complex.h is part of glibc, rather than GCC.
-
Works for me :-//
$ grep -C2 CMPLX /usr/include/complex.h
#if defined __USE_ISOC11 && __GNUC_PREREQ (4, 7)
/* Macros to expand into expression of specified complex type. */
# define CMPLX(x, y) __builtin_complex ((double) (x), (double) (y))
# define CMPLXF(x, y) __builtin_complex ((float) (x), (float) (y))
# define CMPLXL(x, y) __builtin_complex ((long double) (x), (long double) (y))
#endif
#if __HAVE_FLOAT16 && __GLIBC_USE (IEC_60559_TYPES_EXT)
# define CMPLXF16(x, y) __builtin_complex ((_Float16) (x), (_Float16) (y))
#endif
#if __HAVE_FLOAT32 && __GLIBC_USE (IEC_60559_TYPES_EXT)
# define CMPLXF32(x, y) __builtin_complex ((_Float32) (x), (_Float32) (y))
#endif
#if __HAVE_FLOAT64 && __GLIBC_USE (IEC_60559_TYPES_EXT)
# define CMPLXF64(x, y) __builtin_complex ((_Float64) (x), (_Float64) (y))
#endif
#if __HAVE_FLOAT128 && __GLIBC_USE (IEC_60559_TYPES_EXT)
# define CMPLXF128(x, y) __builtin_complex ((_Float128) (x), (_Float128) (y))
#endif
#if __HAVE_FLOAT32X && __GLIBC_USE (IEC_60559_TYPES_EXT)
# define CMPLXF32X(x, y) __builtin_complex ((_Float32x) (x), (_Float32x) (y))
#endif
#if __HAVE_FLOAT64X && __GLIBC_USE (IEC_60559_TYPES_EXT)
# define CMPLXF64X(x, y) __builtin_complex ((_Float64x) (x), (_Float64x) (y))
#endif
#if __HAVE_FLOAT128X && __GLIBC_USE (IEC_60559_TYPES_EXT)
# define CMPLXF128X(x, y) \
__builtin_complex ((_Float128x) (x), (_Float128x) (y))
#endif
I suppose you could use __builtin_complex as a substitute :-DD
With that said - I tried with the latest GCC (11.2), and the macro is indeed not supported. Even with std=c2x.
complex.h is part of glibc, rather than GCC.
Yep, that's the case on my system too.
-
pow(I, 0) = 1.0+0.0i: correct
pow(I, 1) = 0.0+0.0i: wrong
pow(I, 2) = 0.0+0.0i: wrong
pow(I, 3) = 0.0+0.0i: wrong
If <tgmath.h> is not included the above results are exactly what expected, a _Complex value converted to regular float or double simple loses the imaginary part - so 0 is what the (double) pow() sees.
Along NominalAnimal lines, I would suggest to try including only <tgmath.h>: it already includes both <math.h> and <complex.h>.
Correctly written includes should be idempotent, but the matter here is complex...
EtA: tgmath.h defines the type generic math library functions and macros - a basic form of overloading that can now (>=C11) be written in standard C using _Generic.
So, complex.h will contain the complex specific stuff and math.h the real specific ones.
With tgmath.h generic pow calls will be replaced with the right one (powf, pow, powl, cpowf, cpow, cpowl) according to the arguments.
-
Along NominalAnimal lines, I would suggest to try including only <tgmath.h>: it already includes both <math.h> and <complex.h>.
//#include <math.h>
//#include <complex.h>
#include <tgmath.h>
#include <stdio.h>
typedef _Complex float zf_t;
typedef _Complex double zd_t;
typedef _Complex long double zld_t;
void hh1()
{
zd_t z1;
zd_t z2;
zd_t z3;
zd_t z4;
zd_t z5;
double PI;
z1 = I; // imaginary unit
printf("I = %.1f%+.1fi\n", creal(z1), cimag(z1));
z1 = I * I; // imaginary unit squared
printf("I * I = %.1f%+.1fi\n", creal(z1), cimag(z1));
z2 = 1 * I;
z2 = pow(z2, 0);
printf("pow(I, 0) = %.1f%+.1fi\n", creal(z2), cimag(z2));
z2 = 1 * I;
z2 = pow(z2, 1);
printf("pow(I, 1) = %.1f%+.1fi\n", creal(z2), cimag(z2));
z2 = 1 * I;
z2 = pow(z2, 2); // imaginary unit squared
printf("pow(I, 2) = %.1f%+.1fi\n", creal(z2), cimag(z2));
z2 = 1 * I;
z2 = pow(z2, 3);
printf("pow(I, 3) = %.1f%+.1fi\n", creal(z2), cimag(z2));
PI = acos(-1);
z3 = exp(I * PI); // Euler's formula
printf("exp(I*PI) = %.1f%+.1fi\n", creal(z3), cimag(z3));
z4 = 1+2*I;
z5 = 1-2*I; // conjugates
printf("(1+2i)*(1-2i) = %.1f%+.1fi\n", creal(z4*z5), cimag(z4*z5));
}
- compiling cplx ... In file included from cplx.c:9:
cplx.c: In function 'hh1':
cplx.c:55:10: error: duplicate type-generic parameter type for function argument 2 of '__builtin_tgmath'
55 | z2 = pow(z2, 0);
| ^~~
cplx.c:59:10: error: duplicate type-generic parameter type for function argument 2 of '__builtin_tgmath'
59 | z2 = pow(z2, 1);
| ^~~
cplx.c:63:10: error: duplicate type-generic parameter type for function argument 2 of '__builtin_tgmath'
63 | z2 = pow(z2, 2); // imaginary unit squared
| ^~~
cplx.c:67:10: error: duplicate type-generic parameter type for function argument 2 of '__builtin_tgmath'
67 | z2 = pow(z2, 3);
| ^~~
cplx.c:70:10: error: duplicate type-generic parameter type for function argument 2 of '__builtin_tgmath'
70 | PI = acos(-1);
| ^~~~
cplx.c:72:10: error: duplicate type-generic parameter type for function argument 2 of '__builtin_tgmath'
72 | z3 = exp(I * PI); // Euler's formula
| ^~~
make: *** [Makefile:77: obj/macmini-intel-i686/cplx.o] Error 1
-
With that said - I tried with the latest GCC (11.2), and the macro is indeed not supported. Even with std=c2x.
complex.h is part of glibc, rather than GCC.
No. complex.h is part of the C std, and *is* provided by GCC.
Do not confuse particular file layouts/distributions on particular systems with what is standard C. Complex numbers are part of the C std when supported, glibc is just a particular implementation.
GCC absolutely does come with complex.h. The boggling part is why CMPLX is not defined in it, while basic support of complex numbers is. Actually, it looks like complex.h in GCC never got updated past C99 support, although I have no 100% certainty about it.
But this is certainly a bug. The std clearly states:
Implementations that define the macro __STDC_NO_COMPLEX__ need not provide this header* nor support any of its facilities.
*'complex.h'
On all configurations I tried (various GCC versions, various targets), this macro is NOT defined, 'complex.h' exists, but it does not define the CMPLX macro. It does define the rest of complex numbers support as far as I've seen though, at least the basic support - didn't thoroughly check whether anything in C11+ but not in C99, apart from this macro, wasn't there. There is no valid reason for that.
If complex numbers are not supported on a given platform/version/target, the '__STDC_NO_COMPLEX__ ' should just be defined, end of story. Otherwise, anything defined in the std for "Complex arithmetic" should be available. Something kinda fishy here. Looked for something related in GCC's bugzilla, but could not find anything very interesting, except a remotely related issue that apparently never got even assigned.
-
It's part of the standard library, like stdlib.h and a few others. It even makes sense.
Perhaps you are looking at some C++ headers which do appear to ship with GCC itself.
-
I'm used to using a number of different GCC versions for various targets, cross-compilation or native, and most of the time, those headers are part of the GCC's distribution. But digging a bit deeper, this would not be strictly a problem with GCC indeed.
For instance, if you're using GCC for ARM-Cortex M, the standard headers are usually provided by newlib.
If you're using other targets, it depends. But if you're not doing "native" compilation on Linux (or similar environment), there is often NOT a common 'include' path - each compiler has its own stuff for the std library. It may come from newlib, glibc or yet other implementations.
So it looks like the problem lies with newlib - and some other implementations - rather than with GCC itself. I confused the two as they often come as a bundle - unless again you're strictly on native Linux.
Why a number of implementations of the C srd lib, including newlib, do not define the CMPLX macro, is now the question.
But what the compiler implements, and what the std lib implements, for complex numbers, is still a bit involved. There's of course basic support for complex numbers directly in the compiler itself, not requiring the std lib, for easy to understand reasons. (Otherwise you wouldn't have access to arithmetic on complex numbers with common operators in C, for instance. C does not have operator overloading. =) )
-
collisions
gnat-gpl-v2016-r4 -> /usr/lib/gcc/i686-pc-linux-gnu/4.9.4/include/g++-v4/complex.h
gcc-v6.5.0-r2 -> /usr/lib/gcc/i686-pc-linux-gnu/6.5.0/include/g++-v6/tr1/complex.h
gcc-v6.5.0-r2 -> /usr/lib/gcc/i686-pc-linux-gnu/6.5.0/include/g++-v6/complex.h
gcc-v7.5.0-r1 -> /usr/lib/gcc/i686-pc-linux-gnu/7.5.0/include/g++-v7/tr1/complex.h
gcc-v7.5.0-r1 -> /usr/lib/gcc/i686-pc-linux-gnu/7.5.0/include/g++-v7/complex.h
gcc-v8.4.0-r1 -> /usr/lib/gcc/i686-pc-linux-gnu/8.4.0/include/g++-v8/tr1/complex.h
gcc-v8.4.0-r1 -> /usr/lib/gcc/i686-pc-linux-gnu/8.4.0/include/g++-v8/complex.h
gcc-v9.3.0-r1 -> /usr/lib/gcc/i686-pc-linux-gnu/9.3.0/include/g++-v9/tr1/complex.h
gcc-v9.3.0-r1 -> /usr/lib/gcc/i686-pc-linux-gnu/9.3.0/include/g++-v9/complex.h
gcc-v10.2.0-r3 -> /usr/lib/gcc/i686-pc-linux-gnu/10.2.0/include/g++-v10/tr1/complex.h
gcc-v10.2.0-r3 -> /usr/lib/gcc/i686-pc-linux-gnu/10.2.0/include/g++-v10/complex.h
glibc-2.32-r5 -> /usr/include/complex.h
clang-v11.0.0 -> /usr/lib/clang/11.0.0/include/openmp_wrappers/complex.h
-
Yes, it can become a mess if you're using common include paths (such as is usual on Linux), and compilers with their own version of the std library.
And, this particular header file (complex.h) also comes with other things like openmp... =)
In any case, I'd suggest sticking to the 'I' macro for defining complex constants, as I showed earlier. This macro is defined in complex.h on most plaforms I've tried, contrary to CMPLX.
-
..did not work...
Crap. I know it's not nice to say "works for me", but it works for me!
Note that of course you need to compile as C11 to have access to CMPLX(), see below, and link with -lm.
I used exactly your code, but with int main(void) instead of hh() and with an added z0 to test CMPLX.
newbrain@MUON:~$ gcc -std=c99 -Wall -Wextra -pedantic cpx.c -o cpx -lm
cpx.c: In function ‘main’:
cpx.c:20:10: warning: implicit declaration of function ‘CMPLX’ [-Wimplicit-function-declaration]
20 | z0 = CMPLX(0,I);
| ^~~~~
cpx.c:12:10: warning: variable ‘z0’ set but not used [-Wunused-but-set-variable]
12 | zd_t z0;
| ^~
/usr/bin/ld: /tmp/ccxcDS2j.o: in function `main':
cpx.c:(.text+0x26): undefined reference to `CMPLX'
collect2: error: ld returned 1 exit statusnewbrain@MUON:~$ gcc -std=c11 -Wall -Wextra -pedantic cpx.c -o cpx -lm
cpx.c: In function ‘main’:
cpx.c:12:10: warning: variable ‘z0’ set but not used [-Wunused-but-set-variable]
12 | zd_t z0;
| ^~
newbrain@MUON:~$ gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
newbrain@MUON:~$ ./cpx
I = 0.0+1.0i
I * I = -1.0+0.0i
pow(I, 0) = 1.0+0.0i
pow(I, 1) = 0.0+1.0i
pow(I, 2) = -1.0+0.0i
pow(I, 3) = -0.0-1.0i
exp(I*PI) = -1.0+0.0i
(1+2i)*(1-2i) = 5.0+0.0i
newbrain@MUON:~$
-
On all configurations I tried (various GCC versions, various targets), this macro is NOT defined, 'complex.h' exists, but it does not define the CMPLX macro.
If you compile with -std=C11 the "macro" will magically appear, as demonstrated above.
System include files must externally behave as if they are written in C (e.g. you can undefine a macro to make sure to get to a library function), but can use any trick the compiler writers fancy. The standard just describe how they work and what they provide, not their actual code content - in theory (but here I'm not 100% sure) they could just contain a single #pragma that tells the compiler to make their definitions available.
Edit: extended a bit my ramblings, as if someone needs them...
-
ok, some progress:
applied Gentoo Glibc Patchset 2.32-4-rc9 and rebuilt glibc-v2.32-r5 with gcc-v10.2.0
now the above code compiles, and works as expected ...
... but glibc has still some glitches :o :o :o
# make interfaces
- touching interface private
- touching interface public
- generating interface private for cplx
/usr/include/bits/mathcalls-helper-functions.h:20: syntax error at token '__value'
Expected: ')'
/usr/include/bits/mathcalls-helper-functions.h:24: syntax error at token '__value'
Expected: ')'
/usr/include/bits/mathcalls-helper-functions.h:29: syntax error at token '__value'
Expected: ')'
/usr/include/bits/mathcalls-helper-functions.h:33: syntax error at token '__value'
Expected: ')'
/usr/include/bits/mathcalls-helper-functions.h:37: syntax error at token '__value'
Expected: ')'
/usr/include/bits/mathcalls-helper-functions.h:41: syntax error at token '__x'
Expected: ')'
/usr/include/bits/mathcalls-helper-functions.h:44: syntax error at token '__value'
Expected: ')'
- generating interface public for cplx
/usr/include/bits/mathcalls-helper-functions.h:20: syntax error at token '__value'
Expected: ')'
/usr/include/bits/mathcalls-helper-functions.h:24: syntax error at token '__value'
Expected: ')'
/usr/include/bits/mathcalls-helper-functions.h:29: syntax error at token '__value'
Expected: ')'
/usr/include/bits/mathcalls-helper-functions.h:33: syntax error at token '__value'
Expected: ')'
/usr/include/bits/mathcalls-helper-functions.h:37: syntax error at token '__value'
Expected: ')'
/usr/include/bits/mathcalls-helper-functions.h:41: syntax error at token '__x'
Expected: ')'
/usr/include/bits/mathcalls-helper-functions.h:44: syntax error at token '__value'
Expected: ')'
glibc-2.32-r5 -> /usr/include/bits/mathcalls-helper-functions.h
/* Classify given number. */
__MATHDECL_ALIAS (int, __fpclassify,, (_Mdouble_ __value), fpclassify)
__attribute__ ((__const__));
(/usr/include/bits/mathcalls-helper-functions.h)
it doesn't understand what is "_Mdouble_" :-//
-
# make
- compiling cplx ... done
- linking to app
# make run
sizeof(data): 64
alignof(data): 32
sizeof(_Complex float) = 8
sizeof(_Complex double) = 16
sizeof(_Complex long double) = 24
I = 0.0+1.0i
I * I = -1.0+0.0i
pow(I, 0) = 1.0+0.0i
pow(I, 1) = 0.0+1.0i
pow(I, 2) = -1.0+0.0i
pow(I, 3) = -0.0-1.0i
exp(I*PI) = -1.0+0.0i
(1+2i)*(1-2i) = 5.0+0.0i
Phase Angle = 1.3 radians
-
Std lib quirkiness aside, I was wondering lately whether using the complex types in C would be any more efficient than just hand-implementing complex operations with FP numbers. (Sure it's more convenient, but is it any more efficient?) Or is it even possibly less efficient? I'm curious!
-
Std lib quirkiness aside, I was wondering lately whether using the complex types in C would be any more efficient than just hand-implementing complex operations with FP numbers. (Sure it's more convenient, but is it any more efficient?) Or is it even possibly less efficient? I'm curious!
Considering that I have already recompiled glibc and gcc four times, I hope at least it will be more efficient ;D
-
/usr/include/math.h -> includes <bits/mathcalls-helper-functions.h>
#define _Mdouble_ double
#define __MATH_PRECNAME(name,r) __CONCAT(name,r)
#define __MATH_DECLARING_DOUBLE 1
#define __MATH_DECLARING_FLOATN 0
#include <bits/mathcalls-helper-functions.h>
(/usr/include/math.h)
it defines Mdoble several times
#define _Mdouble_ double
# define _Mdouble_ float
# define _Mdouble_ long double
# define _Mdouble_ _Float16
# define _Mdouble_ _Float32
# define _Mdouble_ _Float64
# define _Mdouble_ _Float128
# define _Mdouble_ _Float32x
# define _Mdouble_ _Float64x
# define _Mdouble_ _Float128x
-
Std lib quirkiness aside, I was wondering lately whether using the complex types in C would be any more efficient than just hand-implementing complex operations with FP numbers. (Sure it's more convenient, but is it any more efficient?) Or is it even possibly less efficient? I'm curious!
Considering that I have already recompiled glibc and gcc four times, I hope at least it will be more efficient ;D
I admit I didn't take the time to compare this as of yet. I'll try to do that - at least look at the assembly - to get an idea.
-
So, just made a quick comparison for just one operation: multiplication. Optimization on. GCC 11.2.0, x86_64.
double complex cmul(double complex x, double complex y)
{
return x*y;
}
typedef struct { double Re, Im; } complex_t;
complex_t cmul2(complex_t x, complex_t y)
{
return (complex_t){ .Re = x.Re*y.Re - x.Im*y.Im, .Im = x.Re*y.Im + x.Im*y.Re };
}
Ready for the resulting assembly?
cmul:
subq $104, %rsp
.seh_stackalloc 104
movups %xmm6, 64(%rsp)
.seh_savexmm %xmm6, 64
movups %xmm7, 80(%rsp)
.seh_savexmm %xmm7, 80
.seh_endprologue
movsd 8(%r8), %xmm4
movsd 8(%rdx), %xmm2
movsd (%r8), %xmm3
movsd (%rdx), %xmm1
movapd %xmm2, %xmm5
movapd %xmm4, %xmm0
movapd %xmm2, %xmm7
unpcklpd %xmm3, %xmm0
unpcklpd %xmm5, %xmm5
movapd %xmm3, %xmm6
mulpd %xmm0, %xmm5
movapd %xmm1, %xmm0
unpcklpd %xmm4, %xmm6
movq %rcx, %rax
mulsd %xmm3, %xmm7
unpcklpd %xmm0, %xmm0
mulpd %xmm6, %xmm0
movapd %xmm0, %xmm6
addpd %xmm5, %xmm0
subpd %xmm5, %xmm6
movapd %xmm2, %xmm5
mulsd %xmm4, %xmm5
movsd %xmm6, %xmm0
movapd %xmm1, %xmm6
mulsd %xmm3, %xmm6
subsd %xmm5, %xmm6
movapd %xmm1, %xmm5
mulsd %xmm4, %xmm5
addsd %xmm7, %xmm5
ucomisd %xmm5, %xmm6
jp .L9
.L8:
movups %xmm0, (%rax)
movups 64(%rsp), %xmm6
movups 80(%rsp), %xmm7
addq $104, %rsp
ret
.L9:
movq %rcx, 112(%rsp)
leaq 48(%rsp), %rcx
movsd %xmm4, 32(%rsp)
call __muldc3
movupd 48(%rsp), %xmm0
movq 112(%rsp), %rax
jmp .L8
cmul2:
.seh_endprologue
movdqu (%rdx), %xmm0
movdqu (%r8), %xmm2
movapd %xmm0, %xmm1
unpcklpd %xmm0, %xmm1
unpckhpd %xmm0, %xmm0
mulpd %xmm2, %xmm1
shufpd $1, %xmm2, %xmm2
movq %rcx, %rax
mulpd %xmm2, %xmm0
movapd %xmm1, %xmm2
subpd %xmm0, %xmm2
addpd %xmm0, %xmm1
movsd %xmm2, %xmm1
movups %xmm1, (%rcx)
ret
:popcorn:
-
glibc-2.32-r5 -> /usr/include/complex.h
This is the one to worry about.
Others are C++ which doesn't have this macro because it has constructors to deal with it.
-
[... not what one could naïvely expect...]
:popcorn:
The two code snippets are not equivalent.
Consider what happens with all the combinations of Inf, 0 and NaN: the simple routine will return some surprising results, all the pirouetting in the complex.h version is needed to keep them straight.
The way they should work is described for C11 in Annex G IEC 60559-compatible complex arithmetic.
Note that the annex is normative (if __STDC_IEC_559_COMPLEX__ is defined - which gcc does not by default, AFAICS), so a compliant implementation needs to abide by it.
So, even without declaring full compliance (https://gcc.gnu.org/c99status.html), they are trying to make things right.
Use -ffast-math (or -ffinite-math-only) to get the same (https://godbolt.org/z/K979ahfjr) (unsafe, but mostly OK if one accepts the non compliance) results.
Yes, definitely complex stuff... ;D
-
glibc-2.32-r5 -> /usr/include/complex.h
This is the one to worry about.
Others are C++ which doesn't have this macro because it has constructors to deal with it.
Yup, that's one of the reasons why I rebuilt glibc :D
-
I haven't yet understood how things work, mathcalls-helper-functions.h looks invoked several times with different data-types.
-
When I have to deal with numbers, I am more comfortable with Fortran.
program Complex
implicit none
complex z /(4,3)/ ! this is 4+3i
print *, abs(z) ! result is 5.00000000
end program Complex
[/font]
This result is from the GNU Fortran compiler where complex arithmetic just works. It wasn't an afterthought, it has always been part of the language back to at least FORTRAN IV released in 1962.
-
[... not what one could naïvely expect...]
:popcorn:
The two code snippets are not equivalent.
Clearly not.
Consider what happens with all the combinations of Inf, 0 and NaN: the simple routine will return some surprising results, all the pirouetting in the complex.h version is needed to keep them straight.
The way they should work is described for C11 in Annex G IEC 60559-compatible complex arithmetic.
Note that the annex is normative (if __STDC_IEC_559_COMPLEX__ is defined - which gcc does not by default, AFAICS), so a compliant implementation needs to abide by it.
So, even without declaring full compliance (https://gcc.gnu.org/c99status.html), they are trying to make things right.
Use -ffast-math (or -ffinite-math-only) to get the same (https://godbolt.org/z/K979ahfjr) (unsafe, but mostly OK if one accepts the non compliance) results.
Yes, definitely complex stuff... ;D
Of course. But that's just to show that you could get unexpectedly worse performance when using the complex type in C, without necessarily realizing it, or even less so requiring the overhead in your case.
Although this is a reasonable approach, this kind of "hand-holding" is still questionable. And, if you set -ffast-math or -ffinite-math-only, this has other implications on your code, especially if you're also using FP numbers.
Sure, expressions like 'a*c + b*d' can yield funky results or loss of precision depending on a, b, c and d using FP arithmetic. But that's true in any case, not just when dealing with complex numbers, and expressions like this are extremely common.
-
Got tired and found a workaround
/*
* ISO C99 Standard: 7.22 Type-generic math <tgmath.h>
*/
#ifndef _TGMATH_H
#define _TGMATH_H 1
#define __GLIBC_INTERNAL_STARTING_HEADER_IMPLEMENTATION
#include <bits/libc-header-start.h>
/* Include the needed headers. */
#include <bits/floatn.h>
#ifndef myautoproto // myhack
#include <math.h> // myhack
#endif // myhack
#include <complex.h>
...
(/usr/include/tgmath.h)
added "-Dmyautoproto=1" *only* to the interface generator of my autobuilder
# myproject-autobuild-makefile-v5-revC
# make clean_all
- cleaning interfaces /usr/include
- cleaning interfaces app
- cleaning interfaces .
- cleaning all
# make interfaces
- touching interface private
- touching interface public
- generating interface private for app
- generating interface private for cplx
- generating interface public for app
- generating interface public for cplx
# make
- compiling app ... done
- compiling cplx ... done
- linking to app
and boooom, now it works without annoying unintelligible messages :D
-
When I have to deal with numbers, I am more comfortable with Fortran.
Of course, GNU Fortran is also less messed up than Gcc, but I have to support complex numbers in the cleanest and safest way for the firmware I wrote for industrial embroidery machine and I don't want to also implement complex numbers because I am already about 7K lines of C.
The next steps are
- to get it working for Linux-{ MIPS32/be, HPPA2/32bit, PPC32/be, PPC64/be-32bit, ARM7-32bit }-glibc
- to get it working for cross-compiling, for the industrial embroidery machine, target = PPC32/be
PPC64/be-32bit will also have to support complex numbers natively for a radio-telescope application. Next month.
-
When I have to deal with numbers, I am more comfortable with Fortran.
Of course, GNU Fortran is also less messed up than Gcc, but I have to support complex numbers in the cleanest and safest way for the firmware I wrote for industrial embroidery machine and I don't want to also implement complex numbers because I am already about 7K lines of C.
It's pretty easy to mix C and Fortran once you get used to the idea that Fortran is 'call by reference' and C is 'call by value'. I used an RS232 library written in C on a Fortran project by writing a little shim code. In Fortran, a complex value is held as a 2 element vector of real numbers. I suppose that would be a 'struct' in C were I writing my own complex library but I haven't tried it.
But really, you're probably too far along to pull the math bits out and rewrite them in Fortran. It's just that FORTRAN was my first language and will always be my favorite. When it comes to numbers, I tend to prefer it to all others.
-
In Fortran, a complex value is held as a 2 element vector of real numbers. I suppose that would be a 'struct' in C were I writing my own complex library but I haven't tried it.
From ISO/IEC 9899:2011 (C11 standard) "6.2.5 Types", paragraph 13:
Each complex type has the same representation and alignment requirements as an array type containing exactly two elements of the corresponding real type; the first element is equal to the real part, and the second element to the imaginary part, of the complex number.
So yes, it's an array exactly as in Fortran, not a structure.
In practice, it's a difference without a distinction:
the amount of padding between members in a struct is not specified (at least, that's how I read the standard), while an array can have only as much as to guarantee alignment of its elements (due to pointer arithmetic) though I would not expect any sane compiler to actually insert pointless padding.