I think stdlib should have printf() and fprintf() with C-style formatting

I don’t know if this will be an unpopular opinion. But I think stdlib should come with two functions, printf() and fprintf() with C-style string formatting strings:

program main
    use iso_fortran_env, only: stdout => output_unit
    use stdlib

    real :: x = 2.e-4

    printf("%e\n",x)
    fprintf(stdout,"%0.5f\n",x)
end program

I have been using Fortran for a decade, and I still can’t remember how Fortran string formatting works. I usually just avoid it and make do without it. I honesty find it quite difficult, and I think it is a barrier to adoption. Specially if you are hoping to bring in Python+NumPy users that already learned the C-style formatting strings.

Just a suggestion.

1 Like

I’m the other way around. I have been using Fortran formatting off and on for 60 years but I have to use C-style formatting with Maple, and it’s a pain.

6 Likes

@danielc, have a look at this existing stdlib issue: Additional format specification languages · Issue #340 · fortran-lang/stdlib · GitHub

This can’t work in Fortran. In C, both functions return an int (which people often discard). But in Fortran you can’t do this, as it doesn’t represent a valid statement.

In the stdlib thread I linked, I was thinking about a translator function:

write(output_unit,cfmt("%0.5f\n")) x

Mixing the syntaxes can also be confusing, as Fortran already adds a new line to a record, so the one in the format string could be omitted. Another downside of this solution is that the correctness of the format string with respect to the passed arguments, can only be checked at runtime. In the GNU C compiler, there is a compiler extension, __attribute__ ((format (printf, ...))), that instructs the compiler to do type-checking at compile time. But it’s of no help for cross-language calls, because when you call cfmt(...), I doubt there is a simple way to pass also the argument types. Perhaps, if you are willing to hack a bit on the compiler internals.

I think the better short-term solution is to put together a translation crib sheet or build a helper program (either command-line, or with a simple web interface). Maybe you could assemble a large collection of examples in C and Fortran, and train a custom language model. ChatGPT gets at least some format strings correct:

In Fortran, the equivalent format string for the C printf format string "%0.5f\n" in a WRITE statement would be:

WRITE(*, '(F0.5)') your_variable

Here:

  • F indicates that the variable is a floating-point number.
  • 0 indicates that the width should be determined automatically.
  • .5 specifies the number of decimal places.
2 Likes

Thanks!

That would be a huge improvement, IMO. Yeah, I see your point that \n doesn’t make sense. I think cfmt() should definitely exist.

EDIT: The rest of this post suggests adding *printf subroutines. But I think that’s not actually possible. Fortran can’t have functions with a variable number of arguments. Right? I’m keeping part of my old post in case I’m wrong.

[ … snip … ]

write(output_unit,cfmt("%0.5f")) x
call fprintf(output_unit,"%0.5f",x)

write(*,cfmt("%0.5f")) x
call printf("%0.5f",x)

write (result,cfmt("%0.5f")) x
call sprintf(result,"%0.5f",x)
call fprintf(result,"%0.5f",x) ! sprintf not truly needed.

[ … snip …- ]

In your Mar 16, 2021 comment on that thread you suggested the full word format(). I think that’s better.

write(output_unit,format("%0.5f")) x

I think this looks much cleaner and is easier to remember. It also looks more like the rest of modern Fortran, where words are spelled and not compressed.

Removing the reference to ‘C’ also gives you opportunity to deviate from C, and make it your own. You can add Python syntax, like the example you gave in that thread:

use iso_fortran_env, only: stdout => output_unit
write(stdout, format("Hello {}!\n")) "world"

And you can add whatever interesting changes you can dream up. For example, you could extend it to ALSO accept this idea by @gronki.

write(stdout, format("(A) has (I0) apples")) me,N
1 Like

Fortran can have a function or a subroutine with a variable number of arguments: some of them must be optional.

1 Like

Fortran doesn’t support variadic functions the way C does. Or at least not in subroutines in functions, the exception being the intrinsic min and max functions.

In a C printf call or a Fortran write statement, you can have an arbitrary number of variables:

write(*,fmt) a, b, c, ...

In Fortran you could try and mimic this up to a certain number of dummy arguments using unlimited polymorphic variables (class(*)) but it becomes complex and unwieldy at some point. You’d still be lacking the compile time type-checking of the format string, and such code wouldn’t be particularly efficient. Perhaps useful for some user-level messages, but not for reading or writings tons of data.

Note that the Fortran format string syntax also has things like (*(_)) (unlimited format repetition) or (_,:,_) (colon editing), where _ is a placeholder for a valid edit descriptor. For printing arrays in C you’d need to iterate through the elements, or use a function that assembles a string.

