计算化学公社

 找回密码 Forget password
 注册 Register
Views: 4718|回复 Reply: 9
打印 Print 上一主题 Last thread 下一主题 Next thread

[Fortran] Fortran可否从长度未知字符串读取格式未知浮点数?

[复制链接 Copy URL]

1187

帖子

5

威望

2859

eV
积分
4146

Level 6 (一方通行)

跳转到指定楼层 Go to specific reply
楼主
本帖最后由 snljty 于 2020-12-21 20:38 编辑

用C/C++/Python等语言处理Fortran写的程序的输出文件的时候,很大的一个麻烦是浮点数的格式。Fortran的格式和前面几个不完全统一,比如以下几点。
1.Fortran用的不是标准科学记数法,默认输出小数部分是(0.1,1]的一个数,C是(1,10],不过这个一般无关痛痒。
2.Fortran程序员有时候会用D代表双精度浮点数,比如0.2D+02,除了强调双精度浮点以外这个和0.2E+02没有区别,但是前者不能被C识别。
3.当指数超出了限制范围(比如限定指数2位,实际算出来3位的时候),代表浮点数的字母D或者E会被省略,如0.1+100可能代表0.1D+100.
4.输出一堆****的时候,有时候可以考虑当成NaN处理。
上述2.3等问题导致C没法直接读Fortran输出的诸多浮点数格式。虽然可以用一系列字符串处理方法,比如D替换成E,发现没字母就补一个之类的,但是终究很麻烦,也不优雅。

所以我想如果有一个简单方法,可以让Fortran从一个任意长度的字符串(字符数组,对于C只要传递指向数组首位的指针),中读取一个任意格式的浮点数,就好办了。
把这个函数写个简单的fortran文件,当做一个接口,和其他C写的程序混合编译一下即可,那就可以方便很多C/C++/Python开发者。C的字符串就是字符指针和一段空间,用'\0'来标记字符串结束。但Fortran好像没有这种设定。

希望是这个样子。

Makefile:
  1. # C和Fortran混合编译测试文件test.exe

  2. CC      = gcc
  3. FC      = gfortran
  4. FLINKER = $(FC)

  5. all: test.exe

  6. test.exe: test.o read_double.o
  7.         $(FLINKER) -o $@ $^

  8. test.o: test.c
  9.         $(CC) -o $@ -c $^

  10. read_double.o: read_double.f90
  11.         $(FC) -o $@ -c $^

  12. .PHONY: clean
  13. clean:
  14.         -del test.o read_double.o 2>NUL 1>NUL
  15.         -del test.exe 2>NUL 1>NUL

复制代码


test.c:
  1. /*  调用Fortran写的 read_double_from_string_ 函数,注意常见编译器会把fortran编译的函数名字后面多加个下划线  */

  2. # include <stdio.h>

  3. void read_double_from_string_(const char *const, double *);

  4. int main()
  5. {
  6.     char a[] = "1.23";
  7.     double d = 0.0;

  8.     read_double_from_string_(a, & d);
  9.     /* 如果只是C支持的格式,其实用sscanf(a, "%lf", & d);就行了。  */

  10.     printf("%.2lf\n", d);

  11.     return 0;
  12. }
复制代码


read_double.f90
  1. ! 这个文件提供一个函数,从任意长度字符串读取任意格式浮点数

  2. subroutine read_double_from_string(read_str, d_number)
  3.     implicit none
  4.     character, pointer :: read_str ! 这里这么写应该不行。
  5.     real(kind=8) :: d_number

  6.     read(read_str, "(D)") d_number ! 这里这么写编译器通不过。
  7.     return
  8. end subroutine read_double_from_string
复制代码


请问这个read_double.f90有没有办法写出来?谢谢!
抱歉实在不熟悉Fortran,问题有些幼稚的地方还请见笑了。



4103

帖子

4

威望

8861

eV
积分
13044

Level 6 (一方通行)

MOKIT开发者

