nextupprevious
Next:2.2.7 Определяемые функции Up:2.2 Выражения, операторы и Previous:2.2.5 Операторы ввода-вывода


2.2.6 Общий вид Zonnon-программ

Каждая рассматриваемая нами Zonnon-программа представляет собой отдельно транслируемый модуль, который состоит из заголовка модуля и тела модуля, за которым следует имя модуля и точка, являющаяся признаком конца текста программы. Заголовок содержит имя модуля, которое никак не может использоваться внутри программы, а тело программы -- это так называемый блок, который состоит из разделов описаний (констант, переменных, процедур) и составного операторов. Таким образом, наши Zonnon-программы строятся в соответствии с синтаксической диаграммой рис. 2.9, из которой видно, что все описания могут отсутствовать.

Module = module ModuleName ";"
        { const { ConstantDeclaration ";"  }{ var   { VariableDeclaration ";" }}{ ProcedureDeclaration }BlockStatement
         SimpleName ".".

Рис. 2.9. Общий вид Zonnon-программы

В языке Zonnon помимо стандартных процедур и функций разрешены процедуры и функции, определяемые программистом. Каждая вводимая в употребление процедура (функция) должна быть описана (определена) -- см. п. 2.2.7. Описание функции состоит из ее заголовка, в котором задается имя функции, фиксируются область ее определения (число и типы ее аргументов) и область ее значения (тип результата), а также тела функции, в котором определяются правила вычисления результата функции по ее аргументам. Аналогично обращению к стандартной функции вызов (или, что то же самое, указатель) определенной в программе функции может являться частью любого выражения (см. п. 2.2.1) и имеет вид имени функции, за которым в скобках перечисляются его фактические параметры -- выражения, вычисляющие те конкретные значения аргументов, к которым при данном обращении должна быть применима указанная функция.

Определение константы вводит имя (идентификатор) как синоним некоторой константы и записывается по правилам рис. 2.10. Константное выражение – это выражение, которое может быть вычислено при простом текстовом просмотре, без реального выполнения программы. Его операндами должны быть константы или вызовы предопределенных функций.
 

ConstantDeclaration = ident "=" ConstExpression.
ConstExpression = Expression.

Рис. 2.10. Определение константы

Использование таких именованных констант делает программу более наглядной и понятной и позволяет легко модифицировать программу, если возникает потребность в изменении константы в тексте программы.

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

VariableDeclaration = ident { "," ident } ":" Type.

Рис. 2.11. Совокупность описаний переменных
 

Например, программа вычисления длины окружности может иметь следующий вид:

module ДлинаОкружности;
    const Два = 2; Пи = 3.14; Текст = 'Длина окружности равна';
    var R, Длина : real;
begin read(R); Длина:=Два $*$ Пи $*$ R ; write(Текст, Длина) end ДлинаОкружности.

Рассмотрим задачу вычисления корней уравнения $ax^2 + bx + c = 0$, заданного коэффициентами $a$$b$ и $c$. Предположим, что $а \ne 0$ и что корни действительны. Тогда возможным решением является

module Корни1;
    var A, B ,C, X1, X2 : real;
begin
    read(A,B,C);
    X1 := (-B+Sqrt(Sqr(B)-4$*$A$*$C))/(2$*$A);
    X2 := (-B-Sqrt(Sqr(B)-4$*$A$*$C))/(2$*$A); write(X1, X2)
end Корни1.

Недостаток этой программы заключается в том, что в ней дважды вычисляются значение Sqrt(Sqr(B)-4$*$ A$*$ C) и значение 2$*$А. Запоминая значения выражений в подходящих переменных, можно избежать необходимости повторных вычислений их значений.

module Корни2;
    var A,B,C,X1,X2,D,E : real;
begin
    read(A,B,C);
    D := Sqrt(Sqr(B) - 4$*$A$*$C);
    E :=2$*$A;
    X1 := (-B+D)/E;
    X2 := (-B-D)/E; write(X1,X2)
end Корни2.

Сравнивая Корни1 и Корни2, нетрудно понять, что программа Корни1 заметно уступает программе Корни2 "по времени" (по числу шагов, которые необходимо выполнить для получения результата), а если и выигрывает "по памяти" (по числу ячеек, отводимых при выполнении программы под ее команды и данные), то незначительно.

Вместе с тем программы Корни1 и Корни2 имеют один общий недостаток, связанный со свойствами машинной арифметики: из-за ограниченного числа значащих цифр в вещественных значениях, представимых в ЭВМ, в результате вычисления по программам можно получить совершенно неверные результаты.

Поясним это на примере работы программы Корни2. Для простоты изложения предположим, что все арифметические операции выполняются с четыремя значащими десятичными цифрами, и рассмотрим, что произойдет, если a=1.000, b=-200.0, c=1.000. В результате выполнения программы Корни2 мы получим: D=Sqrt(40000-4.000)=200.0; X1=400.0/2.000=200.0; X2=0.000/2.000=0.000.

Правильными результатами, однако, будут X1=200.0 и X2=0.005. Если качество программы Корни2 оценивать с точки зрения относительной точности вычисляемых значений, то результат X2 следует отвергнуть как совершенно неверный.

Причина неудовлетворительного результата состоит в том, что программа Корни2 не учитывает "опасности", которая связана с операциями сложения и вычитания. Они могут явиться причиной значительных ошибок, особенно при вычитании двух почти равных значений. В этом случае большое количество значащих цифр взаимно уничтожаются, и в полученной разности может не оказаться нескольких, а может быть всех значащих цифр.

Алгоритм, в котором учтены указанные эффекты, может быть построен с использованием правила Виета. Нужно производит вычисления таким образом, чтобы сначала в X1 вычислялся максимальный по модулю корень (т.е. X1 нужно вычислять по формуле -(B+D)/(2$*$A), если B - положительно, либо по формуле (-B+D)/(2$*$A), в противном случае), а уж затем вычислить второй корень X2 по формуле C/(X1$*$A). (Заметим, однако, что мы рассмотрели еще недостаточно конструкций языка Паскаль, чтобы программно реализовать указанный алгоритм, и отложим его построение до следующей главы -- см. программу Корни3 в п.3.1.2.)

Рассмотренный пример нахождения корней квадратного уравнения демонстрирует довольно характерный случай, когда математические методы, известные еще со школьной скамьи, требуют дополнительного и тщательного исследования перед тем как их использовать для решения задач на ЭВМ.

Рассмотрим задачу вычисления функции $f(x) = x^{10}$, где $х$ -- натуральное число. Ее решением является

module X10;
    var X,Y : integer;
begin read(X); Y := X$*$X$*$X$*$X$*$X$*$X$*$X$*$X$*$X$*$X; write(Y) end X10.

Однако эта программа хотя и является весьма короткой, но долго работает, осуществляя вычисление за 9 умножений. В то же время можно решить задачу за 4 умножения:

module X10A ;
    var X,X2,X4,X8,X10 : Integer;
begin read(X); X2 := X$*$X; X4 := X2$*$X2; X8 := X4$*$X4; X10 := X8$*$X2; write(X10) end X10A.

В программе X10A из соображений наглядности заведены различные переменные для хранения входного значения, промежуточных значений и результата программы. Поэтому она не только длиннее первой программы, но и использует большее число переменных. Однако программу X10A можно переписать так, чтобы в ней было только две переменные. Для этого нужно использовать тот факт, что переменная в программе отличается от математической переменной и является по существу элементом памяти, своеобразной камерой хранения одного любого значения некоторого (в данном случае целого) типа. Это значение поступает туда в результате выполнения оператора присваивания или ввода; при этом старое значение переменной перестает существовать. Ясно, что в некоторой точке программы переменная "неприкосновенна" для записи в нее, если текущее значение переменной позднее может использоваться в качестве аргумента при исполнении какого-либо оператора, и ей можно присвоить новое значение, если текущее значение переменной уже не потребуется в дальнейшем. В результате сокращения числа переменных в программе X10A получаем

module X10B ;
    var X,X2: integer;
begin read(X); X2 := X$*$X; X := X2$*$X2; X := X$*$X; X:= X$*$X2; write(X) end X10B.

Нетрудно понять, что количество и обозначения переменных, используемых в различных вариантах программы, не столь существенны для результата выполнения программы. Различные (по обозначениям переменных в операторах) варианты программы будут работать одинаково, если сохраняются все так называемые информационные связи в программе, т.е. "передачи" значений от одних операторов к другим. Например, можно, не нарушая смысла, переписать программy X10B:

module X10C ;
    var A,B: integer;
begin read(A); B := A$*$A; A:= B$*$B; A := A$*$A; A := A$*$B; write(A) end X10C.

Next:2.2.7 Определяемые функции
Up:2.2 Выражения, операторы и функции
Previous:2.2.5 Операторы ввода-вывода


© В.Н. Касьянов, Е.В.Касьянова, 2004