Поиск:

Ответ в темуСоздание новой темы Создание опроса
> [Pro] Этюды. Сюжет 1. Вывод вещественных чисел, без лишних пробелов и нулей 
:(
    Опции темы
kamre
Дата 22.9.2006, 08:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 330
Регистрация: 24.3.2006

Репутация: нет
Всего: 13



Подскажите, что нужно написать в строке форматирования, чтобы числа выводились так:
Код

a = 11111.22
write(*,  10) a   ! выводит '11111.22'
a = 11.22222
write(*,  10) a   ! выводит '11.22222'

10 format(?????) ! что здесь написать нужно?


Смысл в том, чтобы не было лишних нулей  и пробелов.
Хотя бы лишние ведущие пробелы то можно убрать?
PM MAIL   Вверх
popovda
Дата 22.9.2006, 10:07 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 290
Регистрация: 9.6.2006
Где: Москва

Репутация: нет
Всего: 6



Вот что:

10 format(F8.2)

Здесь 8 - общее количество цифр в выводе + дес. точка
          2 - кол-во знаков после запятой

Лучше так:
10 format('a = ',F8.2)
или
10 format(x,F8.2)


--------------------
С уважением, Попов Д.А.
PM MAIL   Вверх
kamre
Дата 22.9.2006, 15:31 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 330
Регистрация: 24.3.2006

Репутация: нет
Всего: 13



С F8.2 не получается:
Код

11111.22
   22.11

А нужно, чтобы было вот так:
Код

11111.22
22.11111

Вообще, смысл этого все таков: мне нужно выводить числа в таком виде
(11111.22, 22.11111, ...)
Так вот, все эти лишние нули и особенно ведущие пробелы очень не красивым такой вывод делают.


PM MAIL   Вверх
popovda
Дата 22.9.2006, 19:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 290
Регистрация: 9.6.2006
Где: Москва

Репутация: нет
Всего: 6



Ага. Понял. Нужно выводить числа разной длинны целой и дробной частей, но без лишних пробелов и нулей. А я то на 1-ый пример смотрел и не понял. 
Здесь два варианта:
1. Преобразование число->строка. Ну это простая задачка. И есть плюс: это более универсальный подход - можно повторным проходом по строке контролировать вывод лишних нулей в конце числа.
2. По-фортрановски: сгенерировать с помощью внутреннего файла форматную строку.
Как это делается можно посмотреть или у Рыжикова (Современный Фортран или Фортран PowerStation для инженеров), или у Бартеньева. Это классический пример.


--------------------
С уважением, Попов Д.А.
PM MAIL   Вверх
kamre
Дата 22.9.2006, 21:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 330
Регистрация: 24.3.2006

Репутация: нет
Всего: 13



Насчет лишних нулей в конце - ладно, пусть будут.
Сейчас главное убрать лишние пробелы в начале, т.к. это именно они портят весь вид!
Про варианты:
1. преобразование самому что-ли предлагается написать? неужели фортран совсем не умеет не выводить лишние ведущие пробелы? может быть есть какие-то дескрипторы, чтобы указать на это? я читал про форматированный ввод-вывод, ничего про это не нашел.
2. т.е. придется подсчитывать количество цифр до запятой (и, возможно, после) и составлять форматную строку?

На фортран я перешел с c/c++. И мне не очень понятно, как написать аналог вот такого вывода чисел:
Код

#include<stdio.h>
int main() 
{
    double a[] = {11111.22, 22.11111};
    printf("(");
    for (int i = 0; i < 2; i++) {
        if (i)
            printf(", ");
        printf("%.5lf", a[i]);
    }        
    printf(")");
    return 0;
}

чтобы на экран вывелось:
Код

(11111.22000, 22.11111)


Добавлено @ 22:07 
Ура, ура!
Вот оно:
Цитата

Замечание. Фортран 95 позволяет задать значение w, равное нулю,
например I0 или F0.5. В этом случае длина поля определяется значением
выводимого числа.

PM MAIL   Вверх
Cr@$h
Дата 22.9.2006, 22:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Исследователь
***


Профиль
Группа: Участник Клуба
Сообщений: 1693
Регистрация: 3.4.2005
Где: Санкт-Петербург, Россия

Репутация: 1
Всего: 41



Чтобы отводить на число ровно столько знаков, сколько в этом числе есть, нужно знать, сколько знаков после запятой, а сколько до. Плюс не забывать про знак самого числа. Не советую пользоваться метками (а занчит и декларативным оператором format). В Фортране в любых ситуациях можно обойтись без меток и только, наверное, в случае format это может оказаться чуть побольше по коду (например, один и тот же специикатор формата задавать несколько раз в каждом оперторе write. Ну и что. Зато без меток, и лапши нет.).
Цитата(kamre @  22.9.2006,  22:52 Найти цитируемый пост)
2. т.е. придется подсчитывать количество цифр до запятой (и, возможно, после) и составлять форматную строку?

Фактически, да.
Цитата(kamre @  22.9.2006,  22:52 Найти цитируемый пост)
1. преобразование самому что-ли предлагается написать? неужели фортран совсем не умеет не выводить лишние ведущие пробелы? может быть есть какие-то дескрипторы, чтобы указать на это? я читал про форматированный ввод-вывод, ничего про это не нашел.

Цитата(kamre @  22.9.2006,  22:52 Найти цитируемый пост)
На фортран я перешел с c/c++. 

И в этом тебе тут помогут. Пиши "такой же" дескриптор: f0.5  smile Fortran ни чуть не хуже С++.
Спасибо за задачку. Я ещё подумаю.

Даже странно, что в тему по Fortran я печатаю только шестым постом.

Добавлено @ 22:32 
Цитата(kamre @  22.9.2006,  22:52 Найти цитируемый пост)
Замечание. Фортран 95 позволяет задать значение w, равное нулю,
например I0 или F0.5. В этом случае длина поля определяется значением
выводимого числа.

Бартеньев, могу поспорить. smile 
PM MAIL ICQ   Вверх
popovda
Дата 23.9.2006, 20:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 290
Регистрация: 9.6.2006
Где: Москва

Репутация: нет
Всего: 6



Признаюсь. Не знал про спецификатор F0.k, но если это из Бартеньева, то я так же не уверен, что это стандарт 95. Бартеньев очень жестко привязан к линейке CVF. Про некоторые расширения языка относительно стандарта и моменты реализации он умалчивает.

Насчет меток и оператора format. Нда. Согласен. Это последний камень преткновения в Ф. относительно меток. Что делать - привычкаsmile 


--------------------
С уважением, Попов Д.А.
PM MAIL   Вверх
Cr@$h
Дата 23.9.2006, 22:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Исследователь
***


Профиль
Группа: Участник Клуба
Сообщений: 1693
Регистрация: 3.4.2005
Где: Санкт-Петербург, Россия

Репутация: 1
Всего: 41



Цитата(popovda @  23.9.2006,  21:57 Найти цитируемый пост)
Не знал про спецификатор F0.k, но если это из Бартеньева, то я так же не уверен, что это стандарт 95.

Проверил. Это F95. В нём можно писать и i0 для той жже цели при целых числах. Вот жалко, нельзя f0.0, что нам и надо вообще-то  smile  smile 
Цитата(popovda @  23.9.2006,  21:57 Найти цитируемый пост)
Бартеньев очень жестко привязан к линейке CVF. Про некоторые расширения языка относительно стандарта и моменты реализации он умалчивает.

В книге 2005 г. в этом смысле всё более менее нормально, а так ты прав.
Цитата(popovda @  23.9.2006,  21:57 Найти цитируемый пост)
Насчет меток и оператора format. Нда. Согласен. Это последний камень преткновения в Ф. относительно меток. Что делать - привычка

Да. Ну, если что, напомню: полностью заменяется программированием спецификатора. Плюс есть расширение во могих реализациях:
Код

print '(f<n>.2)'

где n -- целочисленное выражение.
А так, можно использовать только буквальные константы (и без угловых скобок, естественно).

P.S. В Философии программирования есть немного занимательная тема "Кого это вы 'go to'!? или Нужны ли goto, как и стоит ли их обходить?". Что-то я давно не заходил на неё. Там есть ещё о чём поговорить.

Это сообщение отредактировал(а) Cr@$h - 23.9.2006, 22:17
PM MAIL ICQ   Вверх
Cr@$h
Дата 24.9.2006, 00:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Исследователь
***


Профиль
Группа: Участник Клуба
Сообщений: 1693
Регистрация: 3.4.2005
Где: Санкт-Петербург, Россия

Репутация: 1
Всего: 41



Я здесь упомянул об расширении над стандартом языка: в дескрипторах преобразования везде вместо буквальных констант можно использовать целочисленные выражения заключённые в угловые скобки.
Код

print '(f<n>.<m>)'

Это позволяет не прибегать к программированию спецификации формата, чтобы напрямую подставить в дескриптор преобразования значения этих переменных n и m (или целочисленных выражений).

Рассматриваемое расширение, хоть и является расширением над стандартом F03 (на сколько я знаю), позволяет часто подобным образом сократить код по выводу чисел. Использовать его следует на своё усмотрение (например, не использовать для переносимости кода). Я же покажу один любопытный код с этим расширением. Для этого поговорим об симметричных матрицах, но это уже отдельная тема сюжетной серии "Этюды для Fortran-программистов": Сюжет 2. Симметричные матрицы: построение типа данных, работа с ним и визуализация.

Это сообщение отредактировал(а) Cr@$h - 28.9.2006, 00:56
PM MAIL ICQ   Вверх
Cr@$h
Дата 3.10.2006, 04:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Исследователь
***


Профиль
Группа: Участник Клуба
Сообщений: 1693
Регистрация: 3.4.2005
Где: Санкт-Петербург, Россия

Репутация: 1
Всего: 41



Наконец, выкроил время. Давайте разберём фундаментально задачу по кирпичикам. Уже было предложено куча идей, и по большей части осталось их только объективно реализовать.

Имеется код на С++:
Код

#include<stdio.h>
int main() 
{
    double a[] = {11111.22, 22.11111};
    printf("(");
    for (int i = 0; i < 2; i++) {
        if (i)
            printf(", ");
        printf("%.5lf", a[i]);
    }        
    printf(")");
    return 0;
}

Его задача -- вывести в консоль в скобках числа, соддержащиеся в массиве a, и вот как он это делает:
Код

(11111.22000, 22.11111)

Нам же поставлена задача: реализовать подобный вывод на Fortran. Ну, что, kamre, покажем C++-программистам, как шла эволюция языков?

На основе того, что мы уже обсудили и предложили, сформировалось пять реализаций. Рассмотрим их сразу с примерами кода на Fortran. Заметим, что в оригинале используется тип double, давайте учитывать это. Переменные этого типа могут иметь 15 значащих разрядов (в принципе, всегда их имеют).

1. Использование дескриптора преобразования, введённого в F95

Также, как и в С++, в Fortran 95 введён дескриптор преобразования, позволяющий не задавать общее число позиций для вывода числа, а предоставить динамическую обработку этого: требуется задать только число знаков после запятой. Он выглядит аналогично сишному (.5lf): f0.x, где x -- число разрядов после запятой. Общее число позиций будет определяться самим числом; если значащих разрядов после запятой будет меньше, чем x, то необходимые заполнятся нулями при выводе.

Напишем соответствующий код на Fortran:
Код

program Format
    implicit none
    
    integer, parameter :: R_ = 8
    real(R_), allocatable :: a(:)
    integer :: N = 2
    
    allocate( a(N) )
    a = [ 11111.22_R_, 22.11111_R_ ]
    print '("(" <N-1>(f0.5, ", ") f0.5, ")")', a
    read *
end program Format

Да, вот так просто. В консоли мы увидим требуемый текст:
Код

(11111.22000, 22.11111)

Если писать не в общем виде, как в исходном примере, то это будет виглядеть ещё проще:
Код

program Format
    real(8) :: a(2) = [ 11111.22_8, 22.11111_8 ]

    print '("(" 1(f0.5, ", ") f0.5, ")")', a
    read *
end program Format

Здесь мы разрешили типизацию по умолчанию implicit none, убрали константу R_ для разновидности типа вещественного числа, сделали массив a статическим и убрали переменную-длину массива N. Это плохой стиль и в дальнейшем мы не будем его применять, так что пускай никто не дуется на размер кода.

Итак, у С++ на подобную операцию ушло 7 строк кода, у Fortran -- 1. Куда уж меньше. Если оставаться честными, то только второй вариант кода удовлетворяет стандарту F03, ведь мы использовали целочисленное выражение в дескрипторе формата: <N>. Хорошо, хорошо, код можно сделать и переносимым. Запрограммируем спецификацию формата, вот и всё:
Код

program FormatStandard
    implicit none
    
    integer, parameter :: R_ = 8
    real(R_), allocatable :: a(:)
    integer :: N = 2
    character(80) descriptor
    
    allocate( a(N) )
    a = [ 11111.22_R_, 22.11111_R_ ]
    write( descriptor, '(a, i0, a)' ) '("(" ', N - 1, '(f0.5, ", ") f0.5, ")")'
    print descriptor, a
    read *
end program FormatStandard

Тут мы добавили строку character(80) descriptor и запись в неё: write( descriptor, '(a, i0, a)' ) '("(" ', N - 1, '(f0.5, ", ") f0.5, ")")'. Её значением стало: ("(" 1(f0.5, ", ") f0.5, ")"), которое мы затем использовали как дескриптор преобразования.

Недостатки подхода. Остаются нули в разрядах после запятой. Фактически, мы должны были бы писать f0.15, т.к. после запятой могут оказаться все 15 значащих разрядов (это для real(8), не забываем), ведь мы не знаем, сколько в действительности значащих разрядов после запятой у какого числа. Так что правильный код будет выглядеть так (заменили f0.5 на f0.15):
Код

program FormatStandard
    implicit none
    
    integer, parameter :: R_ = 8
    real(R_), allocatable :: a(:)
    integer :: N = 2
    character(80) descriptor
    
    allocate( a(N) )
    a = [ 11111.22_R_, 22.11111_R_ ]
    write( descriptor, '(a, i0, a)' ) '("(" ', N - 1, '(f0.15, ", ") f0.15, ")")'
    print descriptor, a
    read *
end program FormatStandard

От этого весь вывод "портится":
Код

(11111.219999999999345, 22.111110000000000)

А что делать? Ведь мы могли занести в массив числа, меньшие 0, и используя все 15 значащих разрядов. Откуда мы знаем, что в массиве "за числа"? Недостаток подхода на лицо.

Достоинства подхода. Простота реализации: требует всего несколько строк, даже одной элегантной строки при использовании расширения.

Этот подход можно и удобно применять, если обладать некоторой информацией о том, сколько максимум значащих разрядов после запятой (в десятичной форме) используется числами массива.

2. Преобразование числа в строку с удалением ведущих пробелов

Очень просто. Пишем число в строку и удаляем в ней ведущие пробелы. Код пишем сразу в стандарте и в общем виде. Если что, знаем как убрать строку дескриптора формата, используя расширение над языком.
Код

program StringSpaces
    implicit none
    
    integer, parameter :: R_ = 8
    real(R_), allocatable :: a(:)
    integer :: N = 2, i
    character(80), allocatable ::  Strings(:)
    character(80) descriptor
    
    allocate( a(N), Strings(N) )
    a = [ 11111.22_R_, 22.11111_R_ ]
    do i = 1, N
        write(Strings(i), * ) a(i)
    end do
    ! Удаляем ведущие пробелы и переносим их в конец строки.
    Strings = AdjustL( Strings )
    write( descriptor, '(a, i0, a)' ) '("(" ', N - 1, '(a, ", ") a, ")")'
    print descriptor, (Trim( Strings(i) ), i = 1, N)
    read *
end program StringSpaces

Важно сделать несколько замечаний по коду.
  • Заметим, что во втором операторе вывода нам пришлось использовать неявный цикл, т.к. функция удаления завершающих пробелов Trim не является элементной, а значит записать просто Trim( Strings ) мы не можем.
  • Когда мы записывали числа в массив строк, то мы опускали дескриптор преобразования. Это удобно, т.к. при выводе по умолчанию выводится ровно 15 разрядов числа. Так, если все значащие разряды лежат перед запятой, то при таком выводе после запятой ничего не будет. Здесь не надо думать, что в f0.x писать под x.
  • Функцией Adjust мы перемещаем ведущие пробелы в конец строки. Заметим, что она вынесена из тела цикла. Действительно, ведь вторым оператором цикла можно было написать Strings(i) = AdjustL( Strings(i) ). Мы не сделали этого специально, т.к. функция Adjust является элементной и эту операцию для всего массива строк можно будет делать параллельно: при таком коде достаточно будет указать компилятору Auto Parallelization.
Так что мы видим на экране?
Код

(11111.2200000000, 22.1111100000000)

Если сравнить с предыдущим подходом (ведь мы будем смотреть на взрослый вариант):
Код

(11111.219999999999345, 22.111110000000000)

то сразу видно отличие. В первом варианте нам приходится отводить 15 разрядов под знаки после запятой, т.к. заранее не известно, сколько их там у каждого числа. В этом же варианте вывода мы загоняем сперва число в строку, убираем ведущие пробелы и выводим число как часть строки без завершающих пробелов. И именно при загоне числа в строку: write(Strings(i), * ) a(i), вы используем дескриптор преборазования по умолчанию. Он же выводит все 15 разрядов числа и ставит запятую, где надо. Блягодаря этому мы не выводим разрядов числа после запятой, которые не представлены в числе.

Недостатки подхода. Используем массив строк. Это требует некоторой дополнительной памяти. Хотя, всё можно было сделать в одном цикле, обходясь всего одной строкой. Длина строк в массиве должна быть не меньше, чем 15+15+2. На каждое число отводится 16(+1) разрядов, отсюда завершающие пробелы могут появляться, если элементу массива присваивалось число с нулевыми разрядами на конце (например, число 3,14 выведится как 3,140.0, где 12 нулей на конце).

Достоинства подхода. На каждое число отводится 16(+1) разрядов, ни больше, ни меньше. Здесь не надо думать, сколько разрядов выводить после запятой, компилятор выводит всё 8-мибитное представление числа. Число никак из-за этого не искажается на конце. Нет ведущих пробелов.

Как приспособить дескриптор по умолчанию к первому подходу, не понятно. Тут появляются ведущие пробелы: ведь 15 разрядов отводится на знаки до запятой, и, если их меньше, появятся ведущие пробелы. Если кто знает, как убирать здесь ведущие пробелы, не используя строк, то будет совсем здорово.

Из-за отсутствия искажеий чисел этот подход можно развить дальше.

3. Преобразование числа в строку с удалением ведущих пробелов и завершающих нулей

Используем предыдущий подход. После получения строки с числом без ведущих пробелов удаляем из неё завершающие нули: заменяем их на пробелы, чтобы последние потом вместе со всеми отсечь Trim'ом. Запишем код в функциональном стиле: для замены завершающих нулей на пробелы напишем элементную функцию, чтобы подставлять туда сразу весь массив строк Strings.
Код

program StringSpacesZero_Elemental_Function
    implicit none
    
    integer, parameter :: R_ = 8
    real(R_), allocatable :: a(:)
    integer :: N = 2, i
    character(80), allocatable ::  Strings(:)
    character(80) descriptor
    
    allocate( a(N), Strings(N) )
    a = [ 11111.22_R_, 22.11111_R_ ]
    do i = 1, N
        write(Strings(i), * ) a(i)
    end do
    ! Удаляем ведущие пробелы и переносим их в конец строки.
    Strings = AdjustL( Strings )
    ! Заменяем завершающие нули на пробелы.
    Strings = DeleteZeros( Strings )
    write( descriptor, '(a, i0, a)' ) '("(" ', N - 1, '(a, ", ") a, ")")'
    print descriptor, (Trim( Strings(i) ), i = 1, N)
    read *
    
contains
    elemental function DeleteZeros( String ) result(result)
        character(80), intent(in) :: String
        character(Len(String)) :: result
        integer i
        
        result = String
        do i = Len_Trim( result ), 1, - 1
            if( result(i:i) == "0" ) then
                result(i:i) = " "
            else
                return
            end if
        end do
    end function DeleteZeros
end program StringSpacesZero_Elemental_Function

Добавили элементную функцию DeleteZeros и её вызов Strings = DeleteZeros( Strings ). При вызове этой функции на её значение будет тратиться память, если это кому-то принципиально, то можно её подставить прямо в код (хотя, я догадываюсь, что компилятор сам это сделает при разрешении на подстановку (inlining)):
Код

program StringSpacesZero
    implicit none
    
    integer, parameter :: R_ = 8
    real(R_), allocatable :: a(:)
    integer :: N = 2, i, j
    character(80), allocatable ::  Strings(:)
    character(80) descriptor
    
    allocate( a(N), Strings(N) )
    a = [ 11111.22_R_, 22.11111_R_ ]
    do i = 1, N
        write(Strings(i), * ) a(i)
    end do
    ! Удаляем ведущие пробелы и переносим их в конец строки.
    Strings = AdjustL( Strings )
    ! Заменяем завершающие нули на пробелы.
    DeleteZeros: do i = 1, N
        do j = Len_Trim( Strings(i) ), 1, - 1
            if( Strings(i)(j:j) == "0" ) then
                Strings(i)(j:j) = " "
            else
                cycle DeleteZeros
            end if
        end do
    end do DeleteZeros
    write( descriptor, '(a, i0, a)' ) '("(" ', N - 1, '(a, ", ") a, ")")'
    print descriptor, (Trim( Strings(i) ), i = 1, N)
    read *
end program StringSpacesZero

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

program StringSpacesZero_Elemental_Subroutine
    implicit none
    
    integer, parameter :: R_ = 8
    real(R_), allocatable :: a(:)
    integer :: N = 2, i
    character(80), allocatable ::  Strings(:)
    character(80) descriptor
    
    allocate( a(N), Strings(N) )
    a = [ 11111.22_R_, 22.11111_R_ ]
    do i = 1, N
        write(Strings(i), * ) a(i)
    end do
    ! Удаляем ведущие пробелы и переносим их в конец строки.
    Strings = AdjustL( Strings )
    ! Заменяем завершающие нули на пробелы.
    call DeleteZeros( Strings )
    write( descriptor, '(a, i0, a)' ) '("(" ', N - 1, '(a, ", ") a, ")")'
    print descriptor, (Trim( Strings(i) ), i = 1, N)
    read *
    
contains
    elemental subroutine DeleteZeros( String )
        character(80), intent(inout) :: String
        integer i
        
        do i = Len_Trim( String ), 1, - 1
            if( String(i:i) == "0" ) then
                String(i:i) = " "
            else
                return
            end if
        end do
    end subroutine DeleteZeros
end program StringSpacesZero_Elemental_Subroutine

В любом случае при параллельном выполннии программы вызовы элементной функции DeleteZeros в первом случае или выполнение итераций цикла DeleteZeros во втором случае или вызовы элементной подпрограммы DeleteZeros в третьем будут проходить параллельно.
Вот, что мы видим на экране:
Код

(11111.22, 22.11111)

Красиво, правда? Как раз то, чего мы добивались.

Недостатки подхода. Как и прежде, используем массив строк. Это требует некоторой дополнительной памяти. Хотя, всё можно было сделать в одном цикле, обходясь всего одной строкой, но из-за этого потеряем распараллеливаемые участки кода.

Достоинства подхода. Получили то, что хотели: нет ни ведущих пробелов, ни завершающих нулей у чисел.

4. Преобразование числа в строку с удалением ведущих пробелов и вычислением длины числа без завершающих пробелов

Если обратить внимание на третий подход, то он хорошо всем, но делает некоторую лишнюю работу: мы находим каждый завершающий нуль, преобразовываем его в пробел, который всё равно удалим при выводе функцией Trim. Фактически, по строке числа ходим дважды. Вместо этого предлагается, запомнить позицию самого левого завершающего нуля и вывести строку до него. Нужно будет завести дополнительный массив целых чисел только.
Код

program ValueLength
    implicit none
    
    integer, parameter :: R_ = 8
    real(R_), allocatable :: a(:)
    integer :: N = 2, i, j
    integer, allocatable :: ValueLengths(:)
    character(80), allocatable ::  Strings(:)
    character(80) descriptor
    
    allocate( a(N), Strings(N), ValueLengths(N) )
    a = [ 11111.22_R_, 22.11111_R_ ]
    do i = 1, N
        write(Strings(i), * ) a(i)
    end do
    ! Удаляем ведущие пробелы и переносим их в конец строки.
    Strings = AdjustL( Strings )
    ! Вычислением длины числа без завершающих пробелов.
    Lengths: do i = 1, N
        do j = Len_Trim( Strings(i) ), 1, - 1
            if( Strings(i)(j:j) == "0" ) then
                Strings(i)(j:j) = " "
            else
                ValueLengths(i) = j
                cycle Lengths
            end if
        end do
    end do Lengths
    write( descriptor, '(a, i0, a)' ) '("(" ', N - 1, '(a, ", ") a, ")")'
    print descriptor, (Strings(i)(1:ValueLengths(i)), i = 1, N)
    read *
end program ValueLength

Что же мы видим на экране? Да всё тот же пейзаж:
Код

(11111.22, 22.11111)

Сделаем несколько замечаний.
  • Мы завели отдельный массив ValueLengths, где будем хранить число разрядов числа без завершающих нулей.
  • Для каждого числа a(i) будет своя длина ValueLengths(i), но вывод всё равно записан в две строчки благодаря неявному циклу.
  • Здесь также двумя способами, как и в третьем подходе, в функциональном стиле можно выделить в отдельную операцию подсчёт для каждого числа количества разрядов без завершающих нулей.
  • Отличие кода от третьего подхода сразу видно: нет вызова функции Trim в операторе вывода.
Недостатки подхода. Используем массив строк и массив целых чисел. Это требует некоторой дополнительной памяти. Хотя, всё можно было сделать в одном цикле, обходясь всего одной строкой и числом, но из-за этого потеряем распараллеливаемые участки кода.

Достоинства подхода. Получили то, что хотели: нет ни ведущих пробелов, ни завершающих нулей у чисел.

5. Вычисление числа разрядов после запятой без завершающих пробелов (брутальный метод)

Посчитаем для каждого числа, сколько разрядов после запятой нам надо вывести и подставим это значение в дескриптор преобразования.
Код

program Digits
    implicit none
    
    integer, parameter :: R_ = 8
    real(R_), allocatable :: a(:)
    integer :: N = 2, i, j
    integer, allocatable :: DigitsNumber(:)
    character(80), allocatable ::  Strings(:)
    character(80) descriptor
    
    allocate( a(N), Strings(N), DigitsNumber(N) )
    a = [ 11111.22_R_, 22.11111_R_ ]
    ! Заисываем в массив строк вещественные части чисел.
    do i = 1, N
        write(Strings(i), * ) a(i)
    end do
    ! Считаем для каждого числа количество разрядов после запятой без
    ! завершающих нулей.
    DigitsNumbers: do i = 1, N
        DigitsNumber(i) = Len_Trim( Strings(i) )
        do j = Len_Trim( Strings(i) ), 1, - 1
            if( Strings(i)(j:j) /= "0" ) then
                DigitsNumber(i) = j
                exit
            end if
        end do
        do j = DigitsNumber(i), 1, - 1
            if( Strings(i)(j:j) == "." ) then
                DigitsNumber(i) = DigitsNumber(i) - j
                cycle DigitsNumbers
            end if
        end do
    end do DigitsNumbers
    print '("(" \)'
    do i = 1, N - 1
        write( descriptor, '(a, i0, a)' ) '(f0.', DigitsNumber(i), ', ", " \)'
        print descriptor, a(i)
    end do
    write( descriptor, '(a, i0, a)' ) '(f0.', DigitsNumber(i), ', ")")'
    print descriptor, a(i)
    read *
end program Digits

Важно сделать несколько замечаний и здесь:
  • Здесь мы в определённом смысле объединили код первого и четвёртого подходов: при выводе используем дескриптор преобразований для самих чисел (в первом подходе выводили с 15-тью разрядами), но посчитали предварительно сколько разрядов после запятой без завершающих пробелов (в четвёртом подходе считали длину числа без завершающих пробелов). Круг замкнулся.
  • Мы завели отдельный массив DigitsNumber, где будем хранить число разрядов после запятой без завершающих нулей.
  • Для каждого числа a(i) будет своё число этих разрядов DigitsNumber(i), поэтому коэффициентом повторения в дескрипторе преобразования здесь не обойтись: нам пришлось выводить числа в явном цикле.
  • Подход не требует удаления ведущих пробелов и переноса их в конец строки.
  • Здесь также двумя способами, как и в третьем подходе, в функциональном стиле можно выделить в отдельную операцию подсчёт для каждого числа количества разрядов после запятой без завершающих нулей.
Крылья другие, птица та же:
Код

(11111.22, 22.11111)

Недостатки подхода. Используем массив строк и массив целых чисел. Это требует некоторой дополнительной памяти. Хотя, всё можно было сделать в одном цикле, обходясь всего одной строкой и числом, но из-за этого потеряем распараллеливаемые участки кода. Решение слишком фундаментально: мы считаем число разрядов после запятой без завершающих нулей, всё равно используем строки, но при выводе от них отказываемся.

Достоинства подхода. Получили то, что хотели: нет ни ведущих пробелов, ни завершающих нулей у чисел.


Заключение и вывод

Для задачи мы увидели пять различных реализаций, но только три из них делают вывод изящно и аккуратно.
  • Если допускается вывод завершающих нулей, то можно использовать второй подход: на каждое число при выводе будет тратиться +/- одинаковое число разрядов (просто может быть знак и ведущий нуль у числа, меньшего нуля: +0.1234567 -- 10 знаков, против 1234567. -- 8 знаков).
  • Если завершающие нули недопустимы, то нужно использовать один из трёх последних методов.
  • Третий и четвёртый подходы похожи, но четвёртый не требует лишних вызовов функции Trim.
  • Пятый метод самый фундаентальный: мы выводим числа как числа, но предварительно считаем, используя строки, сколько у них после запятой разрядов без завершающих нулей. Вывод делается явным циклом.
  • Везде можно обойтись без программирования спецификации формата, используя расширение над языком.
  • Для целых чисел нужен только первый, вырожденный метод с дескриптором преобразований i0.
Самым хорошим представляется четвёртый метод. Он не требует лишних операций, один раз проходит по строке, и то не всей: проходит по строке справа до первого ненулевого знака. Пятый метод же проходит справа до десятичной точки. Хотя, он не требует удаления ведущих пробелов и переноса их в конец строки. Вывод в четвёртом методе реализуется в две строки.

Если нужно будет обходится без дополнительных массивов, то придётся всё выводить в одном цикле, а значит вывод в две строки в четвёртом методе не будет преимуществом, т.к. его просто не будет. Есть ещё одно отличие у двух последних подходов: один выводит числа, а другой -- строки. Что касается скорости: четвёртый метод проходит справа по строке до первого ненулевого символа, но требует удаления ведущих пробелов, пятый метод проходит справа до десятичной точки, но не требует удаления ведущих пробелов. Можно сказать, что, если вам не важно небольшое изящество кода, то пятый метод является лучшим.


M
Cr@$h
Эта тема открыла, по моему мнению, цикл интересных сюжетных тем "Этюды для Fortran-пограммистов". Тема прежде называлась "Вывод чисел в фортране", я переименовал ей в "Этюды для Fortran-пограммистов. Сюжет 1. Вывод вещественных чисел без лишних пробелов и нулей".

Конечно, эта тема открыта для обсуждений, вопросов и новых реализаций, тем более, после такого материала. Наверное, со временем такие темы в переработанном и аккумулированном виде хорошо было бы помещать на Fortran Vingrad Wiki (Fortran Viki).

От этой темы уже выделилась следующая, вторая сюжетная тема: "Симметричные матрицы: построение типа данных, работа с ним и визуализация"

Создавайте или предлагайте новые интересные темы, которые можно будет так же хорошо и занимательно разобрать и включить в цикл этюдов для Fortran-программистов. С длинными названиями тем и их описаний помогу;-)


Это сообщение отредактировал(а) Cr@$h - 3.10.2006, 04:43
PM MAIL ICQ   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Fortran | Следующая тема »


 




[ Время генерации скрипта: 0.1189 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.