Re: Desirable Usage of Fortran Modules

From: James Van Buskirk (not_valid_at_comcast.net)
Date: 03/12/04

  • Next message: James Van Buskirk: "Re: first time using direct access files: recl ??"
    Date: Fri, 12 Mar 2004 20:14:51 GMT
    
    

    "Ed Wells" <wellsed@wam.umd.edu> wrote in message
    news:692b4a0d.0403112206.199c2ca7@posting.google.com...

    > James, I've been admiring your signature, but I cannot for the life of
    > me understand how TRANSFER works. Could you please enlighten me?

    The usual place to look turns up some documentation:

    http://h18009.www1.hp.com/fortran/docs/lrm/lrm0344.htm#transfer_intrin

    We see that TRANSFER takes three arguments: SOURCE, MOLD, and the
    optional argument SIZE. The SOURCE argument is interpreted as
    an ordered bucket of bits and the MOLD argument tells the compiler
    how to interpret the bit-bucket.

    First example: suppose we want to cast a character array as
    a string. We can do this several ways:
    1) A couple of levels of subroutine call
    2) A function that returns a string
    3) TRANSFER!!

    module convert
       implicit none
       contains
          subroutine cast_sub1(x)
             character x(:)
             call cast_sub2(x,size(x))
          end subroutine cast_sub1

          subroutine cast_sub2(x,n)
             integer, intent(in) :: n
             character x(n)

             call cast_sub3(x(1),n)
          end subroutine cast_sub2

          subroutine cast_sub3(x,n)
             integer, intent(in) :: n
             character(n) x(1)

             write(*,'(1x,a)') x
          end subroutine cast_sub3

          function cast_fun(x)
             character, intent(in) :: x(:)
             character(size(x)) cast_fun
             integer i

             do i = 1, size(x)
                cast_fun(i:i) = x(i)
             end do
          end function cast_fun
    end module convert

    program transfer_ex
       use convert
       implicit none
       character mess(8)

       mess = (/'M','e','s','s','a','g','e','!'/) ! Yuck, looks like C
       write(*,'(a)') ' This is what it looks like in a ''(1x,a)'' format.'
       write(*,'(1x,a)') mess
       write(*,'(a)') ' Now the subroutine version:'
       call cast_sub1(mess)
       write(*,'(a)') ' Now the function version:'
       write(*,'(1x,a)') cast_fun(mess)
       write(*,'(a)') ' Now for TRANSFER!'
       write(*,'(1x,a)') transfer(mess,repeat('x',size(mess)))
    end program transfer_ex

    Output with LF95 5.70d:

    This is what it looks like in a '(1x,a)' format.
    M
    e
    s
    s
    a
    g
    e
    !
    Now the subroutine version:
    Message!
    Now the function version:
    Message!
    Now for TRANSFER!
    Message!

    In this case the action of TRANSFER is fairly transparent:
    we want to cast the mess array of character*1 elements

    mess = (/'M','e','s','s','a','g','e','!'/)

    as a single character*8 variable. Sometimes it's easier
    to think of a string a an array of character*1 because we
    can apply Fortran's powerful array transformational
    intrinsics to it and sometimes it's better to think of
    the string as a single long scalar character variable
    because we can then apply Fortran's string manipulation
    functions to it. We could get a character*8 version of
    our message by transferring from the original array to
    a character*8 scalar. The MOLD argument of
    repeat('x',size(mess)) is a character expression as long
    as mess has array elements. You can transfer from a
    long scalar character expression to a character*1 array
    by making the MOLD argument a character*1 array:

    transfer('Message!',(/'x'/))

    would be an array equal to what the mess array in our
    example above; that is:

    all(mess == transfer('Message!',(/'x'/)))

    would print as .TRUE.

    It's simple to envision the action of TRANSFER when both
    input and output are CHARACTER type variables, but life
    is much more interesting and non-transportable when
    variables of dissimilar types are involved. Here, I
    have a program that converts an input string to an
    array of double precision numbers:

    program makesig
       implicit none
       type charnode
          character x
          type(charnode), pointer :: next => NULL()
       end type charnode
       type(charnode), pointer :: head => NULL()
       type(charnode), pointer :: temp
       integer :: numchar = 0
       integer iostat
       character x
       integer, parameter :: dp = selected_real_kind(15,300)
       integer, parameter :: qp = selected_real_kind(30,1000)

       write(*,'(a)',advance='no') ' Enter the string:> '
       do
          read(*,'(a)',advance = 'no',iostat=iostat) x
          if(iostat /= 0) exit
          numchar = numchar+1
          allocate(temp)
          temp%x = x
          temp%next => head
          head => temp
       end do
       call step_2(head, numchar)
       contains
          subroutine step_2(head, numchar)
             type(charnode), pointer :: head
             integer, intent(in) :: numchar
             character(numchar) string
             integer, parameter :: nchars = size(transfer(1.0_dp,(/'x'/)))
             character(nchars*((numchar+nchars-1)/nchars)) full_str
             real(dp) full_num(len(full_str)/nchars)
             type(charnode), pointer :: temp
             integer i
             character(80) fmt
             integer, parameter :: ndigits = precision(1.0_dp)
             real(qp) quad
    ! real(dp) quad

             do i = numchar, 1, -1
                temp => head
                head => head%next
                nullify(temp%next)
                string(i:i) = temp%x
                deallocate(temp)
             end do
             full_str = string
             write(*,*) '#'//full_str//'#'
             full_num = transfer(full_str,full_num)
             write(fmt,'(a,i0,a,i0,a)') '(1x,d',ndigits+9,'.',ndigits+2,')'
             do i = 1, size(full_num)
                quad = full_num(i)
                write(*,fmt) quad-spacing(full_num(i))
                write(*,fmt) quad
                write(*,fmt) quad+spacing(full_num(i))
                write(*,'()')
             end do

          end subroutine step_2
    end program makesig

    Now, this program is kind of awkward due to compiler bugs:
    I don't always like the way LF95 prints out extra digits
    of a double precision variable and the latest version of
    CVF (6.6C3) has a bug that breaks on this code. Neither
    problem is due to TRANSFER, Richard! If you do manage to
    get it to work, however, the interesting part happens in
    subroutine step_2 after the line

    full_str = string

    where the user's input string has finally been converted
    to a character variable of length equal to the size of his
    input padded to the next highest multiple of a double
    precision variable's length in characters. The next line
    enables you to see the input with padding. Here's a
    sample run:

    Enter the string:> Secret message
     #Secret message #
     0.45379127063540210+218
     0.45379127063540220+218
     0.45379127063540230+218

     0.61143882726677190-153
     0.61143882726677200-153
     0.61143882726677220-153

    So how did it get these two numbers? First off, the
    input string had 14 characters or 14 bytes of ASCII
    and an IEEE-754 64-bit double precision variable
    occupies 8 bytes of memory, so we must round up to
    16 bytes by padding with two spaces. Having done
    this, let's look at what we have as a hex dump:

     S e c r e t m e s s a g e
    53 65 63 72 65 74 20 6d 65 73 73 61 67 65 20 20

    Since all my machines are little-endian, our array
    as hex numbers will be:

    6d20746572636553 2020656761737365

    For the first number, the high (sign) bit is zero, so
    the number is positive. Pull of the next 11 bits:
     B'11011010010' = 1746
    Subtract 1023 to get 1746-1023 = 723
    Now take the last 52 bits Z'0746572636553' = 127979059635539
    Divide by 2**52 to get 127979059635539/2**52 =
       0.028417059735450278523671840957832
    Add the implied one bit and multiply by 2**723 to get
    2**723*1.028417059735450278523671840957832 =
    4.537912706354022390361624462237e+217, quite close to
    the middle of the upper set of three numbers.

    The next number is also positive. B'01000000010' = 514
    514-1023 = -509
    Z'0656761737365' = 111494690992997
    111494690992997/2**52 = 0.024756794612778465491942370135803
    (1+0.024756794612778465491942370135803)*2**(-509) =
    6.11438827266772017821835579061e-154

    Good thing Fortran can do all of this so easily for us
    via TRANSFER, right? OK, now we can write a Fortran
    program that takes these double precision constants and
    decodes them back into ASCII:

    write(*,*) transfer((/0.4537912706354022d+218, &
    0.6114388272667720d-153/),(/'x'/));end

    We see in the above that SOURCE is an array of double
    precision literals that contains our encoded data in
    order. We had to add the 'd' exponents ourselves because
    the 'd' format descriptor can't do that. Then we make
    MOLD an array of character*1 literals so that the output
    will be an array of 16 characters that has the same
    bit pattern in order as the encoded data. Output:

    Secret message

    Hope this helps in your understanding of the most
    interesting (to me) low-level programming feature
    among the features new to F90. The spammers seem
    to experience a certain level of difficulty in
    figuring this out, but the Enlightened Reader
    either has a Fortran 95 compiler or can puzzle out
    the low-level details outlined above for himself.

    -- 
    write(*,*) transfer((/17.392111325966148d0,6.5794487871554595D-85, &
    6.0134700243160014d-154/),(/'x'/)); end
    

  • Next message: James Van Buskirk: "Re: first time using direct access files: recl ??"

    Relevant Pages

    • Help in Spanish translation of the description of UDFs
      ... functions of minimum / maximum values among elements of an array column. ... GETALLWORDS- Inserts the words from a string into a global dimensioned ... WORDTRAN- Searches a character string for occurrences of a first word, ... ARRAYSUM- Returns the sum of all or a specified range of numeric (and/or ...
      (microsoft.public.fox.helpwanted)
    • Re: Check for Common character sequence ( I will pay)?
      ... Do I need to return an array? ... You need to identify character sequences of 3 or more characters that appear ... in more than one string. ... and test each 3-character sequence that results. ...
      (microsoft.public.dotnet.framework)
    • Re: Check for Common character sequence ( I will pay)?
      ... Yes you are returning an array of FoundString objects. ... in more than one string. ... This means that you have to identify sequences 1 character at a time, ... Again, obviously, if the 3-character sequence doesn't match, neither will ...
      (microsoft.public.dotnet.framework)
    • Re: character*1(28) and character*28
      ... One is a character string. ... Note, by the way, that your dmmy argument is an assumed-size array. ... Mostly the compiler does not know how long it ...
      (comp.lang.fortran)
    • Re: reverse engineering a sig
      ... This was James Van Buskirk's sig on a recent post. ... bits as a character string. ... figure out what double precision values would have the right bits. ... This one obviously depends on character data being ASCII and double ...
      (comp.lang.fortran)