Complex numbers with C interface

I’m trying to use complex numbers returned from C functions in Ada, but there seems to be an issue with the my bindings.

My C code is the following:

#include <complex.h>
#include <stdio.h>

complex double add_complex(complex double z1, complex double z2) {
    printf("z1: %f + i%f\n", creal(z1), cimag(z1));
    printf("z2: %f + i%f\n", creal(z2), cimag(z2));
    return z1 + z2;
}

And my Ada code is this:

with Interfaces.C;
with Ada.Numerics.Long_Complex_Types; use Ada.Numerics.Long_Complex_Types;
with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
   function Add_Complex (z1 : Complex; z2 : Complex) return Complex
      with Import => True, 
           Convention => C, 
           External_Name => "add_complex";

   Z1 : Complex := (Re => 1.0, Im => 3.0); 
   Z2 : Complex := (Re => 3.0, Im => 4.0);
   Result : Complex;

begin
   Result := Add_Complex(Z1, Z2);

   Put_Line (Result'Image);
end Main;

The line where I import Add_Complex seems to be the same as the bindings generated by gcc -fdump-ada-spec, but when this program is compiled, it gives unexpected outputs like
(RE => 3.00000000000000E+00, IM => 9.29389764200523E-310) instead of the expected values. The problem seems to be that both Z1 and Z2 are not being passed to add_complex correctly.

Does anyone have any idea how to fix this?

Thanks.

Hi @wynme and welcome to the forums!

My guess is that the Ada Complex type is different to the C complex type. How are the C complext type defined? Are they just a simple struct with a couple of floats, maybe doubles? I suppose that what you need to do is define an Ada record that has the same layout/structure/size/types as the C one and then create a To_Ada and To_C functions to swap between those two.

I hope this helps,
Fer

The problem you have is that C’s double complex is a macro that refers to a structure. Depending on the compiler version it can be passed by copy or by reference. E.g. your code works OK with GNAT 10 and does not with GNAT 15.

I suppose that GNAT 15 considers Long_Complex a non-integral type when interfacing C and passes it by reference while GCC 15 expect _Complex (or however that structure is named) passed by copy. To make it work with GNAT 15 you must create a record type passed by copy:

   type C_Complex is record
      Re, Im : Interfaces.C.double;
   end record with Convention => C_Pass_By_Copy;

Morale, do not use built-in Ada types to interface other languages.

1 Like

It’s a bit more nefarious than that. In C, the complex macro from complex.h refers to the _Complex keyword (that’s not confusing at all, right?? :slight_smile: ).

For variables declared with the _Complex keyword, the implementation can define the underlying representation how it likes. It doesn’t have to be a structure, but it can be. That said, however it is implemented, the standard requires that it has the same representation and alignment as a 2 element array of the corresponding real type (float, double, long double). The first element must be the real part, and the second the imaginary. Implementations can still do this via struct, but they have to ensure the struct used conforms to those low level requirements (it has to map to the 2 element array as mentioned above).

C standard section 6.2.5 pg 11-13

11 
There are three complex types, designated as float _Complex, double
_Complex, and long double _Complex.33) The real floating and complex types
are collectively called the floating types.

12 
For each floating type there is a corresponding real type, which is always a 
real floating type. For real floating types, it is the same type. For complex 
types, it  is the type given by deleting the keyword _Complex from the type 
name.

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.

EDIT: I would probably still implement it as a struct on the Ada side for ease of use and readability, but you would want to do a record representation clause to ensure the values are placed according to the C standard requirements for correct mapping. And you would want to make sure the Size and Alignment were set correctly. I don’t know if you use Conventoin => C or Convention => C_Pass_By_Copy for it, but that’s easy to swap out and test. My initial guess is it will be C_Pass_By_Copy.

EDIT #2: This seems to work in godbolt:

with Interfaces.C;
with Ada.Text_IO; use Ada.Text_IO;

procedure Example is

   Double_Size      : constant := Interfaces.C.double'Size;
   Double_Alignment : constant := Interfaces.C.double'Alignment;

   type Complex is record
      Real, Imaginary : Interfaces.C.double;
   end record 
      with Size       => 2 * Double_Size, 
           Alignment  => Double_Alignment, -- C arrays are aligned by element alignment
           Convention => C_Pass_By_Copy;

   for Complex use record
      Real      at                0 range 0 .. Double_Size - 1;
      Imaginary at Double_Alignment range 0 .. Double_Size - 1;
   end record;

   function C_Sin(Angle : Complex) return Complex
      with Import, Convention => C, External_Name => "csin";

   Value : constant Complex := C_Sin(Complex'(0.785398, 0.0));

begin
   Put_Line("Result = " & Value'Image);
end Example;

Compiler Switches: -gnat2022 -largs -lm

Results:

Result = 
(REAL =>  7.07106665647094E-01,
 IMAGINARY =>  0.00000000000000E+00)
2 Likes

Thank you for the help!

It seems like I should have looked at how C implemented complex numbers first. Everything is working now.

1 Like