2#
发表于 Post on 2020-12-17 18:02:37 | 只看该作者 Only view this author
我已经忘记C了。以下代码可以运行,结果也是对的。不知道是不是足够合理。
C代码

  1. #include<stdio.h>
  2. #include<string.h>

  3. int main()
  4. {
  5.     char a[] = "1.23";
  6.     double d = 0.0;
  7.     int la = (int)strlen(a);

  8.     read_double_from_string_(&la, a, &d);

  9.     printf("%.2lf\n", d);

  10.     return 0;
  11. }
复制代码
Fortran90代码,fortran读的时候不需要操心数据类型,写*即可,后面读取的变量对了就行。
  1. subroutine read_double_from_string(lstr, read_str, d_number)
  2. implicit none
  3. integer, intent(in) :: lstr
  4. character(len=lstr), intent(in) :: read_str
  5. real(kind=8), intent(out) :: d_number

  6. read(read_str,*) d_number
  7. return
  8. end subroutine read_double_from_string
复制代码
也可以从一个带空格的字符串中读取若干个数据,例如开两个双精度数d1, d2,

这里改为char a[] = "1.23 1.25";
(当然中间要先声明d1, d2,不详细写了)后面fortran可以改为
read(read_str,*) d1, d2

自动做多参考态计算的程序MOKIT

1187

帖子

5

威望

2859

eV
积分
4146

Level 6 (一方通行)

3#
 楼主 Author| 发表于 Post on 2020-12-17 18:59:18 | 只看该作者 Only view this author
zjxitcc 发表于 2020-12-17 18:02
我已经忘记C了。以下代码可以运行,结果也是对的。不知道是不是足够合理。
C代码

谢谢邹神!就知道论坛一定能解决!我以前记错了,还以为栈里不能声明大小编译时期未知的变量,因为编译器无法确认尺寸呢...原来有
integer :: lstr
character(len=lstr) :: read_str
这种写法。
不过编译器为什么能允许这种操作...?才发现C语言也行,看来确实是我记错了...之前每次这种情况都是用allocate的...

1187

帖子

5

威望

2859

eV
积分
4146

Level 6 (一方通行)

4#
 楼主 Author| 发表于 Post on 2020-12-17 21:40:01 | 只看该作者 Only view this author
本帖最后由 snljty 于 2020-12-22 00:25 编辑

更新:把python版本的测试代码也发上来了,编译python需要的pyd库的方法是使用numpy包的f2py模块。
  1. f2py -m read_double -c read_double.f90
复制代码

根据2L的回复,稍微改了一下,供参考。
假设要用C从input.txt里面读一行文本,然后从文本里读几个(Fortran格式的)双精度浮点数。

input.txt:
  1. 1.23  0.456E+01  0.789D+02   0.100+111
复制代码


test.c:
  1. /**************************************************************
  2. *  This file uses Fortran function read_double_from_string_  *
  3. **************************************************************/

  4. # ifdef __cplusplus
  5. extern "C" {
  6. # endif

  7. # include <stdio.h>
  8. # include <string.h>

  9. /*  note  */
  10. /*  for most compilers, Fortran will append a '_' to the name of the function,  */
  11. /*  and uses only lower letters in name of functions.  */
  12. /*  also, please remember that Fortran passes addresses of arguments, but C/C++ passes values,  */
  13. /*  hence if you want to call a Fortran function in a C program, you have to pass pointers for all.  */
  14. void read_double_from_string_(const int *const str_length, const char *const read_str, double *d_number);

  15. void read_fortran_double(const char *const read_str, double *d_number);

  16. int main()
  17. {
  18.     const char splitter[] = " \r\n\t\v";
  19.     FILE *ifp = fopen("input.txt", "rt");

  20.     char buf[BUFSIZ + 1u] = "";
  21.     double d = 0.0;
  22.     char *tok = NULL;

  23.     fgets(buf, BUFSIZ, ifp);
  24.     tok = strtok(buf, splitter);
  25.     while(tok)
  26.     {
  27.         read_fortran_double(tok, & d);
  28.         /*  sscanf(a, "%lf", & d);  */
  29.         printf("%g\n", d);
  30.         tok = strtok(NULL, splitter);
  31.     }

  32.     fclose(ifp);
  33.     ifp = NULL;

  34.     return 0;
  35. }

  36. void read_fortran_double(const char *const read_str, double *d_number)
  37. {
  38.     int len = (int)strlen(read_str);

  39.     read_double_from_string_(& len, read_str, d_number);

  40.     return;
  41. }

  42. # ifdef __cplusplus
  43. }
  44. # endif

