Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Разработка корректных программ / 2Верификация / 2ОсновыАналитВерифПрогРовно.doc
Скачиваний:
34
Добавлен:
01.05.2014
Размер:
245.25 Кб
Скачать

2.2. Теорема о цикле, его инварианте и ограничивающей функции

Предыдущее рассмотрение цикла while-do резюмирует теорема.

Теорема (о цикле while-do, его инварианте и ограничивающей функции). Рассмотрим цикл while B do S. Предположим, что предикат P удовлетворяет условию

1) {P & BS {P}.

Далее пусть t – целочисленная функция от переменных программы, удовлетворяющая следующим условиям (t1 – переменная целого типа):

2) P & B  (t > 0);

3) {P & Bbegin t1 := t; S end {0  t < t1}.

Тогда имеет место свойство цикла

{Pwhile B do S {P & not B}

и цикл завершится.

Аннотирование цикла и понимание аннотаций. Цикл рекомендуется оформлять следующим образом:

{Q}

{inv P: инвариант }

{bound t : ограничивающая функция }

while B do

begin

S

end {while};

{R}

Здесь Q – предусловие, а R  постусловие цикла, и выполняются соотношения Q  P, not B & P  R.

Список условий для проверки (аннотированного) цикла:

1) показать, что P – истинно до выполнения цикла, т. е. Q  P;

2) показать, что тело цикла имеет свойство {B & PS {P}, т. е. P – инвариант;

3) показать, что not B & P  R;

4) показать, что P & B  (t > 0);

5) показать, что {P & Bbegin t1 := tS end {0  t < t1}, т. е. t уменьшается на каждом шаге цикла.

Рассмотрим пример:

var n, i: Integer; { n > 0 }

i := 1;

{inv P: (0 < i  n)} {bound t: ni}

while (2*i) <= n do i := 2*i ;

{post: ( 0<i  n < 2*i)}

Проверим 5 указанных ранее условий.

1) Очевидно {(n > 0) & (i = 1)}  inv.

2) Проверим свойство {B & inv} i := 2*i {inv}. В соответствии с правилом вывода оператора присваивания имеем: {предусловие} i := 2*i {0<i  n}. Слабейшее предусловие есть {0 < 2*i  n}. Очевидно, что оно следует из (B & inv), которое в данном случае есть {(2*i  n) & (0 < i  n)}.

3) Утверждение not B есть 2*i > n. Отсюда очевидно, что (not B & inv)  post.

4) t = n  i > 0 при 0 < 2*i  n.

5) Проверим свойство {0 < 2*i  n} t1:= n  i; i := 2*i {0  t < t1}. Действительно, wp(t1 := n  i;  i:= 2*i  0  n  t1)  (0  n  2*i <  i) и (0 < 2*i  n)  wp (...).

2.3. Соотношение между хоаровскими инвариантами цикла и индуктивными утверждениями Флойда

Индуктивное утверждение Флойда, согласованное с предусловием Q, приписано некоторой точке программы и является справедливым при каждом прохождении этой точки, если до выполнения программы относительно переменных было справедливо утверждение Q. Например, для цикла while-do и точки в конце тела цикла можно записать

