Форматный ввод-вывод и дескрипторы формата

Форматный ввод-вывод даёт точный контроль над тем, как числа и текст превращаются в символы на бумаге, в файле или на экране.

Дескриптор формата — код в строке формата (например F8.3 или I5), задающий, сколько символов отвести под значение и как его представить: ширину поля, число знаков после запятой, форму записи.

Зачем нужен явный формат

Fortran создавался для печати таблиц чисел, и управление их видом — его историческая сильная сторона. Когда вы выводите результат расчёта в отчёт, в файл для другой программы или в колонки на экран, вам важно: сколько знаков после запятой показать, как выровнять столбцы, не «съедет» ли вёрстка на больших значениях. Списочный вывод (print *) на это не способен — он сам решает, как печатать, и результат зависит от компилятора. Форматный вывод (print '(...)' или write(unit, '(...)')) отдаёт контроль вам: вы пишете строку формата из дескрипторов, и каждое значение печатается строго по правилу. Это критично для воспроизводимых отчётов и для файлов фиксированного формата, которые читают другие инструменты.

Анатомия строки формата

Строка формата — это список дескрипторов в круглых скобках, разделённых запятыми. Каждый дескриптор отвечает за одно значение (или за пробелы/переход строки). Самые употребительные дескрипторы данных:

ДескрипторДля чегоПример
Iwцелое в поле ширины wI5 → " 42"
Fw.dвещественное, d знаков после точкиF8.3 → " 3.142"
Ew.dнаучная запись (мантисса+экспонента)E12.4 → " 0.3142E+01"
ESw.dнаучная с одной значащей цифрой до точкиES12.4 → " 3.1416E+00"
Aw / Aстрока (ширины w или по длине)A10
Lwлогическое (T/F)L2
nXn пробелов3X
/переход на новую строку/

Здесь w — общая ширина поля в символах, d — число цифр после десятичной точки. Если число не помещается в ширину w, Fortran заполняет поле звёздочками (****) — это сигнал «увеличьте ширину», а не молчаливая порча данных.

Практические примеры вывода

Формат можно задать прямо в операторе или вынести в отдельный оператор format с меткой. Сравним.

program demo_fmt
  implicit none
  integer :: n = 42
  real    :: pi = 3.14159265, big = 1.5e6

  ! формат прямо в операторе:
  print '(A, I5)', "n = ", n
  print '(A, F8.3)', "pi = ", pi
  print '(A, ES12.4)', "big = ", big

  ! таблица из двух колонок:
  print '(I4, 2X, F10.4)', n, pi

  ! формат в отдельном операторе с меткой:
  write(*, 100) "итог", pi
100 format(A6, 1X, F9.4)
end program demo_fmt

Вывод:

n =    42
pi =    3.142
big =   1.5000E+06
  42       3.1416
  итог    3.1416

Обратите внимание: F8.3 дало ровно три знака после точки и выровняло число по правому краю восьмисимвольного поля. ES12.4 вывело в нормализованной научной форме — одна значащая цифра перед точкой, что удобно для очень больших и малых чисел. Разница E и ES: классический E ставит ноль перед точкой (0.1500E+07), а ES — первую значащую цифру (1.5000E+06); второе привычнее инженеру.

Повторители и группы

Чтобы не повторять одинаковые дескрипторы, перед ними ставят повторитель — число. 3F8.2 означает три подряд поля F8.2. Группу дескрипторов в скобках тоже можно повторять: 2(I3, F6.1) — дважды пара «целое + вещественное». Это незаменимо при печати массивов и таблиц.

real :: row(4) = [1.1, 2.22, 3.333, 4.4]
print '(4F8.3)', row          ! четыре поля по F8.3
print '(A, 3(1X, I4))', "id:", 10, 20, 30

Вывод:

   1.100   2.220   3.333   4.400
id:   10   20   30

Если значений в списке вывода больше, чем дескрипторов в формате, Fortran переиспользует формат, переходя на новую строку — это называется реверсией формата. Так одной короткой строкой формата печатают длинный массив: формат «оборачивается» нужное число раз.

Форматное чтение

Те же дескрипторы работают и на чтение. read '(I5, F8.3)', k, x прочитает из входа целое из первых 5 позиций и вещественное из следующих 8. Форматное чтение полезно для файлов фиксированной ширины колонок (научные данные часто хранятся именно так). Однако оно требовательно: позиции должны точно совпадать. На практике для чтения чаще берут списочный ввод (read *, k, x), который сам разбирает значения по разделителям, а форматное чтение применяют там, где формат жёстко фиксирован стандартом данных.

Как работает под капотом

Форматный ввод-вывод — это процесс редактирования: библиотека времени выполнения проходит по списку значений и по списку дескрипторов одновременно, преобразуя каждое значение в последовательность символов согласно текущему дескриптору. Внутреннее двоичное представление числа (например, IEEE-754 double) переводится в десятичную строку с округлением до заданного числа знаков. Это объясняет две вещи. Во-первых, форматный вывод дороже неформатного: конверсия двоичное→текст требует вычислений и округления. Во-вторых, он теряет точность: F8.3 печатает лишь 3 знака, остальные отбрасываются, и обратное чтение не восстановит исходное число. Поэтому для точного сохранения чисел между прогонами программы используют неформатный (бинарный) вывод — о нём отдельный урок, — а форматный оставляют для человекочитаемых отчётов.

Внутренние файлы: форматирование в строку

Дескрипторы формата работают не только с внешними файлами и экраном, но и с обычными символьными переменными — это называется внутренним файлом (internal file). Если в операторе write указать вместо номера юнита символьную переменную, форматированный вывод пойдёт не на диск, а в эту строку. Зеркально, read из символьной переменной разбирает её содержимое по формату. Это незаменимый инструмент для двух задач: преобразования числа в строку (и обратно) и построения сообщений с подставленными значениями.

character(len=32) :: buf
integer :: n = 42
real :: x = 3.14159

! число -> строка (как itoa/sprintf):
write(buf, '(I0)') n              ! I0 — минимальная ширина
print *, "buf = [", trim(buf), "]"

! собрать имя файла с номером:
character(len=64) :: fname
integer :: step = 7
write(fname, '(A, I4.4, A)') "output_", step, ".dat"
print *, trim(fname)             ! output_0007.dat

! строка -> число:
character(len=*), parameter :: s = "123"
integer :: parsed
read(s, *) parsed
print *, "parsed + 1 =", parsed + 1

Вывод:

 buf = [42]
 output_0007.dat
 parsed + 1 =          124

Особенно полезен дескриптор I0 — целое минимальной необходимой ширины (без ведущих пробелов), и I4.4 — целое в поле ширины 4 с дополнением нулями слева (даёт 0007), что идеально для нумерованных имён файлов кадров симуляции. Внутренние файлы — это Fortran-аналог sprintf/sscanf из C, и они закрывают почти все потребности в форматировании строк, не требуя никаких внешних библиотек.

Управление округлением и знаком

Помимо дескрипторов данных, формат содержит управляющие дескрипторы, тонко настраивающие представление. Они особенно важны, когда отчёт должен соответствовать строгому стандарту или быть единообразным между платформами. Несколько практически важных:

ДескрипторДействие
SPпечатать знак + у положительных чисел
SSподавить знак + (по умолчанию)
RU / RDокругление вверх / вниз
RNокругление к ближайшему (банковское)
ENинженерная запись (экспонента кратна 3)
Tnперейти к колонке n (табуляция)

Дескриптор EN заслуживает особого внимания в инженерном контексте: он даёт экспоненту, кратную трём (103, 106, 10-9), что соответствует приставкам СИ (кило, мега, нано) — 1.5e6 печатается как 1.5000E+06, удобно сопоставляясь с «1,5 МГц». Управление округлением (RU/RD/RN) важно для воспроизводимости: разные платформы по умолчанию могут округлять последнюю цифру по-разному, и явное указание режима устраняет расхождения в отчётах. Дескрипторы позиционирования (Tn, TLn, TRn) позволяют ставить значения в точные колонки, что критично для генерации файлов фиксированного формата, читаемых сторонними программами по позициям. В сумме система дескрипторов формата делает Fortran непревзойдённым инструментом для построения именно табличного, точно сверстанного числового вывода — задачи, ради которой язык когда-то и создавался и в которой он по сей день удобнее многих современных языков с их менее выразительными средствами форматирования.

