Omission in the Fortran 2023 Standard?

I think I discovered an inconsistency in the Standard for Fortran 2018 which seems to exist still in Fortran 2023 (but I’m relying on the working draft 24_007.pdf so that it’s possible that it has have been fixed by now).

In section 13.7.2.4 item 1 describing B, O, and Z editing it says the “effective item shall be of type integer, real, or complex…” but in section 13.4 item 5 it says that "except that an effective item of type complex requires the interpretation of two F, E, EN, ES, EX, D, or G edit descriptors. ". I think the list there should now include B, O, and Z descriptors for completeness.

This is, admittedly, a very trivial error and I don’t suppose that anyone in the history of the Universe so far has tried to transfer a complex data value using B/O/Z descriptors and not realised this, but it probably ought to be fixed all the same. If this is indeed an omission I’d be happy to report it but I’m not sure how or to whom.

Not in my opinion. The B, O, and Z edit descriptors do not interpret the bits of data; unlike the others you mentioned. 13.7.2.4 refers to the “internal value”, which is how the standard refers to the bytes of data in memory. While a value of complex type can be considered two reals in some contexts, it is just a string of bits as far as B, O and Z are concerned. (Same applies to TRANSFER, for example.)

2 Likes

Thanks Steve, and I see your point. But as far as I can see you still need two edit descriptors of B/O/Z for each complex number, and that’s what I think is missing from section 13.4 item 5. This is what a current compiler such as gfortran thinks is needed for a complex number: that is two B/O/Z edit descriptors for each complex data value, and that is also what common sense would suggest.

1 Like

No, sorry. B, O and Z formats work much like B, O and Z constants do - they have no type and are just a “bag of bits”. I discuss the BOZ constant issue in Doctor Fortran in “We’re All BOZos on This Bus” - Doctor Fortran

If gfortran thinks otherwise, it is wrong. 13.4 item 5 says, “except that an effective item of type complex requires the interpretation of two F, E, EN, ES, EX, D, or G edit descriptors.” B, O, and Z are not mentioned.

Yes, I fully accept your point that B, O and Z are different in that they are typeless. But as I pointed out section 13.7.2.4 item 1 describing B, O, and Z editing it says the “effective item shall be of type integer, real, or complex" so the Standard already agrees that a complex data value can be transferred via a B/O/Z data exit descriptor.

Then one looks in section 13.4 item 5 which is there because the normal rule is that every data item requires ONE data descriptor, but there is an exception for Complex which requires two of them. So if section 3.7.2.4 item 1 is correct that B/O/Z are valid for Complex data, then it’s clear that two of them are needed, just as two would be needed for any other data descriptor.

I still see an inconsistency: either 13.4 item 5 is wrong in failing to list B, O and Z as valid for Complex and therefore needing two descriptors just as for any other descriptor, or 13.7.2.4 item 1 is wrong in saying that B, O or Z can be used for complex data values. I don’t see that the typeless nature of B, O and Z affects at all the need for two of these per Complex data value.

Sorry to follow up my own post, but I forgot to thank you for your web pages and in particular your posting about B/O/Z constants which was very useful, although it really doesn’t deal with the B/O/Z edit descriptors question.

And I had another thought. If you think that a complex data value is “just a bag of bits” as far as writing it with a B/O/Z format descriptor is concerned, then presumably the same argument would apply to say a real array of two elements - or indeed of any number of elements. You would only need one B/O/Z descriptor for the whole array.

At present gfortran is the only compiler to which I have easy access, but I’m sorry to say that, as with complex, it also behaves as if one needs two B/O/Z data descriptors for a real array of two elements, just as for a complex scalar. This is consistent with my reading of the Standard. It would be nice to know what other compilers do.

Both Intel compilers: ifx (2025.0.1) and ifort (2021.13.1) apparently do the same as gfortran.

program m
  implicit none
  complex :: z=(1.0,1.0)
  print '("1:",(Z9.8))', z
  print '("2:",2Z9.8)', z
end program m

output

1: 3F800000
 3F800000
2: 3F800000 3F800000

so the Z edit descriptor is clearly reinterpreted in the first print.

IMHO, if even the compilers writers seem to follow the two descriptors for a complex value rule in case of BOZ decriptors, the Standard should probably include a straight clarification of the issue.

