FORTRAN 77 - packing 8 a1 characters into an integer*8 - fortran

An odd issue here. I am converting legacy F77 code that packed 8 a1 characters into a 64 bit integer, and the code worked back in the day. But in today's world to get 8 characters in a word I need to move to integer*8 variables, and the code is failing in that case. It works until it's packed 4 characters, but character 5 simply rotates around and overwrites the first packed character. Here is a sample output for "hello world”, of note is that the integer*8 variable OUTBUF suddenly shrinks and appears to convert to an integer*4 variable too:
hello world
in='hello world '
cn= 8,k=1,j='h',outbuf(k)='h '
cn=16,k=1,j='e',outbuf(k)='he '
cn=24,k=1,j='l',outbuf(k)='hel '
cn=32,k=1,j='l',outbuf(k)='hell'
cn=40,k=1,j='o',outbuf(k)='oell'
# SPACE
cn= 8,k=2,j='w',outbuf(k)='w '
cn=16,k=2,j='o',outbuf(k)='wo '
cn=24,k=2,j='r',outbuf(k)='wor '
cn=32,k=2,j='l',outbuf(k)='worl'
cn=40,k=2,j='d',outbuf(k)='dorl'
# SPACE
I've distilled the code to this snippet, anyone know F77 anymore and see what’s happening? thanks.
program cow
implicit integer*8 (a-z)
integer inbuf(72)
integer*8 outbuf(40)
c changing the integer*1 to integer*8 fixes the problem !!!!!!!!!
integer*1 j
read 99, inbuf
99 FORMAT(BZ,72a1)
print 1, inbuf
1 format("in='",72a1,"'")
IP=0
k = 1
DO 100 I=1,40
OUTBUF(I)= 8H
100 CONTINUE
200 IP=IP+1
IF(IP.GT.72) GO TO 6000
J= INBUF(IP)
IF(J.EQ." ") GO TO 6000
CN = (MOD(CP,8)+1) * 8
outbuf(k) = outbuf(k) .and. (.not. lshift('377'O, (cn-8)))
OUTBUF(K) = OUTBUF(K) .OR. lSHiFT( j,(CN-8))
print 4301, cn, k, j, outbuf(k)
4301 format("cn=",i2,",k=",i1,",j='",a1,"',outbuf(k)='",a8,"'")
CP=CP+1
GO TO 200
6000 continue
print *,'# SPACE'
call exit
end
Note that this code is for little endian machines.

" So ignore the code, the task at hand - in FORTRAN 77 - is to pack 8
a1 characters into an integer*8 variable. "
Ok, but I will have to wash my hands after this:
INTEGER*8 i
CHARACTER*8 ch
EQUIVALENCE (i, ch)
ch = "abcdefgh"
Of course, transfer is nicer, but certainly not FORTRAN 77.

This is quite horrible code to do something trivial in newer Fortran
(since F77...).
You can replace the whole routine by something like
integer, parameter :: llen=72
character(len=llen) inbuf
character(len=llen) output
integer :: i,k
read (*,'(BZ,A)') outbuf
k = 1
do i=1,llen
if (inbuf(i) /= ' ') then
outbuf(k) = inbuf(i)
if (mod(k,8) == 0) then
! Some output goes here
endif
k = k + 1
end if
end do

OK, here is the second answer; not preferred, it is really better to rewrite the program using characters.
Caveat: integer*8 is non-standard.
program main
character(len=8) :: c
integer*8 output(10)
c = "12345678"
call foo(output,10,c)
write (*,'(Z16)') output(1)
end program main
subroutine foo(a,n,b)
integer*8 :: a(1)
character(len=8) :: b
a(1) = transfer(b,mold=a(1))
end subroutine foo