Форматный вывод как мост между расчётом и человеком

Стоит осмыслить роль форматного ввода-вывода в более широком контексте, потому что она объясняет, почему Fortran уделяет ему столько внимания. Любой научный расчёт в конечном счёте должен сообщить свои результаты человеку или другой системе, и форматный вывод — это мост между миром двоичных чисел внутри программы и миром читаемых таблиц, отчётов и файлов снаружи. От качества этого моста зависит, насколько результаты пригодны к использованию. Плохо отформатированный вывод — съехавшие колонки, обрезанные звёздочками числа, непредсказуемое число знаков — обесценивает даже безупречный расчёт: результаты невозможно прочитать, сравнить, передать дальше. Хорошо отформатированный — аккуратные выровненные таблицы, единообразная точность, заголовки колонок — делает результаты сразу пригодными для анализа, публикации, ввода в другие инструменты. Fortran создавался учёными для учёных именно с этой задачей во главе угла, и его система дескрипторов формата по сей день одна из самых выразительных среди языков программирования. Где-нибудь в Python для красивого табличного вывода чисел приходится подбирать спецификаторы f-строк или подключать библиотеки; в Fortran это решается компактной строкой формата, дающей точный контроль над каждым полем. Эта историческая сила не случайна: язык, на котором печатали таблицы логарифмов, баллистические расчёты и инженерные ведомости, обязан был уметь форматировать числа идеально. Современный научный код наследует эту способность: будь то вывод сходимости итераций в виде аккуратной таблицы, генерация входного файла для стороннего пакета по его строгому формату или подготовка данных для построения графиков — система дескрипторов даёт нужный инструмент. Поэтому, хотя для отладки достаточно списочного print *, для любого «настоящего» вывода — отчётов, файлов обмена, публикуемых результатов — владение форматными дескрипторами обязательно. Это не устаревшая мелочь, а ключевой навык: расчёт, результаты которого нельзя аккуратно представить, наполовину бесполезен, и именно форматный вывод доводит работу программы до пригодного человеку итога.

Частые ошибки

  • Слишком узкое поле → звёздочки. Если число не влезает в ширину w, поле заполняется *. Закладывайте запас ширины под знак, целую часть и точку.
  • Путать E и ES. E даёт мантиссу меньше единицы (ведущий ноль), ES — нормализованную (первая значащая цифра до точки). Для инженерных отчётов обычно нужен ES (или EN с экспонентой, кратной 3).
  • Ожидать, что форматный вывод сохранит точность. Он округляет до d знаков; для точного хранения используйте неформатный I/O.
  • Несоответствие позиций при форматном чтении. Если колонки во входном файле не совпадают с дескрипторами, данные считаются неверно. Для свободных данных надёжнее списочный ввод.
  • Забыть пробелы между полями. Без X или достаточной ширины колонки сольются. Управляйте разделением через nX и ширину.

Итоги

  • Форматный I/O даёт точный контроль вида чисел и текста — основа воспроизводимых отчётов и файлов фиксированного формата.
  • Главные дескрипторы: I (целые), F/E/ES (вещественные), A (строки), X (пробелы), / (новая строка).
  • Повторители (3F8.2) и группы (2(I3,F6.1)) компактно описывают таблицы; формат реверсируется для длинных списков.
  • Узкое поле даёт звёздочки; ES удобнее E для инженерных величин.
  • Форматный вывод округляет и теряет точность — для точного хранения нужен неформатный I/O.
Проверьте себя
1. Что выведет Fortran, если число не помещается в ширину поля формата (например, 100000.0 в F6.1)?
AОбрежет число до 6 символов
BЗаполнит поле звёздочками (******)
CАвтоматически расширит поле
DВыведет число в научной форме
2. Чем дескриптор ES отличается от E при выводе вещественного?
AES выводит без экспоненты
BES даёт нормализованную мантиссу — первую значащую цифру до точки, а E ставит ведущий ноль
CES округляет, а E нет
DОни полностью идентичны