Hi Clive, if you are on a Windows platform there are several ‘free’ compiler options. Gfortran obviously, but also Intel and Salford/Silverfrost running natively under Windows. Additionally if you enable the Windows subsystem for linux you can install both the nvidia and amd compilers. Using hyper-v is also another option to access the ‘free’ linux compilers. Nag obviously costs money, but after the initial purchase Nag offer good annual mainteance rates. You can also ‘buy’ the Intel compiler, and their maintenace rates are pretty reasonable.

F2023 Draft section 13.7.2.3.7 may give some clarification, if interpreted “broadly”:

Complex editing A complex datum consists of a pair of separate real data. The editing of a scalar datum of complex type is specified by two edit descriptors each of which specifies the editing of real data. The first edit descriptor specifies the editing for the real part; the second specifies it for the imaginary part. The two edit descriptors may be different. Control and character string edit descriptors may be processed between the edit descriptor for the real part and the edit descriptor for the imaginary part.

Particularly interesting is the mention of possibly different descriptors for Re/Im parts and the possibility of adding control descriptors between Re and Im parts.

Thanks for this discussion - I see there is some room for disagreement here and will investigate further.

Thanks Ian. Yes at home I’m using Windows. I have used Silverfrost in the past but my experience was that gfortran was ahead of it in keeping up-to-date with the standards - but that may not always be true (I’d have to check your table of which compilers support which to be sure). This feature, using complex type with B/O/Z descriptors, was I think only introduced in Fortran 2008. Now that the Intel compiler appears to be free for home use, I may have a go at installing that as well.

But what actual compilers do in practice is something of a side issue; the real question whether there really is an inconsistency between two parts of the 2018 Standard. Fortunately rather it’s a rather trivial one, but it would be nice to get clarity on it all the same.

OK, I’ve tried both NAG and Intel compilers and both treat a complex value as two data items for formatted I/O using Z format. I’ve changed my mind and think the standard needs an edit here. Stay tuned.

2 Likes

NAG fortran gives the following runtime error on this program:

program boz
   complex :: z = (1.25,1.5)
   write(*,'(b64)') z
   write(*,'(b32)') z%re, z%im
end program boz

$ nagfor -f2018 boz.f90 && a.out
NAG Fortran Compiler Release 7.2(Shin-Urayasu) Build 7203
[NAG Fortran Compiler normal termination]
Runtime Error: boz.f90, line 3: Invalid edit descriptor for real i/o-list item
Program terminated by I/O error on unit 6 (Output_Unit,Formatted,Sequential)
Abort trap: 6

I think this is a standard conforming program. Any ideas what I’m doing wrong?

Malcolm Cohen of NAG responded to my question about this.

The “effect” is that there are no effective items of type complex in formatted i/o. This is specified by

13.7.2.3.7 Complex editing
A complex datum consists of a pair of separate real data. The editing of a scalar datum of complex type is specified by two edit descriptors each of which specifies the editing of real data. The first edit descriptor specifies the editing for the real part; the second specifies it for the imaginary part. The two edit descriptors may be different. Control and character string edit descriptors may be processed between the edit descriptor for the real part and the edit descriptor for the imaginary part.


The conclusion is that the standard properly specifies the behavior, and my earlier assertions that a single BOZ format item was sufficient were incorrect. No edits are needed.

3 Likes

I have told people in the past that printing a complex value C should
be treated exactly as if they were printing C%re and C%im instead (or
REAL(C) and CMPLX(C), depending on how long ago), and have used formats
along the lines of the one in the example below for a long time with a
lot of different compilers (back when you had to use a FORMAT statement
and the G descriptor did not even exist yet, but still something like
the one in the example) so good to hear!

When I jotted down the example program it occurred to me it seems odd that
a HYPOT() function exists now but it does not take complex values. That
seems more intuitive than using ABS() as a distance from the origin to
get that value.

Nice to see someone using complex values though. I have thought for
a long time the extensive support of complex values in Fortran was
underutilized, and that perhaps that was due to it rarely
being described in detail well in intrinsic descriptions.