2 Likes

I get that the Fortran format strings are more powerful. I just can’t ever remember how they work :slight_smile:

But yeah. Those repetitions in Fortran strings are cool.

I may be wrong, but I have found nothing concerning Fortran formats by quickly scanning the Learn section of the site: Learn — Fortran Programming Language

A specific section about that important subject could be added, at least with the basics.

That apart, I don’t think Fortran formatting is more difficult to use or stranger than C formatting. It is just a question of habit and must be learned. The only really weird thing is the syntax between '( and )'.

1 Like

I think Fortran needs:

string :: s
s = "modern"
print *, fmt("{s} formatting style")

Result:

modern formatting style

Solutions from the 1970s are not any progress.

Dominik

3 Likes

I agree. But am I right that what you wrote is not doable with a library?

Then again, the LFortran team has already decided that it’s ok to extend and modify the language. Perhaps they can embrace that idea and add features like what you said, that require changes at the compiler level.

This wouldn’t be the first time that a compiler extends Fortran because the committee moves too slow.

`write(stdout,“(A,I0)”) me," has “,N,”

I don’t think the C formatting syntax is easier to learn, but I find it a bit easier to read (too many , and " in Fortran):

write(*, "(A,I0)") me," has ",N," apples"
write(*, "(2A,I0,A)") me," has ",N," apples"

printf("%s has %d apples") me, N

But none is really perfect… Similar to the @Gronki proposal :

write(*, "(A,I0)") "{me} has {N} apples"

or optionally:

write(*,*) "{me} has {N:I0} apples"

An escape character would be needed to be able to print curly brackets.

I agree.

By the way, in Fortran it would rather be:

write(*, "(2A, I0, A)") me," has ",N," apples"

But you can also write the following line which is a little closer to the C version:

write(*, '(A, " has ", I0, " apples")') me, N

But yes, still a lot of punctuation.

Totally agreed. I know it’s only a little thing, but honestly, figuring out formatting in Fortran when I first starting learning very almost put me off learning it for good. Very glad I stayed, but I wouldn’t be surprised if others get put off at first sight!

1 Like

I’m not sure about that. I actually don’t understand how that example works. It’s bewildering to me.

I understood your printf and your last line. I don’t understand how the middle one works. My mind is telling me that the right-hand-side is a string and I don’t understand why the write statement isn’t just “(A)”.

@gronki’s suggestion is presumably to avoid counting. This is a valid Fortran 2008 way to do it:

  character(:),allocatable:: s
  s = "modern"
  print *,s," formatting style"

Of course this would have been valid Fortran earlier than 2008 but not in 1977:

  character(*),parameter:: s = "modern"
  print *,s," formatting style"

If you don’t want to count characters, and you don’t want to use list-directed i/o, then another possibility is something like

character(:), allocatable :: me
integer :: N = 10
me = 'foo'
write(*, '(*(g0))') me, ' has ', N, ' apples'
end

Although some compilers prior to f77 supported quoted character strings, for example in format statements, the character type was introduced in f77. Only single quotes were allowed as delimiters, so strings with embedded quotes required some extra effort. F90 allowed both single and double quotes to be used, which was a big improvement. The g0 output format field is a more recent addition, f2008 I think.

The main feature that I would like to see is to allow g0, and i0, f0, e0, etc., as input format fields. This would be especially useful for nonadvancing i/o, where the data types and field widths are determined on-the-fly as the input is read and parsed.

Oops, indeed !

write(*, "(A,I0)") "{me} has {N} apples"

Ultimately, any formatted write statement is a string. Still, with such a syntax you would need a way decide how to format each individual variable between the { }

1 Like

I find the behavior when a format specification is shorter than the input/output list really, really weird. (Section 13.4 Note 2 in the 2023 standard)

If format control encounters the rightmost parenthesis of a complete format specification and another effective item is not specified, format control terminates. However, if another effective item is specified, format control then reverts to the beginning of the format item terminated by the last preceding right parenthesis that is not part of a DT edit descriptor. If there is no such preceding right parenthesis, format control reverts to the first left parenthesis of the format specification. If any reversion occurs, the reused portion of the format specification shall contain at least one data edit descriptor. If format control reverts to a parenthesis that is preceded by a repeat specification, the repeat specification is reused. Reversion of format control, of itself, has no effect on the changeable modes. The file is positioned in a manner identical to the way it is positioned when a slash edit descriptor is processed.

1 Like

I can not read it, my brain aches… :exploding_head: