With the Intel compiler, I was able to read a floating point number from character string into an integer variable. Is this a bug the intended behavior for the Intel compiler?
I tested gfortran, lfortran, and llvm flang and they all raised a runtime error. This was causing problems because certain models weren’t running if they were compiled with gfortran etc.
Here’s an example program showing the issue.
program main
implicit none
character(4) :: tmpc = "10.2"
integer :: a
a = 0
read(tmpc, *) a
print *, a
end program main
A numeric field ends with a blank, a comma or semi-colon, with an end-of-record acting as a space. So that means the field is the entire numeric value. What can be in the field is
then dependent on the type being read, which is an INTEGER type. So the entire field should be readable by an Iw descriptor, where w is the length of the field. So it should be an error by that specification. List-directed I/O is replete with extensions though, so not surprised if it is stopping at the first character not allowed in an integer (the period) or reading the entire field as a float and then conforming it by truncation or rounding to a whole number (with what is given here, not sure which it is doing).
So my vote would be that it is non-standard to read a type INTEGER from that string.
As noted, list-directed I/O can vary significantly between programming environments.
A lot is left up to the implementer (intentionally). So using formatted I/O or
reading the line
s in as strings and parsing them will produce far more reliable
results.
It is not clear which behavior you prefer, but you can still use list-directed I/O
to read the first word on the line and then check that it is composed only of
adjacent digits ignoring leading and trailing spaces. The only oddity this would
produce is that a quoted value would work as well as an unquoted one.
program main
implicit none
character(256) :: tmpc = "10.7"
integer :: a, iostat
character(len=256) :: iomsg, word
! read first word from line
read(tmpc, *,iostat=iostat,iomsg=iomsg) word
if(iostat.ne.0)write(*,*)trim(iomsg)
a = 0
call checkit(word)
print *, 'A=',A
call checkit(' 11.99999999999 ')
call checkit(' 107 ')
contains
subroutine checkit(word)
! read first word and make sure just all digits still using list-directed I/O
character(len=*),intent(in) :: word
! check if all digits
if (verify(trim(adjustl(word)), '1234567890') == 0)then
print *,trim(WORD),'is all digits'
read(word,*) A
print *, 'good value A=',A
else
print *,'<ERROR>',trim(WORD),' is not all digits'
endif
end subroutine checkit
end program main
Another approach is to use list-directed I/O but read a float
variable and then round the result as desired.
If it matters whether the string was an integer or not
you can check the value to see if it is likely to be an integer,
but that is not reliable with fringe values.
A longer example to explore the dusty corners
program main
implicit none
character(256) :: tmpc = "10.7 22,33.3,39.999999 44 XXX"
integer :: a, iostat
character(len=256) :: ch, iomsg
real :: tmp(5)
a = 0
ch=repeat('X',len(ch))
! the same as the original except see what is left on line on ifx/nvfortran
read(tmpc, *,iostat=iostat,iomsg=iomsg) a,ch
if(iostat.ne.0)write(*,*)trim(iomsg)
print *, 'A=',a
print *, 'CH=',trim(ch)
! read as floats
read(tmpc, *,iostat=iostat,iomsg=iomsg) tmp
if(iostat.ne.0)write(*,*)trim(iomsg)
print *, 'Original string and resulting floats'
print *, 'TMPC= ',TMPC
print *, 'TMP= ',TMP
A=nint(tmp(1))
print *, 'nearest integer to first value is A=',a
A=int(tmp(1))
print *, 'truncated first float is A=',a
print *, 'distinguish between values very close to whole values and those that are not'
print *, 'NEAREST ',nint(tmp)
print *, 'INT ',int(tmp)
print *, 'CLOSE ENOUGH?',tmp-int(tmp)<2*epsilon(tmp)
end program main
In general, it gets complicated to rely on list-directed I/O to the
point that parsing it yourself is easier, particularly if writing a
production code.
For instance, note that 39.9999999 is seen basically as 40 in the above
program.
So do you want the other compilers to behave like ifx/nvfortran, or do
you want to get an error?
As a dusty corner, it appears unclear whether the compilers that do
not produce an error are reading up to the period or reading the entire
value and truncating it. Adding the “ch” variable to the read should
tell you just out of curiosity, but whichever behavior it would not be
required by the standard that I know of.
with gfortran …
Bad integer for item 1 in list input
A= 0
CH=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TMPC= 10.7 22,33.3,39.999999 44 XXX
TMP= 10.6999998 22.0000000 33.2999992 40.0000000 44.0000000
A= 11
A= 10
NEAREST 11 22 33 40 44
INT 10 22 33 40 44
CLOSE ENOUGH? F T F T T
Related materials
or more specifically
which has a number of procedures related to parsing numbers out of strings
To answer the original question, it is an extension, not a bug. The standard says:
When the next effective itemis of type integer or of an enumtype, the value in the input record is interpreted as if an Iw edit descriptor with a suitable value of w were used.
Note that this is not a rule that the standard requires to be enforced. Intel Fortran, like some other Fortran implementations, broadens this to allow a value of any numeric type to be read in using list-directed input to any other numeric type.
I’ll add a historical note that DEC Fortran, Intel Fortran’s ancestor, also allowed mixing of numeric and logical with list-directed input. Intel also allowed that for a while, but it no longer does by default. (You can enable that ill-conceived behavior if you wish using a compile option.)
I remember that feature. When reading an integer value, a .false. would read as 0 and a .true. would read as -1 (all bits set for twos-complement integers). I’m thinking this applied to namelist i/o too. That feature never bothered me one way or the other, but it was surprising to see it for the first time.
It bothered me as I was often the one who had to explain to customers why it was doing that, mainly when someone just typed T or F. I lobbied the team to disable that by default. There was also a behavior where just an E or D would be accepted as a real value - that got removed.
A number of compilers allow == and /= instead of .eqv. and .neqv. as operators
on LOGICAL argumentss
but if you overload == and /= where it is not supported you have to be very careful about operator precedence. The biggest problem with LOGICAL extensions is that several compilers allow them to be used in numeric expressions, but some expect the values 0 and 1, some -1 and 0, … and so on, leading to very different behavior without a warning message when compiling by default. And assigning integer values to logical variables get even less predictable as some will accept any integer and use it’s parity to determine what Boolean value you get, some use even and odd, some use 0 or -1 as one and every other value as the other, …
I’ve spent more than a small amount of time dealing with that as well.
Another problem with logicals when working with large arrays or even with masks used by intrinsics is they default to the same byte size as a default integer instead of the smallest, but that is easily dealt with and is standard behavior so that pales beside the problems caused by allowing logicals to be treated as integers.
program testit
logical, parameter :: T=.true., F=.false.
write(*,*)T+0, F+0
end program
At least two compilers still allow treating logicals as integers. They even
returned the same values. I would be very surprised if there are not more showing other numeric results.
Programmers have been asking for a 1-bit logical type since the 1980s when fortran 8x was being discussed. A 1-bit logical type is the smallest possible in size as far as information content.
I have always wanted an explicit, or maybe even an implicit, conversion function between logicals and integers to be defined by the language. I think it should apply to assignments and also to some expressions. The most important one probably being in the context of an array index. It would be very convenient within the language to simply write array(p), array(p,q), and so on where p and q are type logical and array is some arbitrary type: integer, real, complex, character, user-defined type, and so on.
The first time I used logicals they were bits or I thought they were. They used a lot less storage than using an integer that was zero or not zero. Too long ago to be sure without looking it up but either CDC/SCOPE2 or Cray/COS Fortran used a bit unless I dreamed it.
Mostly for quick porting I have overloaded operators to provide for operators allowing logical to be used as numbers, for INT() and REAL() to take logical arguments, and == and =/ to act like .eqv. and .neqv. as long as you make sure the expressions using them use parentheses to avoid problems with precedence between the operators. One incarnation (it depends on which processor you are trying to emulate) is in GitHub - urbanjost/M_overload: Common examples of overloading of intrinsics and operators · GitHub. But basically, MERGE() is not too painful for converting logicals to integers.
merge(-1,0,LOGICAL_EXPRESSION)
where you can reset whatever set of numbers you want for TRUE and FALSE.
Memory has gotten so much cheaper I do not come up against a wall as often where using a bit instead of at least a byte is the solution asa often in the past, but I would like to see a bit-sized LOGICAL as well.
The only compilers I know of that CDC offered which had a bit data type were for the STAR-100/CYBER-200 machines. The bits were used to conditionally control vector operations.
The DOE sites also had their locally written LRLTRAN and CIVIC compilers for CDC and Cray machines. These offered a more general bit field handling capability.
Why would someone write that? I don’t know, but they did and suffered for it. I have also seen many programs that used = to compare to .TRUE. or .FALSE..
Nicely done. The sad part is that several processors still allow that by default all these years later. I think that covers all the ones I was mentioning but with much better descriptions. That much trouble with a Boolean. Something that technically describes two states is a bit ironic
The first two are with explicit conversions, the last would be with implicit conversion. In any case, I would want the compiler to adopt whatever conventions would lead to efficient code, I would not want an actual function call to be invoked for every array index reference, I would just want the existing bits in p and q to be used more or less directly.
And a common extension makes it perhaps even more readable with
do while(k <= 2 .and. found == .false.)
but then you have to figure out if == for logicals has the precedencse of .EQV. or of .EQ.
My funniest memory from ALGOL is that I did not use C until after I had used ALGOL when working on a “legacy” code, and found the overuse of {} made scanning C code so difficult at first I was using
#define BEGIN {
#define END }
and so had very few parenthesis in my C code
which drove a C programmer looking over the code to froth at the mouth, or at least it seemed like it. Gave that up pretty quickly but I still prefer .eq. .lt. .gt., … over ==, <, >. Habits die hard I suppose.
Many languages use tokens like -ge (bash) or \ge (latex) for these operators. In fortran I also still use them a lot out of habit, especially in legacy codes where the symbols look out of place, but I tend to use the symbols for newer codes. The only fortran one I don’t like is /=, I still use .ne. mostly for that one. I do wish that fortran could have adopted the shorthand notation for increments and factors, +=, -=, *=, /=. I think that results in cleaner code. But now I think the .ne. symbol killed that possibility. I would have preferred the other path.
I think every fortran programmer has that Aha! moment when he realizes that (expression.eqv..true.) is the same as just (expression), and (expression.eqv..false.) is the same as (.not.expression). Of course, in this case, there is the precedence bug that arises too without the parentheses.