2.2. Теорема о цикле, его инварианте и ограничивающей функции
Предыдущее рассмотрение цикла while-do резюмирует теорема.
Теорема (о цикле while-do, его инварианте и ограничивающей функции). Рассмотрим цикл while B do S. Предположим, что предикат P удовлетворяет условию
1) {P & B} S {P}.
Далее пусть t – целочисленная функция от переменных программы, удовлетворяющая следующим условиям (t1 – переменная целого типа):
2) P & B (t > 0);
3) {P & B} begin t1 := t; S end {0 t < t1}.
Тогда имеет место свойство цикла
{P} while 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 & P} S {P}, т. е. P – инвариант;
3) показать, что not B & P R;
4) показать, что P & B (t > 0);
5) показать, что {P & B} begin t1 := t; S 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 i < t1) (0 n 2*i < n i) и (0 < 2*i n) wp (...).
2.3. Соотношение между хоаровскими инвариантами цикла и индуктивными утверждениями Флойда
Индуктивное утверждение Флойда, согласованное с предусловием Q, приписано некоторой точке программы и является справедливым при каждом прохождении этой точки, если до выполнения программы относительно переменных было справедливо утверждение Q. Например, для цикла while-do и точки в конце тела цикла можно записать
{Q} while B do begin S {Ф} end,
где Ф – индуктивное утверждение Флойда. Очевидно, хоаровский инвариант цикла является флойдовским индуктивным утверждением, а именно таким, что имеет место свойство тела цикла {inv & B} S {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 n < 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 < 2q + 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 & B} S {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*i = 2j + 1) & (j + 1 0).
Теперь надо показать, что это утверждение следует из утверждения inv & B, т. е. из (0 < m*i n < m*(2*i)) & (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).
Очевидно, при i > 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) & H} m := 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 (m div 2) + (j + 1)))
и учтём, что log2 (m div 2) + 1 = log2 m. Последнее равенство очевидным образом справедливо при четном m, а при нечетном m его справедливость следует из соотношений log2 m = log2 (m 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 m + j = 1 + j + log2 m
и, таким образом, log2 n log2 m + 1 + j , что противоречит исходному утверждению 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*x; y := y + 1 {x < 1} не выполняется, т. е. {x < 1} – не есть хоаровский инвариант.
