Форматный ввод-вывод и дескрипторы формата
Форматный ввод-вывод даёт точный контроль над тем, как числа и текст превращаются в символы на бумаге, в файле или на экране.
Дескриптор формата — код в строке формата (например
F8.3илиI5), задающий, сколько символов отвести под значение и как его представить: ширину поля, число знаков после запятой, форму записи.
Зачем нужен явный формат
Fortran создавался для печати таблиц чисел, и управление их видом — его историческая сильная сторона. Когда вы выводите результат расчёта в отчёт, в файл для другой программы или в колонки на экран, вам важно: сколько знаков после запятой показать, как выровнять столбцы, не «съедет» ли вёрстка на больших значениях. Списочный вывод (print *) на это не способен — он сам решает, как печатать, и результат зависит от компилятора. Форматный вывод (print '(...)' или write(unit, '(...)')) отдаёт контроль вам: вы пишете строку формата из дескрипторов, и каждое значение печатается строго по правилу. Это критично для воспроизводимых отчётов и для файлов фиксированного формата, которые читают другие инструменты.
Анатомия строки формата
Строка формата — это список дескрипторов в круглых скобках, разделённых запятыми. Каждый дескриптор отвечает за одно значение (или за пробелы/переход строки). Самые употребительные дескрипторы данных:
| Дескриптор | Для чего | Пример |
Iw | целое в поле ширины w | I5 → " 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 |
nX | n пробелов | 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.