如何使用千位分隔符点和小数分隔符逗号将数字读入 Fortran,例如123.456.891.234,56

How to read a number into fortran with thousand-separator point and decimal-separator comma e.g. 123.456.891.234,56

我有一个用分号 (;) 分隔的巨大文本文件。数字 具有千位分隔符点和小数点分隔符的格式 昏迷,例如123.456.891.234,56

我不想用编辑器搜索和替换文件中的点,因为我没有更改文件的权限。

我可以将它读作字符串并尝试去掉这些点。 但这似乎不是解决问题的好方法。

program prjRead
  implicit none

  integer:: a
  real(8) :: b
  real(8) :: c
  character(10) :: dummy

  open (123,file = "test.csv", DECIMAL='COMMA')

  read(123,*) dummy
  read(123,*) a, &
              b, &
              c

  write(*,*) a,b,c

end program prjRead

test.csv

的内容
integer;decimalcomma;thousandsep
5;56,67;123.456,78

文件prjRead.f90第36行(单位=123,文件='test.csv') Fortran 运行时错误:列表输入项 3 中的实数错误

如果你真的想在 Fortran 中做到这一点,那并不难。首先,让我们创建一个函数,从字符串中删除所有出现的 .

  FUNCTION drop_stops(instr) RESULT(outstr)
    CHARACTER(len=*), INTENT(in) :: instr
    CHARACTER(len=:), ALLOCATABLE :: outstr
    CHARACTER(len=1), DIMENSION(:), ALLOCATABLE :: str_array

    ALLOCATE(str_array(LEN_TRIM(instr)))
    str_array = TRANSFER(instr,str_array)
    str_array = PACK(str_array,str_array /= '.')
    ALLOCATE(CHARACTER(len=SIZE(str_array))::outstr)
    outstr = TRANSFER(str_array,outstr)
  END FUNCTION drop_stops

我相信这很明显,除了您不熟悉的任何函数或语句的文档外,无需任何解释。

然后,坚持使用您的原始代码并声明另一个您可以阅读的字符串变量 dummy,就像您已经做的那样,然后编写类似

的内容
 dummy_without_stops = drop_stops(dummy)

现在您可以对其进行内部阅读以获取您感兴趣的数字,例如

read(dummy_without_stops,*,decimal='comma') a, b, c

请注意,drop_stops 中实现的方法取决于字符串中的字符按顺序排列在内存中并匹配数组中字符的相同存储。我相信这对 ASCII 字符有效,但对 ISO_10646 个字符不太确定。

您没有从输入文件中提供足够大的样本。但本质上,您需要先通过分隔符 ; 拆分文件内容。然后,对于您获得的每个字符串编号,将所有千位分隔符 . 替换为“”(无)。然后,将小数符号,替换为正常的十进制符号.。这是通过下面的 splitStr()replaceStr() 类型绑定过程来实现此目的的尝试,在您提供的示例文件内容行上进行了测试 5;56,67;123.456,78

module String_mod

    use, intrinsic :: iso_fortran_env, only: IK=>int32, RK=>real64
    implicit none

    public

    character(*), parameter :: MODULE_NAME = "@String_mod"

    type :: CharVec_type
        character (:), allocatable  :: record
    end type CharVec_type

    type :: String_type
        character(:)      , allocatable   :: value
        type(CharVec_type), allocatable   :: Parts(:)
        integer(IK)                       :: nPart = 0
    contains
        procedure, nopass :: replaceStr, splitStr, str2num
    end type String_type

!***********************************************************************************************************************************
!***********************************************************************************************************************************

contains

!***********************************************************************************************************************************
!***********************************************************************************************************************************

    recursive function replaceStr(string,search,substitute) result(modifiedString)
        implicit none
        character(len=*), intent(in)  :: string, search, substitute
        character(len=:), allocatable :: modifiedString
        integer(IK)                   :: i, stringLen, searchLen
        stringLen = len(string)
        searchLen = len(search)
        if (stringLen==0 .or. searchLen==0) then
            modifiedString = ""
            return
        elseif (stringLen<searchLen) then
            modifiedString = string
            return
        end if
        i = 1
        do
            if (string(i:i+searchLen-1)==search) then
                modifiedString = string(1:i-1) // substitute // replaceStr(string(i+searchLen:stringLen),search,substitute)
                exit
            end if
            if (i+searchLen>stringLen) then
                modifiedString = string
                exit
            end if
            i = i + 1
            cycle
        end do
    end function replaceStr