复制代码



test.py:
  1. #! /usr/bin/env python3
  2. # -*- Coding: UTF-8 -*-

  3. r"""
  4. This program uses fortran module read_double
  5. """

  6. import read_double

  7. with open('input.txt') as f:
  8.     l = f.readline()

  9. read_fortran_double = lambda _: read_double.read_double_from_string(len(_), _)

  10. for s in l.strip().split():
  11.     print(read_fortran_double(s))
复制代码



read_double.f90:

  1. ! this file reads a double from a string.

  2. subroutine read_double_from_string(str_length, read_str, d_number)
  3.     implicit none
  4.     integer(kind=4), intent(in) :: str_length
  5.     character(len=str_length), intent(in) :: read_str
  6.     real(kind=8), intent(out) :: d_number

  7.     read(read_str, *) d_number
  8.     return
  9. end subroutine read_double_from_string

复制代码


Makefile:
  1. # Makefile for test

  2. CC      = gcc
  3. FC      = gfortran
  4. CLINKER = $(CC)
  5. FLINKER = $(FC)
  6. ARCH   = ar

  7. all: test.exe

  8. test.exe: test.o static_lib
  9.         # 可以用Fortran编译器链接
  10.         $(FLINKER) -o $@ test.o -L . -l read_double
  11.         # 也可以用C编译器链接,但是gcc默认是不链接libgfortran.*库的,可以加上-l gfortran
  12.         # $(CLINKER) -o $@ test.o -L . -l read_double -l gfortran

  13. test.o: test.c
  14.         $(CC) -o $@ -c $^

  15. read_double.o: read_double.f90
  16.         $(FC) -o $@ $^

  17. static_lib: libread_double.a

  18. libread_double.a: read_double.o
  19.         $(ARCH) -rsc $@ $^

  20. .PHONY: clean
  21. clean:
  22.         -rm test.o read_double.o
  23.         -rm test.exe
  24.         -rm libread_double.a

复制代码

感觉这个方法很适合像我这样C写起来相对顺手,极度恐惧Fortran,超过5行Fortran写起来都难受的人...


评分 Rate

参与人数
Participants 1
eV +5 收起 理由
Reason
thanhtam + 5 GJ!

查看全部评分 View all ratings

1187

帖子

5

威望

2859

eV
积分
4146

Level 6 (一方通行)

5#
 楼主 Author| 发表于 Post on 2020-12-21 20:36:59 | 只看该作者 Only view this author
本帖最后由 snljty 于 2020-12-21 20:38 编辑
zjxitcc 发表于 2020-12-17 18:02
我已经忘记C了。以下代码可以运行,结果也是对的。不知道是不是足够合理。
C代码

邹老师好,这个fortran文件在用f2py编译后用python无法调用,能请您看看么?
Win10系统,
python是MSC v.1916 64 bit (AMD64),版本3.8,装了libpython。
fortran编译器用的TDM-gcc 9.2.0里面的gfortran,x86_64-w64-mingw32里面的
f2py是对应numpy 1.19.2的版本。

编译使用
  1. f2py -m read_double -c read_double.f90
复制代码

然后生成了read_double.cp38-win_amd64.pyd

python里面报错如下:
  1. >>> import read_double
  2. Traceback (most recent call last):
  3.   File "<stdin>", line 1, in <module>
  4. ImportError: DLL load failed while importing read_double: A dynamic link library (DLL) initialization routine failed.
复制代码


也尝试了在别的操作系统下编译,没有成功。
用一个最简单的subroutine测试了一下(两个整数相加),编译完了加载没有问题,f2py应该至少(部分)是配置好的。
谢谢!