{Qwhile B do begin S {Ф} end,

где Ф – индуктивное утверждение Флойда. Очевидно, хоаровский инвариант цикла является флойдовским индуктивным утверждением, а именно таким, что имеет место свойство тела цикла {inv & BS {inv} и, кроме того, Q  inv и inv  Post. Оказывается, существуют индуктивные утверждения, которые не являются хоаровскими инвариантами.

В качестве примера рассмотрим ту же задачу, что и в 2.2, но напишем другую программу. Итак, требуется верифицировать программу:

var n, m, i: Integer; …{n>0}

m := n; i := 1;

while m > 1 do begin m := m div 2; i := i*2 end

{Post: (0<i  < 2*i)}.

Содержательно понять смысл программы помогает рассмотрение двоичной записи значений переменных n и m. Пусть двоичная запись числа n содержит log2 n + 1 двоичных разрядов. Иными словами, q = log2 n  есть показатель старшей степени двойки в двоичной записи числа n. Тогда на каждом шаге отбрасываем один бит (операция m := m div 2) и определяем вес (степень двойки) младшего разряда нового значения m в записи исходного числа n (операция i := i*2). Когда дойдем до старшей единицы (m = 1), то получим требуемое i (см. утверждение Post), т. е. i =2q и 2q  n < 2+ 1.

Аннотируем данную программу. Приведенные только что соображения позволяют сформулировать инвариантное утверждение в виде

inv: (0<m*i  n < m*(2*i)) & (i = 2j) & (j  0).

Здесь для записи утверждения введена «переменная-призрак» j, способствующая лучшему пониманию программы. Тогда имеем

{n > 0}

m := n; i := 1; {j := 0: Integer}

{inv}

while m > 1 do begin m := m div 2; i := i*2 {j := j + 1} end

{Post: (0 < i  n < 2*i) & (i = 2j) & (j  0) & (m = 1)}.

Очевидно, первое и третье условия проверки аннотированного цикла выполнены: 1) Pred  inv; 3) (m = 1) & inv  Post.

Основной интерес представляет выполнение второго условия: обладает ли S свойством {inv & BS {inv}? Как обычно, в случае с присваиваниями надо показать, что inv & B  wp (S  inv), при этом, по определению, имеет место свойство {wp (S  inv)} S {inv}.

Итак, пользуясь правилом вывода для операторов присваивания определим wp(S  inv) для тела цикла S  begin m := m div 2; i := i*2 {j := j + 1}end:

(0 < (m div 2)*(2*i)  n < (m div 2)*2*(2*i)) & (2*= 2j + 1) & (j + 1  0).

Теперь надо показать, что это утверждение следует из утверждения inv & B, т. е. из (0 < m*i  n m*(2*i)) & (= 2j) & (j  0) & (m > 1). Так как (m div 2)*2  m, то (0 < m*i  n)  (0 < (m div 2)*(2*i)  n). Для правой части двойного неравенства для n аналогичным образом должны были бы иметь

(n < m*(2*i))  (n < (m div 2)*2*(2*i)).

Но это неверно! Ввиду важности вопроса сформулируем проблему, несколько изменив (упростив) задачу. Итак, сложилась следующая ситуация. Имеется программа Prog:

{Prog:} m := n; i := 1;

while m > 1 do

begin

m := m div 2; i := i*2

{точка A}

end {while}

с предполагаемым свойством {n > 0} Prog {(n < 2*i) & (n > 0) & (i > 0)}.

Рассмотрим предполагаемое индуктивное утверждение Флойда Ф в точке A: (n < m*(2*i)) & (m  1) & (i > 0).

Очевидно, при > 0 имеем

wp(m := m div 2; i := i*2  (n < m*(2*i)) & (m  1)) 

 (n < (m div 2)*2*(2*i)) & (m > 1),

но полученное условие не следует из (m > 1) & Ф, т. е. неверно, что

(m > 1) & (n < m*(2*i))  (n < (m div 2)*2*(2*i)) & (m > 1).

Например, при n = 19, m = 5, i = 2 выполняется неравенство n < m*(2*i), т. е. 19 < 5*2*2 = 20. Однако неравенство n < (m div 2)*2*(2*i) при этом не выполняется. Действительно, (m div 2)*2*(2*i) = 2*2*2*2 = 16 < 19.

Таким образом, утверждение Ф не является хоаровским инвариантом цикла, так как тело цикла S не обладает свойством {B & Ф} S {Ф}.

Покажем, что, тем не менее, Ф есть индуктивное утверждение в смысле Флойда. Для этого рассмотрим другое утверждение (обозначим его H):

log2 n = log2 m + log2 i .

Сначала покажем, что оно является хоаровским инвариантом (и, следовательно, индуктивным утверждением), а затем  что H  Ф.

Используя «переменную-призрак» j и соотношение i = 2j, утверждение H можно записать в виде log2 n = log2 m + j. Докажем, что H – инвариант Хоара нашего цикла. Действительно:

1) перед циклом log2 n = log2 n + 0;