I do not think I have ever seen a document detailing examples on how to
use complex values with the trig functions and such to handle phasors
and polar coordinates, for example. Do any books still in print cover
that? It might be a nice topic for a short book on fortran.lang or an
entry on the Fortran Wiki site.

The thing that used to trip me up the most about using complex types
was several major compilers that did not have a “double complex” and
I often needed the higher precision values. I THINK that is available
everywhere now, but not sure the standard mandates it has to be.

Anyway, seeing this discussion got me to thinking I should extend the info
on how complex values are used with the intrinsics in a few documents
of my own. If anyone knows of any nice examples or descriptions in the
public domain I would be interested in hearing of them.

EXAMPLE

program main
implicit none
character(len=*),parameter :: fmt='("hypotenuse(",g0.3,",i",g0.3,")=",g0.5)'
complex :: c
   c=(3.0,4.0)
   write(*,fmt) c,abs(c)
end program main

OUTPUT

hypotenuse(3.00,i4.00)=5.0000

No. “double complex” is non-standard, though it may be a widely-implemented extension.

FWIW, Malcolm (who is the editor of the standard) just wrote me to say:

I think that 13.4p5 should be corrected though, since as it stands, it contradicts.13.7.2.3.7.

Why does it even have a list? Just deleting the list would seem to be the obvious thing to do, viz “requires the interpretation of two data edit descriptors” (it needs to say “data” because there may be additional control edit descriptors in between).

I think the standard (since f90) has required that all the supported kind values are the same for real and complex types.

Good points. Personally, I think I always have at least an SP between the data descriptors.
I think the only time I do not is when I intend to read the values back in from the output file,

My favorite way of printing complex values is to make little functions
(typically in a module) that convert them to a string; as in the
following:

program main
implicit none
interface px; procedure px_s, px_d; end interface
complex :: c
integer :: i
   c=(3.0,4.0)
   ! converting complex values to s string lets you create simpler formats
   write(*,'(*(g0:,", "))')( px(i*c), i=1,3)      ! list of values
   write(*,'(*(g0:,", "))')[ px(c),px(c),px(c)] ! array
contains

function px_d(value) result(string)
! function to write complex value into a string using a format
complex(kind=kind(0.0d0)),intent(in) :: value 
character(len=80)                    :: line 
character(len=:),allocatable         :: string 
   write(line,'("(",g0,SP,g0,"i)")')value
   string=trim(line)
end function px_d

function px_s(value) result(string)
! function to write complex value into a string using a format
complex(kind=kind(0.0)),intent(in)   :: value 
character(len=80)                    :: line 
character(len=:),allocatable         :: string 
   write(line,'("(",g0,SP,g0,"i)")')value
   string=trim(line)
end function px_s

end program main
(3.00000000+4.00000000i), (6.00000000+8.00000000i), (9.00000000+12.0000000i)
(3.00000000+4.00000000i), (3.00000000+4.00000000i), (3.00000000+4.00000000i)

I know the first time I formatted complex values I was a little surprised by how it works and was looking for descriptors specific to complex types, partly because list-directed and NAMELIST output wrote them looking like “(x,y)” and I was looking for a descriptor to match. So I think the edit is warranted on BOZ usage myself, but actually do wish there was a descriptor like “C” that would print it all like list-directed basically does with the parenthesis and comma. That would make it a lot easier to read in as well if there were a standard format defined by the descriptor.

Should the HYPOT() function take one or two complex value arguments? The single-argument case is equivalent, as you point out, to ABS(Z), while the two-argument function would be equivalent to ABS(Z1-Z2), i.e. giving the distance between the two complex points Z1 and Z2. I guess it is a matter of taste as to whether ABS or HYPOT is the most intuitive for these complex cases.

1 Like

Put that way, maybe that is why HYPOT() does not support complex values. ABS() always operates on a single value, so if given a singe complex value it calculates the distance to the origin. HYPOT() takes two reals, so it might seem more natural, perhaps, two take TWO complex values as well. I was thinking HYPOT(C) should be equivalent to HYPOT(C%re, C%im); but you are right to point out HYPOT() does not otherwise take a single value. You have pointed out a good valid reason for it not to, but I still would like HYPOT(C) so do the same as ABS(C). Guess I can overload it :astonished: :grinning: