Вызов функций Fortran, которые возвращают KIND = 10 комплексных значений из C ++ - PullRequest
3 голосов
/ 03 августа 2020

У меня есть куча старого исходного кода F77 (обычно скомпилированного на x86_64 с gfortran -std=legacy). Он содержит довольно много функций в форме:

      double complex function f(x, y, i)
      double precision x, y
      integer i
      f = cmplx(x, y) * i
      return
      end

Мне нужно вызвать эти функции из некоторого кода C ++ (обычно скомпилированного на x86_64 с g++).

  1. Он работает с Fortran по умолчанию KIND=8:

    extern "C" { std::complex<double> f_(double *x, double *y, int *i); }
    
  2. Он работает, когда я применяю Fortran по умолчанию KIND=4 с помощью параметра -freal-8-real-4:

    extern "C" { std::complex<float> f_(float *x, float *y, int *i); }
    
  3. Это работает, когда я использую Fortran по умолчанию KIND=16 с помощью опции -freal-8-real-16 (и в C ++ #include <quadmath.h>):

    extern "C" { __complex128 f_(__float128 *x, __float128 *y, int *i); }
    

    К моему удивлению , в этом случае, похоже, он также работает (возвращаемое значение находится в *z):

    extern "C" { void f_(__complex128 *z, __float128 *x, __float128 *y, int *i); }
    

    Какой из этих двух прототипов является (больше?) подходящим?

  4. Моя проблема в том, что я не могу заставить его работать с моим желаемым по умолчанию Fortran KIND=10, используя опцию -freal-8-real-10. Внутри Fortran возвращаемые значения kind, precision, range и sizeof напрямую соответствуют C ++ long double. Итак, я попробовал:

    extern "C" { std::complex<long double> f_(long double *x, long double *y, int *i); }
    extern "C" { void f_(std::complex<long double> *z, long double *x, long double *y, int *i); }
    extern "C" { void f_(long double *x, long double *y, int *i, std::complex<long double> *z); }
    

    Но я не могу заставить его работать вообще.

    Возможно, мне нужно добавить какие-то специальные флаги для вызовов gfortran и / или g++ по порядку позволить C ++ получать сложные значения Fortran KIND=10? Примечание: я не думаю, что могу использовать -ff2c.

Обновление (2020.08.04) : мне удалось обмануть компилятор C ++, чтобы похоже, он генерирует правильный код для любого Fortran KIND=4,8,10. Хитрость заключается в использовании ISO C99 _Complex в C ++ (примечание: этот трюк требуется только для KIND=10, но на самом деле он работает и для KIND=4,8):

#include <complex.h>
#define C99KIND long double /* it can be "float", "double" or "long double" */
extern "C" { C99KIND _Complex f_(C99KIND *x, C99KIND *y, int *i); }

Обратите внимание, что, в C ++ вы не можете использовать, например, long double complex, но, к счастью, long double _Complex все еще в порядке.

Использование ISO C99 _Complex в C ++ довольно ограничено. Например, с -std=c++11 (или новее) исчезают даже самые простые c creal* и cimag* функции.

Итак, лучшая идея - немедленно скопировать возвращаемое значение в какой-нибудь стандартный C ++ шаблонная комплексная переменная, например, используя что-то вроде (примечание: f_ возвращает C99KIND _Complex):

std::complex<C99KIND> z = f_(&x, &y, &i);

1 Ответ

1 голос
/ 03 августа 2020

Если вы хотите выполнить задание правильно, узнайте, как на самом деле использовать параметры типа Fortran, и правильно перенести свой код Fortran на REAL(10). Да, я знаю, что 10 не переносится; однако мы обсуждаем конкретный процессор Fortran.

Учтите,

function f(x, y, i) result(r) bind(c, name='f')
  use iso_c_binding, only : ep => c_long_double
  implicit none
  complex(ep) r
  real(ep), intent(in), value :: x, y
  integer, intent(in), value :: i
  r = cmplx(x, y, ep) * i
end function f

и, поскольку я не занимаюсь C ++, вы сможете обновить C в соответствии с вашими потребностями

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

long double complex f(long double, long double, int);

int
main(void)
{
  int i;
  long double x, y;
  long double complex z;

  i = 42;
  x = 1;
  y = 2;
  printf("%.10Lf %.10Lf\n", x, y);

  z = f(x, y, i);
  x = creall(z);
  y = cimagl(z);
  printf("%.10Lf %.10Lf\n", x, y);
  return 0;
}

% gfortran -c a.f90
% gcc -o z b.c a.o -lm
% ./z
1.0000000000 2.0000000000
42.0000000000 84.0000000000

OP заявляет, что его не может беспокоить правильный порт его / ее кода Fortran, и поэтому он должен использовать параметр компилятора, чтобы волшебным образом (и да, это magi c), чтобы сделать порт. Вот пример

% cat a.f90
double complex function f(x, y, i)
  implicit none
  double precision :: x, y
  integer i
  f = cmplx(x, y) * i   ! cmplx my not do what you expect
end function f

% cat b.c
#include <complex.h>
#include <stdio.h>
#include <stdlib.h>

long double complex f_(long double *, long double *, int *);

int
main(void)
{
  int i;
  long double x, y;
  long double complex z;

  i = 42;
  x = 1;
  y = 2;
  printf("%.10Lf %.10Lf\n", x, y);

  z = f_(&x, &y, &i);
  x = creall(z);
  y = cimagl(z);
  printf("%.10Lf %.10Lf\n", x, y);
  return 0;
}

% gfcx -c -freal-8-real-10 a.f90
% gcc -o z b.c a.o -lm
% ./z
1.0000000000 2.0000000000
42.0000000000 84.0000000000

Может также go для trifecta. Вот код C ++ для go с файлом a.f90 выше во втором примере.

#include <iostream>
#include <complex>
#include <cmath>

extern "C" { std::complex<long double> f_(long double *, long double *, int *); }

int main()
{
  std::complex<long double> z;
  long double x, y;
  int i;

  i = 42;
  x = 1;
  y = 2;
  z = f_(&x, &y, &i);

  std::cout << z << '\n';
 }
...