2) при выходе из цикла m = 1 и log2 n = 0 + j;

3) убедимся, что тело цикла обладает свойством

{(m > 1) & Hm := m div 2; i := i*2 {j := j + 1}  {(m  1) & H};

для этого рассмотрим утверждение

wp(S  (m  1) & log2 n = log2 m + j) 

 ((m div 2)  1) & (log2 n = log2 (div 2) + (+ 1)))

и учтём, что log2 (div 2) + 1 = log2 m. Последнее равенство очевидным образом справедливо при четном m, а при нечетном m его справедливость следует из соотношений log2 m = log2 ( 1) ,  m div 2 = (m  1) div 2, где (m  1) – уже чётное.

Итак, H – хоаровский инвариант и, следовательно, индуктивное утверждение. Покажем, наконец, что H  (n < m*2*i). От противного: пусть n  m*2*i, тогда в силу монотонности функций log2  и x имеем

log2 n  log2 (m*2*i) = 1 + log2 j = 1 + j + log2 m 

и, таким образом, log2 n  log2 m + 1 + , что противоречит исходному утверждению log2 n = log2 m + j.

Итак, утверждение (n < m*(2*i)) & (m  1) & (i > 0) является индуктивным утверждением Флойда, но не есть инвариант цикла в смысле Хоара.

Резюме: хоаровский инвариант цикла  это такое индуктивное утверждение, истинность которого устанавливается простейшей индукцией  индуктивный переход доказывается на одном шаге цикла, что в иной форме выражается свойством тела цикла {B & inv} S {inv}. В общей ситуации требуется применение метода математической индукции в его полном варианте, т. е. для доказательства справедливости индуктивного утверждения на текущем шаге цикла требуется использовать предположение о его справедливости на всех (!) предыдущих шагах. Иными словами, может потребоваться рассмотрение свойства всей последовательности состояний переменных при выполнении цикла, а не просто свойства изолированного (изъятого из этой последовательности) тела цикла, описывающего его поведение за один шаг.

Замечание. Полезно дать объяснение тому факту, что в рассмотренном примере утверждение H: log2 n = log2 m + j при n = 19, m = 5, i = 2 не справедливо (4  2 + 1).

Пример ситуации, когда для доказательства индуктивного утверждения требуется индукция за два шага:

var k, n, m: Integer; ...

{(k  0) & (m = n)}

while k <> 0 do begin k := k  1; n := n + (1)n end

{ n  m   1}

Неформальный анализ последовательности значений переменной n показывает, что

1) при четном m имеем n = m + 1, m, m + 1, m, m + 1, m, ... ;

2) при нечетном m имеем n = m  1, m, m  1, m, m  1, m, ... ,

т. е. последовательность значений n периодична с периодом 2, поэтому  n  m   1 – индуктивное утверждение. Формально этот факт устанавливается индукцией за пару шагов (база индукции – два первых шага). Однако утверждение   n  m   1 не является хоаровским инвариантом, поскольку тело цикла не обладает свойством { n  m   1} n := n + (1)n {  n  m   1}. Действительно, неверно, что ( n  m   1)  ( (n + (1)n )  m   1). Например, при k = 1, n = 3, m = 4.

Рассмотрим более простой пример несовпадения хоаровского инварианта и индуктивного утверждения:

var y: Integer; x: Real;

{x = 0}

while y < 10 do

begin x := 2*x; y := y + 1 end

{x < 1}

Очевидно, что {x < 1} – индуктивное утверждение, согласованное с предусловием. Однако свойство {x < 1} x := 2*xy := y + 1 {x < 1} не выполняется, т. е. {x < 1} – не есть хоаровский инвариант.