910

帖子

1

威望

7873

eV
积分
8803

Level 6 (一方通行)

6#
发表于 Post on 2020-12-21 20:54:29 | 只看该作者 Only view this author
本帖最后由 hebrewsnabla 于 2020-12-21 21:02 编辑
snljty 发表于 2020-12-21 20:36
邹老师好,这个fortran文件在用f2py编译后用python无法调用,能请您看看么?
Win10系统,
python是MSC  ...

linux下是可以的。




4103

帖子

4

威望

8861

eV
积分
13044

Level 6 (一方通行)

MOKIT开发者

7#
发表于 Post on 2020-12-21 21:03:59 | 只看该作者 Only view this author
本帖最后由 zjxitcc 于 2020-12-21 21:13 编辑
snljty 发表于 2020-12-21 20:36
邹老师好,这个fortran文件在用f2py编译后用python无法调用,能请您看看么?
Win10系统,
python是MSC  ...

我没法安装你的这么多软件所以没法调试。我在linux下用anaconda python的f2py编译试了,是可以import的,结果也正常。为保稳妥,你可以采取如下形式复杂写法

  1. subroutine read_double_from_string(lstr, read_str, d_number)
  2. implicit none
  3. integer :: lstr
  4. !f2py intent(in) :: lstr
  5. character(len=lstr) :: read_str
  6. !f2py depend(lstr) :: read_str
  7. !f2py intent(in) :: read_str
  8. real(kind=8) :: d_number
  9. !f2py intent(out) :: d_number

  10. read(read_str,*) d_number
  11. return
  12. end subroutine read_double_from_string
复制代码
注意!f2py最好顶格,!与f2py间无空格。运行
$python
>>>from aaa import read_double_from_string
>>>i = read_double_from_string(4, '1.23')
>>>print(i)
1.23

可能是旧的编译器无法自动识别变量depend吧

自动做多参考态计算的程序MOKIT

1187

帖子

5

威望

2859

eV
积分
4146

Level 6 (一方通行)

8#
 楼主 Author| 发表于 Post on 2020-12-21 21:17:50 | 只看该作者 Only view this author

谢谢!刚又试了一下ubuntu下其实是成功的,就是很奇怪python能用,但是ipython找不到库。之前用ipython调试的,没仔细看还以为是一样的报错。

1187

帖子

5

威望

2859

eV
积分
4146

Level 6 (一方通行)

9#
 楼主 Author| 发表于 Post on 2020-12-21 21:18:58 | 只看该作者 Only view this author
zjxitcc 发表于 2020-12-21 21:03
我没法安装你的这么多软件所以没法调试。我在linux下用anaconda python的f2py编译试了,是可以import ...

谢谢!这种写法我也试过,目前Windows下还是不行,估计是gnu编译器版本和python里面某些不兼容。我尝试一下用conda装的mingw(这个装上用就报错,正在调试。)

1187

帖子

5

威望

2859

eV
积分
4146

Level 6 (一方通行)

10#
 楼主 Author| 发表于 Post on 2020-12-21 21:39:45 | 只看该作者 Only view this author
zjxitcc 发表于 2020-12-21 21:03
我没法安装你的这么多软件所以没法调试。我在linux下用anaconda python的f2py编译试了,是可以import ...

已经解决,谢谢您!用conda装了个mingw,把mingw安装目录写在PATH最前面,然后用conda里面mingw的gnu编译器(4.7.0)就通过了。估计还是兼容性问题。

本版积分规则 Credits rule

手机版 Mobile version|北京科音自然科学研究中心 Beijing Kein Research Center for Natural Sciences|京公网安备 11010502035419号|计算化学公社 — 北京科音旗下高水平计算化学交流论坛 ( 京ICP备14038949号-1 )|网站地图

GMT+8, 2025-8-12 21:36 , Processed in 0.158027 second(s), 21 queries , Gzip On.

快速回复 返回顶部 返回列表 Return to list