Как общее замечание, часто хорошей идеей будет называть ваши аннотации, особенно когда вы начинаете иметь несколько инвариантов l oop для одного l oop. Это позволит вам быстрее выявлять неисправности (см. Пример ниже, хотя вы можете не соглашаться с выбранными мной именами).
Теперь вернемся к вашим проблемам: главное в том, что ваш инвариант 2 слишком слабый Если n
в текущем l oop нечетно, вы не можете установить sh, что неравенство выполняется на следующем шаге. С более жесткой границей, а именно \at(n,Pre) < (n+1) * pow2(res)
, гипотеза в начале текущего шага достаточно сильна, чтобы доказать, что инвариант выполняется в конце шага, при условии, что мы знаем, что res
не будет переполнено (в противном случае 1+res
в конечном итоге станет 0
, и неравенство больше не будет сохраняться).
Для этого я использовал промежуточную функцию-призрак, чтобы доказать, что n < pow2(n)
для любого unsigned
, что позволяет мне, благодаря инварианту pow2_lower
ниже, чтобы гарантировать, что res_bound
сохраняется на любом шаге l oop.
Наконец, небольшое замечание по pow2
: здесь это не имеет значения, так как аргументы являются unsigned
, следовательно, неотрицательны, но в общем случае аргумент integer
может быть отрицательным, поэтому вы можете захотеть сделать определение более надежным, возвращая 1
всякий раз, когда n<=0
.
В целом, следующая программа полностью доказана с Frama- C 20 и Alt-Er go (frama-c -wp -wp-rte file.c
). Есть еще два утверждения, которые, по-видимому, необходимы, чтобы направлять Alt-Er go в поиске доказательства.
#include "stddef.h"
/*@ logic integer pow2(integer n) = n<=0?1:2*pow2(n-1); */
/*@ ghost
/@ assigns \nothing;
ensures n < pow2(n);
@/
void lemma_pow2_bound(unsigned n) {
if (n == 0) return;
lemma_pow2_bound(n-1);
return;
}
*/
/*@
requires n > 0;
assigns \nothing;
ensures pow2(\result) <= \old(n) < pow2(\result + 1);
*/
unsigned int log2(size_t n)
{
unsigned int res = 0;
/*@
loop invariant n_bound: 0 < n <= \at(n, Pre);
loop invariant pow2_upper: \at(n, Pre) < (n+1) * pow2(res);
loop invariant pow2_lower: n*pow2(res) <= \at(n, Pre);
loop invariant res_bound: 0 <= res < \at(n,Pre);
loop assigns n, res;
loop variant n;
*/
while (n > 1) {
L:
/*@ assert n % 2 == 0 || n % 2 == 1; */
n /= 2;
/*@ assert 2*n <= \at(n,L); */
res++;
/*@ ghost lemma_pow2_bound(res); */
}
//@ assert n == 1;
return res;
}