!***********************************************************************************************************************************
!***********************************************************************************************************************************

    function splitStr(string,delimiter)

        implicit none
        character(len=*)  , intent(in)  :: string,delimiter
        character(len=:)  , allocatable :: dummyStr
        type(CharVec_type), allocatable :: splitStr(:)
        integer(IK)                     :: maxNumSplit
        integer(IK)                     :: stringLen, delimLen, splitCounter, currentPos

        dummyStr  = string
        delimLen  = len(delimiter)
        stringLen = len(dummyStr)

        if (delimLen==0) then
            allocate(splitStr(1))
            splitStr(1)%record = string
            return
        end if

        maxNumSplit = 1 + stringLen / delimLen
        allocate(splitStr(maxNumSplit))
        splitCounter = 1
        loopParseString: do
            if (stringLen<delimLen) then
                splitStr(splitCounter)%record = dummyStr
                exit loopParseString
            elseif (stringLen==delimLen) then
                if (dummyStr==delimiter) then
                    splitStr(splitCounter)%record = ""
                end if
                exit loopParseString
            elseif (dummyStr(1:delimLen)==delimiter) then
                dummyStr = dummyStr(delimLen+1:stringLen)
                stringLen = len(dummyStr)
                cycle loopParseString
            else
                currentPos = 2
                loopSearchString: do
                    if (dummyStr(currentPos:currentPos+delimLen-1)==delimiter) then
                        splitStr(splitCounter)%record = dummyStr(1:currentPos-1)
                        if (currentPos+delimLen>stringLen) then
                            exit loopParseString
                        else
                            splitCounter = splitCounter + 1
                            dummyStr = dummyStr(currentPos+delimLen:stringLen)
                            stringLen = len(dummyStr)
                            cycle loopParseString
                        end if
                    else
                        currentPos = currentPos + 1
                        if (stringLen<currentPos+delimLen-1) then
                            splitStr(splitCounter)%record = dummyStr
                            exit loopParseString
                        end if
                        cycle loopSearchString
                    end if
                end do loopSearchString
            end if
        end do loopParseString
        splitStr = splitStr(1:splitCounter)

    end function splitStr

!***********************************************************************************************************************************
!***********************************************************************************************************************************

    pure elemental function str2num(str)
        implicit none
        character(len=*), intent(in) :: str
        real(RK)                  :: str2num
        read(str,*) str2num
    end function str2num

!***********************************************************************************************************************************
!***********************************************************************************************************************************

end module String_mod

program readFile_prog
    use String_mod, only: String_type
    implicit none
    ! Rules: comma means decimal point. Dot means thousands separator. delimiter is ;.
    character(*), parameter :: FileToRead = "5;56,67;123.456,78"
    type(String_type)       :: String
    integer                 :: i

    ! read file
    String%value = FileToRead

    ! split file contents into individual numbers 
    String%Parts = String%splitStr(String%value,";")

    ! count the number of integers in the file
    String%nPart = size(String%Parts)

    do i = 1, String%nPart

        ! For each number, remove the thousands separator, by replacing "." with ""
        String%Parts(i)%record = String%replaceStr(String%Parts(i)%record,".","")

        ! now replace comma decimal symbols with regular . decimal notation
        String%Parts(i)%record = String%replaceStr(String%Parts(i)%record,",",".")

        ! Covert the integer number from character type to integer and print it on screen
        write(*,"(*(g0,:,' '))") "Number(",i,") = ", String%str2num(String%Parts(i)%record)

    end do

end program readFile_prog

这是输出:

$gfortran -std=f2008 *.f95 -o main
$main
Number( 1 ) =  5.0000000000000000
Number( 2 ) =  56.670000000000002
Number( 3 ) =  123456.78000000000
program prjRead
  implicit none

  integer:: a
  real(8) :: b
  real(8) :: c
  character(18) :: header
  character(18) :: cAsString
  character(18) :: cWithOutStops

  open (123,file = "test.csv", DECIMAL='COMMA')

  read(123,*) header
  read(123,*) a, &
              b, &
              cAsString

  cWithOutStops = drop_stops(cAsString)

  read(cWithOutStops,*,decimal='comma')  c

  write(*,*) "gives c without decimal places"
  write(*,*) a,b,c

  write(*,*) "********************"
  write(*,*) "second try, the function drop_stops does what it 
promisses. If have to feed it with the right string"
  cAsString ="123.456,78"
  cWithOutStops = drop_stops(cAsString)
  read(cWithOutStops,*,decimal='comma')  c

  write(*,*) a,b,c

contains

FUNCTION drop_stops(instr) RESULT(outstr)
    CHARACTER(len=*), INTENT(in) :: instr
    CHARACTER(len=:), ALLOCATABLE :: outstr
    CHARACTER(len=1), DIMENSION(:), ALLOCATABLE :: str_array

    ALLOCATE(str_array(LEN_TRIM(instr)))
    str_array = TRANSFER(instr,str_array)
    str_array = PACK(str_array,str_array /= '.')
    ALLOCATE(CHARACTER(len=SIZE(str_array))::outstr)
    outstr = TRANSFER(str_array,outstr)
  END FUNCTION drop_stops
end program prjRead

Output

 gives c without decimal places            5   56.670000000000002        123456.00000000000  ********************  second try, the function drop_stops does what it promisses. If have to  feed it with the right string            5   56.670000000000002        123456.78000000000

HighPerformanceMarks 解决方案即将推出。我只是缺乏在阅读时将数字c一口吞下的技巧。或者我读取 2 个字符串并将它们连接起来。