s=(S k: 1£k£i: 1/k) (11.2)
сохраняет истинное значение в течение всей работы цикла. Здесь запись
(S k: 1£k£i: 1/k)
означает
.Для цикла из примера 10.1, приведенному на рис. 11.3, инвариант выглядит так:
P: s=(Pk: 1£k<i: k)) (11.3)
где s=Pk: 1£k<i :k означает
.Таким образом, мы можем добавить к условию (11.1) утверждение
("i: 0£i<k: PÙBB Þ (IF, P)) Ù (P Ù ØBB) Þ R ,
где P - инвариант цикла.
Другими словами, если P - инвариант, то он должен удовлетворять следующим условиям (11.4):
P должен выполняться непосредственно перед входом в цикл.
"i: 1£i£n: {PÙBi} Si {P}.
P Ù ØBB Þ R
В итоге получаем:
wp(DO, R) = $k: k³0: Ck(R) Ù ("i: 0£i<k: PÙBB Þ (IF, P)) Ù (P Ù ØBB Þ R) ,
где P - инвариант цикла.
Заметим, что не любое утверждение на множестве состояний итераций цикла является инвариантом цикла. Инвариант должен конструктивно помогать доказательству корректности цикла, т.е. что цикл заканчивается, т.е.
P Þ wp(DO, P Ù ØBB) (11.5)
и что заканчивается в состоянии, удовлетворяющем R, т.е.
(P Ù ØBB) Þ R (11.6)
В качестве упражнения докажите, что инварианты (11.2) и (11.3) обладают свойствами (11.5) и (11.6).
Рассмотрим следующий пример:
Доказать, что в следующем цикле P - инвариант и цикл заканчивается в нужном состоянии R.
{{T} - т.е. начинается с произвольного состояния}
i:=10, s:=0;
{P: 0£i£10 Ù s = (Sk: i+1£k£10: b[k])}
while i³1 do begin s:=s+b[i]; i:=i-1; end
{R: s = (Si: 1£i£10: b[i])}
Для того, чтобы доказать, что P - инвариант этого цикла, нам надо показать, что выполняются условия (11.4) 1-3. То, что P выполняется перед входом в цикл, доказать нетрудно. Для этого достаточно подставить в P значения i и s и учесть, что запись
(S k: i+1£k£10: b[k])
при i=10 даст 0.
Докажем, что выполняется условие (11.4) 2., т.е.
"k: 0£k£10: {PÙBk} S {P},
где Bk= k+i=10 Ù i ³1; s: s:=s+b[i]; i:=i-1.
Это несложно сделать с помощью метода математической индукции.
Теперь нам осталось показать (11.4)3, что
PÙØBBÞR,
это условие можно записать так
PÙi<1 ÞR.
Учитывая, что в условии Р переменная i от 0 до 10, следовательно i<1, только при i=0. Отсюда, поставив в Р значение i=0, получим требуемое утверждение R.
Теперь, имея представление о семантике основных операторов языка Pascal, рассмотрим,что же нам дает такое понимание семантики для создания программ? Здесь, конечно, уместно было бы спросить, что мы понимаем под словами “создание программ”? Но мы ответим на этот вопрос чуть позже. А пока вернемся к нашим программам 9.1 и 10.1 и попытаемся на них понять, что же нам дает знание семантики.
Перепишем программу из примера 9.1, но в качестве комментариев будем записывать предусловия и постусловия для операторов этой программы. Результат этого преобразования показан на рис. 11.1.
Program Harmonic (input, output);
{ Предусловие: (nÎ N)Ù(n>0);
Постусловие: s =
}var n, i : integer;
s : real;
begin
{ T }
write(’Введите значение n =’);
readln( n ); { nÎ N }
if n>0 then
begin {(nÎ N)Ù(n>0)}
s:=0 ;
i:=1; {i=1 Ù s=0 Ù n>0}
while i£n do
begin {s=Sk : 0<k<i : 1/k}
begin s:=s+1/i ; i:=i+1 end
{i=n ,
, n>0};writeln (’Сумма’, п , ’ членов
гармонического ряда =’’, s)
end {
}else {n£0} writeln (’Ошибка в исходных данных.
Должно быть’, n,’ >0’)
end {
}Рис. 11.1.
В первом комментарии сформулированы пост- и пред- условия, определяющие множество допустимых исходных данных и множество возможных результатов. Комментарий {T} между строками 2 и 3 утверждает, что в этой точке программы допустимо любое состояние. Действительно, мы ничего пока еще не можем сказать в этой точке о значениях переменных этой программы.
Однако, после оператора readln (n) (строка 4) мы уже можем утверждать, что nÎ N, т.е. целое. Как Вы уже, наверное, изучали на семинарских занятиях, Pascal - это строго типизированный язык программирования. Это означает, что каждой переменной в программе как бы сопоставляется предикат, определяющий тип этой переменной. Всякий раз, когда происходит запись нового значения в эту переменную, происходит вычисление значения этого предиката. Если оно T, то запись выполняется, если F, то выдается сообщение об ошибке, и выполнение программы прекращается. Поэтому, после оператора readln (n), т.е. после его нормального завершения, мы можем быть уверены, что n - целое значение. Но мы ничего не можем сказать о величине этого значения. Поэтому, нам надо убедиться,что n > 0 , прежде чем начать вычисления.
В строке 5 логическое выражение n>0 обеспечивает проверку исходных данных на соответствие предусловию программы. Поэтому, после строки 5 мы можем быть уверены, что любое состояние, полученное в этой точке, будет удовлетворять предикату, написанному в виде комментария - {(nÎ N)Ù(n>0)}.
Комментарий в конце 7 строки определяет состояния вычислительного процесса непосредственно перед входом в цикл. Комментарий после заголовка цикла (строка 8) - { s=Sk : 0<k<i : 1/k} - определяет инвариант цикл. То, что это действительно так, мы убедились ранее, при рассмотрении семантики цикла.
Комментарий после строки 9 определяет состояние вычислительного процесса, непосредственно после окончания цикла. Он как раз утверждает то, что в этом состоянии переменная s будет иметь в качестве своего значения сумму ряда
.Комментарий в строке 10, после end показывает, что в этой точке состояние вычислительного процесса не изменится, если не принимать в расчет переменную П - указатель номера выполняемого оператора (см. лекцию 2).
Комментарий в строке 11 утверждает, что в этой точке у любого состояния вычислительного процесса n£0. Ну и , наконец, последний комментарий утверждает, что в любом заключительном состоянии вычислительного процесса, соответствующего корректным исходным данным (предусловие выполнено), в этой программе
, т.е. постусловие для программы выполнено.Если теперь сравнить программу на рис. 11.1 с двумя другими формами этой же программы, представленными на рис. 9.1 и рис. 9.2 , то мы должны согласиться, что это новая форма, новая программа. Согласно определению 9.1 один и тот же алгоритм может иметь разные программы, его реализующие. Различия между ними могут отражать различия во взглядах, потребностях создателя, исполнителя и пользователя этими программами. Что демонстрирует программа на рис. 11.1?
Представим, что у компьютера, на котором мы будем исполнять эту программу, есть
кнопка, нажав которую, мы заставляем его выполнять очередной оператор программы и, выполнив его, остановиться;
после очередного нажатия, на специальном дисплее высветить текущие значения всех переменных программы.
Тогда, имея программу вычисления суммы n первых членов гармонического ряда в форме как на рис. 11.1, мы легко сможем проверить после каждого нажатия кнопки: действительно ли программа работает верно. Для этого достаточно подставить в предикат, определяющий текущее состояние, значения переменных из этого состояния, получить высказывания и вычислить его истинность. Если мы получим значение Т, то программа работает правильно, если F, то не правильно.
Заметим также, что, зная семантику операторов, мы могли бы доказать, что в случае программы из примера 9.1, для каждого из предусловий соответствующий опреатор приводит состояние, удовлетворяющее требуемому постусловию. И, что в итоге, мы получим состояние, удовлетворяющее постусловию для программы.
Таким образом, мы приходим к выводу, что программа на рис. 11.1 отражает взгляд на программу, показанную на рис. 9.1, того, кто будет проверять правильность этой программы.
Определение 11.1. Назовем процесс формулировки пред и постусловий как для программы вцелом, так и для отдельных ее частей, вплоть до опраторов, процессом спецификации программы.
Определение 11.2. Набор пред и постусловий в порядке их выполнения, как для программы вцелом, так и для отдельных ее частей, вплоть до операторов, назовем спецификацией программы.
Теперь мы можем сказать, что на рис. 11.1 представлена программа с рис. 9.2 со спецификацией.
На рис. 11.2 показана программа и ее спецификация для примера 10.1. В отличие от программы на рис. 11.1, в этой программе, после проверки корректности исходных данных, мы сначала выдаем сообщение об ошибке и только потом приступаем к обработке. В программе на рис. 11.1 сделано наоборот.
Итак, мы обнаружили с Вами три разных формы представления, вообще говоря, одной и той же программы:
для пользовтеля, кто будет читать и стараться понять как работает эта программа;
для компьютера, который будет выполнять эту программу;
для того, кто будет проверять правильность этой программы.
Program Factorial (input, output);
{ Предусловие: (nÎ N)Ù(n ³ 0)
Постусловие: (Fctrl=
) }var i , n , fctrl : integer;
begin { T }
write(¢Введите значение n = ¢);
readln( n ); { nÎ N }
if n < 0 then { n < 0 }writeln (¢Ошибка: n не может быть < 0¢)
else
begin { n ³ 0 }
if n = 0 then {n=0} fctrl:=1 {n=0Ùfctrl=1Ù(i не определенно)}
else
begin { n > 0 }
fctrl : =1 ;
{n=0 Ù fctrl=1}
for i : =2 to n do fctrl : = fctrl * i
{"i: 2 £ i £ n : Pk : 1£k<i : k};
{ n>0 Ù fctrl=(Pi : 1£i<n : i) }
writeln (¢ При n = ¢ , n , ¢ n! = ¢ , fctrl )
end
{ n ³ 0 Ù fctrl= n! };
end
end {Program}
Рис. 11.2.
Упражнения:
{?} 1. if a=1 then b:=a else b:=a+1; {b=1} {i=n Ù j=m} | wp (S5 , a > 0 Ù b > 0) = ? S5: if a > b then a:=a-b else if b < a then b:=b-a; |
2. if i=0 then j:=0 else j:=r; {?} | wp (S6 , x £ y) = ? S6: if x > y then begin t:=x;x:=y;y:=t end; |
{i=n Ù j=m} if i=0 then j:=0; {?} | Вычислить: wp(x:=2*y+3, х=13) wp(x:=х+y, х<2*y) wp(j:=j+1, 0<j Ù("i: 0 £ i £ j: b[i]=5)) wp(x:=х*y , х*y=c) |
4. if w £ r then begin r:=r-w; q:=q+1; end ; {q * w+r = x Ùr ³0} | case c of ¢ + ¢ : p:=p+1; ¢ - ¢ : m:=m+1; ¢(¢, ¢)¢ : b:=b+1; ¢*¢,¢/¢ : p:=b+1; end {p>0, m>0, b=0} |
Цикл.
Проверка цикла.
Доказать, что Р до входа в цикл
"i: 1 £ i £ n : {PÙBi} S {P}
PÙØBBÞR
Доказать,что в следующих циклах Р - инвариант и цикл заканчивается в нужном состоянии R:
1.
{0 £ n }
i:=0 ;
{P: 0 £ i £ n Ù xÏ b [0: i-1]}
while (i < n) and (x¹ b[i]) do i:=i+1;
{R: (0 £ i £ n Ù x= b[i])Ú( i = n Ù xÏ b [0: i-1])}
2.
{0 < n }
i:=1 ;
{P: 0 < i £ n Ù ($p: i=2p)}
while 2*i £ n do i:=2*i ;
{R: 0 < i £ n< 2*i Ù ($p: i=2p)}.