As I mentioned, this input parser took Hollerith data and compared it against Hollerith data in a database, in a case-insensitive manner. The code was written for 60-64 bit, big-endian machines, and used hardware specific shifts, masks, ANDs and ORs to pack the A1 input characters into an A8 INTEGER word for comparison against the database.
And although I did manage to adjust all those shifts, masks and bitwise operations to work on 64-bit little-endian machines (x86_64), it was a PITA, and only worked with pgf77 and ifort, gfortran was having none of that crap! So this is what I came up with, much nicer, hardware independent, and works on all the compilers available to me:
The parser still accepts A1 input characters stored in INTEGERs (80a1), and returns packed A8 characters in INTEGER*8 words, for backwards compatibility.
It converts 80a1 input data to CHARACTER*80 variable IN0:
write( in0, '80a1' ) (inbuf(i), i=1,80)
Since this is all ASCII data, I used this code to lower-case everything in IN0 to a new character*80 variable IN:
do i = 1, len(in0)
#if (! defined __GFORTRAN__)
j = ichar(in0(i:i))
if (j>= ichar("A") .and. j<=ichar("Z") ) then
in(i:i) = char(ichar(in0(i:i))+32)
else
in(i:i) = in0(i:i)
end if
#else
j = iachar(in0(i:i))
if (j>= iachar("A") .and. j<=iachar("Z") ) then
in(i:i) = achar(iachar(in0(i:i))+32)
else
in(i:i) = in0(i:i)
end if
#endif
end do
It was then a simple matter to loop through the CHARACTER*80 variable IN, collect non-space characters into the character variable WORD, and then encode them into INTEGER*8 array OUTBUF:
read( word(1:8), ‘a8' ) outbuf(outbufc)
Thanks all.

Related

Tabular output in Fortran, that is robust against unexpected values?

In Fortran, is it possible to print data in a tabular manner, without losing information, when more space is needed than specified?
For instance consider the program
! format.f90
program main
real(8) :: arr(5)
arr = [0.0, 1.111, 22.22, 333.3, 444444444444444444.44]
print '(F10.3)', arr
end program main
Then by default the output for the last entry will be replaced by stars, indicating the lack of space.
>> ifort format.f90 -o format.bin
>> ./format.bin
0.000
1.111
22.220
333.300
**********
By comparison, C-style format specifiers automatically increase the column width when required, e.g.
// format.c
#include <stdio.h>
int main () {
double arr[5] = {0.0, 1.111, 22.22, 333.3, 444444444444444444.44};
for(int i=0; i<5; i++) {
printf("%10.3f\n", arr[i]);
}
}
>> gcc format.c -o format.bin
>> ./format.bin
0.000
1.111
22.220
333.300
444444444444444416.000
Is it possible to obtain such behavior in Fortran with built-in features?
Options, that don't fulfill the requirements
G descriptor. The G descriptor allows reliably outputting data in a tabular well-readable format and automatically adds exponentials when needed. However, it also wastes space if the exponentials are not needed and it doesn't line up the comma. For example, when switching F10.3 for G11.4,"¶" (paragraph sign added for emphasis):
>> ifort format.f90 -o format.bin
>> ./format.bin
0.000 ¶
1.111 ¶
22.22 ¶
333.3 ¶
0.4444E+18¶
Building a formatting API based on the F0 specifier. The specifier F0.3 would allow variable-width output, but doesn't allow specifying a minimum width. This could be solved using a wrapper function akin to leftpad, but a built-in or widely-used solution would be preferable for a better chance of actually being used in a codebase. As an example:
! format.f90
program main
real(8) :: arr(5)
integer :: i
arr = [0.0, 1.111, 22.22, 333.3, 444444444444444444.44]
! more complicated print statement, because 'float2char'
! cannot be 'elemental' due to needing the 'alloctable' property.
print '(A)', (float2char('(F0.3)', 10, arr(i)), i=1,5)
contains
function float2char(format, width, value) result(r)
character(:), allocatable :: r
character(*), intent(in) :: format
integer, intent(in) :: width
real(8), intent(in) :: value
character(64) :: buffer ! better: calculate size from value?
write(buffer, format) value
allocate(character(max(width, len_trim(buffer))) :: r)
r(:) = trim(buffer) ! (:) needed to prevent reallocation in recent compilers
r(:) = adjustr(r)
end function float2char
end program main
>> ifort format.f90 -o format.bin
>> ./format.bin
.000
1.111
22.220
333.300
444444452740661248.000
Yes, so in Fortran the fixed-width edit descriptors really are FIXED width. Sometimes useful, often annoying.
One thing you can do is to use the G edit descriptor, which is similar to %g in C, namely that it switches to scientific format when the number is large or small. That allows very large or small values to fit in a fixed width field. Note however that with G editing the d is the number of significant digits, not the number of digits after the decimal point as with F editing. Also it leaves space at the end for the exponent even if the number is in the range that no exponent is needed.
Your example could look like
! format.f90
program main
real(8) :: arr(4)
arr = [0.0, 1.111, 222222222222.222, 3.333]
print '(F10.3)', arr
print *, 'With G edit'
print '(G10.4)', arr
end program main
with output
0.000
1.111
**********
3.333
With G edit
0.000
1.111
0.2222E+12
3.333

How to make two large enough integer(kind=4) add together to be stored as integer(kind=8)?

I'm trying to add two integers(kind=4) that can be large enough to produce an integer(kind=8). I'm not sure if this is possible, but I made a few tests trying to make it work:
!gfortran, gcc version 5.4.0 20160609
program hello
use iso_fortran_env
integer, parameter :: i64 = int64
integer(kind=4) :: a1, a2
integer(kind=8) :: b1, b2
integer(kind=8) :: c
integer(kind=8) :: ugly
a1 = 2023123123 !kind(4)
a2 = a1 !kind(4)
b1 = 2023123123 !kind(8)
b2 = b1 !kind(8)
! sum integers of kind=8 that surely will lead to a kind=8
c = b1+b2
print*,c
! sum integers of kind=4 and kind=8 that will lead to a kind=8
c = a1+b1
print*,c
! sum integers of kind=4 which may lead to a kind=8
c = a1+a2
print*,c
! try to tell gfortran to make a kind(4) behave as a kind(8)
! for that operation
c = a1+a2_i64
print*,c
! ugly workaround fail - 64 bit 0 on last position
ugly = 0
c = a2+a1+ugly
print*,c
! ugly workaround ok - 64 bit 0 on first position
ugly = 0
c = ugly+a2+a1
print*,c
! ugly workaround ok - divide in two operations
c = a1+ugly
c = c+a2
print*,c
end program hello
The output of the script is
4046246246 ! kind(8) + kind(8) = kind(8) -> ok
4046246246 ! kind(4) + kind(8) = kind(8) -> ok, but not sure if it always work
-248721050 ! kind(4) + kind(4) = kind(8) -> NOT OK
2023123072 ! kind(4) + kind(4)_i64 = kind(8) -> NOT OK
-248721050 ! ugly workaround summing 0(kind=8) -> FAIL
4046246246 ! ugly workaround summing 0(kind=8) -> OK
4046246246 ! another ugly work around -> OK
Does anyone know how if its possible to add two integer(kind=4) resulting in a integer(kind=8) WITHOUT that very ugly workaround?
If a and b are integers then the expression a+b is an integer. If a and b of the same kind, then the expression is of that kind. If they are of different kind but one has greater decimal exponent range then the expression is of that kind.
If a and b are of the same kind as a+b then there is no conversion. An operand (a or b) which is of different kind from a+b is treated as though it is converted to that kind.
So, if you want the result to be of kind 8 (assuming that is the one of greater range) then you need one (or both) of the operands to be of kind 8. In the case of the question:
b1+b2 both are kind 8;
a1+b1, b1 of kind 8, a1 converted to that kind;
a1+a2, both are kind 4, not converted, result of kind 4.
In the case of a+b+c then the expression is treated as (a+b)+c:
a2+a1+ugly, a2+a1 has no conversion in its evaluation, but is converted for sum (a2+a1)+ugly
ugly+a2+a1, ugly+a2 has a2 converted to kind 8, then a1 converted to kind 8 to give result of kind 8.
So, if a2+a1 is not valid for range then a2+a1+ugly has that same problem, but ugly+a2+a1 has both a1 and a2 treated as kind 8.
Finally:
int(a1,8)+int(a2,8) has explicit conversion to kind 8;
int(a1,8)+a2, a1+int(a2,8) has the expected behaviour.
All of these are ugly: don't use kinds like 4 and 8, especially if you have int64 available and behaving as you want.

Defining identity matrix in fortran?

I came across this F77 program for writing unit matrix:
DO 10 I=1, M
DO 10 J=1, N
IF(I .EQ. J) THEN
UNITM(I,J)=1.0D0
ELSE
UNITM(I,J)=0.0D0
END IF
CONTINUE
What is the purpose of 10 and .0D0 in the above program?
As mentioned in the comments, the 10 is a line label that is the scope of the do loop. Now we would have an enddo statement. You are missing the 10 above though. The continue line should look like:
10 CONTINUE
With modern Fortran we would have 2 enddo statements. With line labels, both do loops can end on the same line.
When Fortran compiles literal numbers, Fortran surmises the number type. A '1' gets compiled as an integer. A '1.0' gets compiled as a single precision real number. Fortran recognized scientific notation so '1.0e3' is 1x10^3 but in single precision!
Substitute the e with a d and Fortran will compile the literal number as double precision! Don't need to have an exponent? Put a 0 for the exponent. So 1.0D0 compiles a 1 as a double precision real number.

Concatenate two integers

What is the best way to concatenate two integers to an integer in Fortran?
integer a = 999
integer b = 1111
integer c should be 9991111
Thanks,
SM.
Here is an example code that does what you need. It writes integers into character strings, trims and concatenetes them, and then reads the result integer from concatenated character string:
integer :: a,b,c
character(len=99) :: char_a,char_b,char_c
a = 999
b = 1111
write(unit=char_a,fmt=*)a
write(unit=char_b,fmt=*)b
char_c = trim(adjustl(char_a))//trim(adjustl(char_b))
read(unit=char_c,fmt=*)c
print*,c
end
Edit: Note that this example is general for any integer lengths, assuming they fit into their respective kind (no integer overflow).
You can use the information of the order of the number:
integer :: a = 999
integer :: b = 1111
integer :: c
c = a * 10**(ceiling(log10(real(b)))) + b
write(*,*) c
Your best bet is to use internal files to convert your two integers to a character, and then convert this back to an integer.
There is no intrinsic procedure for converting a numeric value to a character/string representation. See this discusson on Fortran Wiki for more information (see the part headed "Note").
As an example, in your case you could use the following:
program test_conversion
implicit none
integer :: a=999
integer :: b=1111
integer :: c
character(len=7) :: temp
write(temp, '(i3.3, i4.4)') a, b ! You may need to change these format specifiers
read(temp, *) c
print*, c ! This prints 9991111
end program test_conversion
You will have to change the format string if you want different widths of the character representation of your integers.

How do I handle logical statements being applied to integer values with gfortran?

I'm rewriting some code to make a program compile with the gfortran compiler as opposed to ifort compiler I usually use. The code follows:
_Subroutine SlideBits (WORD, BITS, ADDR)
Implicit None
Integer(4) WORD
Integer(4) BITS
Integer(4) ADDR
Integer(4) ADDR1
ADDR1 = 32 - ADDR
WORD = (WORD .And. (.Not.ISHFT(1,ADDR1))) .Or. ISHFT(BITS,ADDR1)
End_
When I compile the above code using the gfortran compiler, I recieve this error:
WORD = (WORD .And. (.Not.ISHFT(1,ADDR1))) .Or. ISHFT(BITS,ADDR1)
Error: Operand of .NOT. operator at (1) is INTEGER(4)
All three of the variables coming into the subroutine are integers. I've looked around a bit and the gfortran wiki states that the gfortran compiler should be able to handle logical statments being applied to integer values. Several other sites I've visited either quote from the gnu wiki or agree with it. This is the first time I've seen this error as the Intel Fortran compiler (ifort) I normally use compiles cleanly.
The comments/answers above "may .Not. be" the correct responses, depending on your ultimate objective.
The likely purpose of that "WORD = .." statement is .NOT. to arrive at a boolean/logical result, but rather to obtain a kind of integer enumerator.
To see this, first "ignore" the bit shifting (iShift() etc), and just look at something like IntR = Int1 .Or. Int2. This will produce a "proper" integer result. The value will depend on not only the values of the int's, but also on their declared "type" (e.g. Integer(1), Integer(2), etc)
That is, the resulting value of WORD will be a "proper" integer; something like "33504" .. or whatever, (likely) .NOT. a 0/1 or -1/0 or .True./.False. etc
If you replace = Int1 .Or. Int2 with = (Int1 /= 0) .Or. (Int2 /= 0) ... you will get an "integer logical" (i.e. 0/1 etc) and WILL NOT produce the
desired enumerator ... if that is what you are looking for.
The .Or. on two Int's is a kind of bit-wise addition that produces a new num based on how the bits align/word size etc.
e.g. 3 == 011, 2 = 010 ... so, 3 .Or. 2 ==> 011 = 3
e.g. 3 == 011, 5 = 101 ... so, 3 .Or. 5 ==> 111 = 7
e.g. 5 == 101, 5 = 101 ... so, 5 .Or. 5 ==> 101 = 5
... similarly the .And. provides a kind of multiplication.
This technique is sometimes used to create enumerators somewhat like the use of powers of two (1,2,4,8...) are used to assign a value. Then, any sum of those
values can be decomposed, for example, into its constituent elements. For instance, if a(1) = 2, and a(2) = 8, then the sum 10 can be decomposed to
show the selections were the 1st and 4th elements of (1,2,4,8,...) etc.
It may help conceptualise this by noting that bit-shifting is like multiplying by 2 (for left shift) and dividing by 2 (for right shift).
BTW, you don't need to restrict to Fortran for this. Whack it into a VBA function and see the result in your spreadsheet VBA does not
have bit shift intrinsics, but they are available ... in any case it will demonstrate the Int1 .Or. Int2 behaviour even without bit shifting, such as
Function TwoIntsOr(Int1 As Long, Int2 As Long) As Long
'
TwoIntsOr = Int1 Or Int2
'
End Function
-- .Or. in Fortran
Function TwoIntsOr(Int1, Int2)
Integer :: TwoInstOr
Integer, Intent(In) :: Int1, Int2
!
TwoIntsOr = Int1 .Or. Int2
!
End Function
).
It is not standard Fortran to apply logical/boolean operators to integer variables. If the goal is a boolean result, the ideal solution would be to convert the types to logical. If, as it appears from casual examination, the code is really doing bit-wise operations, then it would be better to use the IAND and IOR intrinsic functions.
gfortran is expecting booleans for the logical operators and the code is providing integers. Use comparisons with zero instead of logical operators.
WORD = ((WORD /= 0) .And. (ISHFT(1,ADDR1) == 0)) .Or. (ISHFT(BITS,ADDR1) /= 0)
gfortran and ifort use different representations for .true. and .false. values, so it's best to stick to booleans when that's what the code needs. In a conversion from ifort to gfortran I got bit by the former representing .true. as -1 and the latter using 1 for the same purpose, instead of the traditional (C-like) not 0.