A definição destas condições, no caso de uma rotina, funciona como um contrato estabelecido entre o programador produtor e o programador consumidor da rotina: "desde que o programador consumidor desta rotina a invoque em em circunstâncias em que a pré-condição é verdadeira, o programador produtor desta rotina garante que depois de executada(o) a condição objectivo é verdadeira".
Se se pretender escrever uma função
int
somaQuadrados(int const n)
que some os quadrados dos primeiros
n
inteiros não-negativos, então a estrutura da função,
com a pré-condição e a condição objectivo,
poderá ser:
onde CO é a condição objectivo do corpo da função antes da instrução de retorno.
/**
Devolve a soma dos primeirosn
inteiros não-negativos.@pre (pré-condição da função) 0 <=
n
.@post (condição objectivo da função)
somaDosPrimeirosQuadrados
= (S k : 0 <= k <n
: k2).*/
int somaDosPrimeirosQuadrados(int const n)
{
//
PC (pré-condição do corpo da função): 0 <=n
.int soma_dos_quadrados =
...;
//
CO (condição objectivo do corpo da função):
//
soma_dos_quadrados
= (S k : 0 <= k <n
: k2).return soma_dos_quadrados;
}
Se se pretender escrever uma função
int raizInteiraDe(int const x)
que, assumindo que x
é não-negativo,
devolva o maior inteiro n menor ou igual à raiz quadrada
de x
, então a estrutura da função, com a
pré-condição e a condição objectivo,
poderá ser:
onde CO é a condição objectivo do corpo da função antes da instrução de retorno. Note-se que é a relação
/**
Devolve a melhor aproximação por defeito da raiz quadrada dex
.@pre (pré-condição da função) 0 <=
x
.@post (condição objectivo da função) 0 <=
raizInteiraDe
<=x
½ <raizInteiraDe
+ 1,ou seja, 0 <=
raizInteiraDe
2 <=x
< (raizInteiraDe
+ 1)2.*/
int raizInteiraDe(int const x)
{
//
PC (pré-condição do corpo da função): 0 <=x
.int r =
...;
//
CO (condição objectivo do corpo da função): 0 <=r
2 <=x
< (r
+ 1)2.return r;
}
x
½
< raiz
+ 1 que garante que o valor
devolvido é o maior inteiro menor ou igual a x
½.Voltando à função somaDosPrimeirosQuadrados
()
,
é evidente que o corpo da função pode consistir num
ciclo cuja condição objectivo já foi apresentada:
A condição invariante do ciclo pode ser obtida substituindo na condição objectivo CO a constanteCO:
soma_dos_quadrados
= (S k: 0 <= k <n
: k2).
n
por
uma variável i
, com limites apropriados, ficando portanto
Esta escolha corresponde a relaxar a condição objectivo, pois a condição invariante aceita mais possíveis valores para a variávelCI:
soma_dos_quadrados
= (S k: 0 <= k <i
: k2) e 0 <=i
<=n
.
soma_dos_quadrados
do que a condição
objectivo, que só aceita o resultado final pretendido. Os
valores aceites para essa variável pela condição invariante
correspondem a valores intermédios durante o cálculo do valor
final. Neste caso correspondem a todas as possíveis somas
de quadrados: desde a soma com zero termos até à soma com
os n
termos pretendidos.
A guarda pode ser obtida facilmente observando que no final do ciclo a guarda será forçosamente falsa e a condição invariante verdadeira (i.e., que CI e ¬G), e que se pretende, nessa altura, que a condição objectivo do ciclo seja verdadeira. Ou seja, sabe-se que no final do ciclo
e pretende-se que
soma_dos_quadrados
= (S k: 0 <= k <i
: k2) e 0 <=i
<=n
e ¬G
A escolha mais simples da guarda que garante a verificação da condição objectivo éCO:
soma_dos_quadrados
= (S k: 0 <= k <n
: k2).
¬G:
i
=n
,
ou seja,
G:i
<>n
Voltando à função raizInteiraDe
()
, é
evidente que o corpo da função pode consistir num ciclo cuja
condição objectivo já foi apresentada:
ou seja,CO: 0 <=
r2
<=x
< (r
+ 1)2,
Escolhendo para negação da guarda (¬G) o predicadoCO: 0 <=
r2
er2
<=x
ex
< (r
+ 1)2
.
x
< (r
+ 1)2,
ou seja, escolhendo
obtém-seG: (
r
+ 1)2 <=x
,
Esta escolha faz com que CI e ¬G seja equivalente a CO, pelo que, se o ciclo terminar, termina com o valor correcto.CI: 0 <=
r
2 er
2 <=x
.
Esta escolha da condição invariante corresponde a relaxar
a condição objectivo, pois a condição invariante
aceita mais possíveis valores para a variável r
do que a condição objectivo, que só aceita o resultado
final pretendido.
somaDosPrimeirosQuadrados()
somaDosPrimeirosQuadrados
()
,
a condição invariante é
pelo que a forma mais simples de se inicializar o ciclo é com as instruçõesCI:
soma_dos_quadrados
= (S k: 0 <= k <i
: k2) e 0 <=i
<=n
,
pois nesse caso a condição invariante fica
int resultado = 0;
int i = 0;
que tem sempre valor verdadeiro, pois a pré-condição indica que 0 <=CI: 0 = (S k: 0 <= k < 0 : k2) e 0 <= 0 <=
n
,
n
.
raizInteiraDe()
raiz()
,
a condição invariante é
pelo que a forma mais simples de se inicializar o ciclo é com a instruçãoCI: 0 <=
r
2 er
2 <=x
,
pois nesse caso a condição invariante fica
int r = 0;
que tem sempre valor verdadeiro, pois a pré-condição indica que 0 <=CI: 0 <= 0 e 0 <=
x
,
x
.somaDosPrimeirosQuadrados()
somaDosPrimeirosQuadrados
()
,
a guarda é
pelo que o progresso mais simples é a simples incrementação deG:
i
<>n
i
. Ou seja, o passo do ciclo é
onde se assume a condição invariante como verdadeira no antes da acção e se pretende escolher uma acção de modo a que o seja também depois do passo. Usando a semântica da operação de atribuição, pode-se deduzir a condição mais fraca antes do progresso de modo a que condição invariante seja verdadeira no final do passo:
//
CI e G:soma_dos_quadrados
= (S k : 0 <= k <i
: k2) e 0 <=i
<=n
ei
<>n
,//
ou seja,// soma_dos_quadrados
= (S k : 0 <= k <i
: k2) e 0 <=i
<n
.
acção++i; // i = i + 1;
//
CI:soma_dos_quadrados
= (S k : 0 <= k <i
: k2) e 0 <=i
<=n
.
Falta determinar uma acção tal que
// soma_dos_quadrados
= (S k : 0 <= k <i
+ 1 : k2) e 0 <=i
+ 1 <=n
,//
ou seja,// soma_dos_quadrados
= (S k : 0 <= k <i
+ 1 : k2) e -1 <=i
<n
.++i; // i = i + 1;
//
CI:soma_dos_quadrados
= (S k : 0 <= k <i
: k2) e 0 <=i
<=n
.
A acção mais simples é portanto:
// soma_dos_quadrados
= (S k : 0 <= k <i
: k2) e 0 <=i
<n
.
acção// soma_dos_quadrados
= (S k : 0 <= k <i
+ 1 : k2) e -1 <=i
<n
.
pelo que o o ciclo (e a função) completo é
soma_dos_quadrados += i * i;
ou, substituindo pelo ciclo com a instrução
/**
Devolve a soma dos primeirosn
inteiros não-negativos.@pre (pré-condição da função) 0 <=
n
.@post (condição objectivo da função)
somaDosPrimeirosQuadrados
= (S k : 0 <= k <n
: k2).*/
int somaDosPrimeirosQuadrados(int const n)
{
int soma_dos_quadrados = 0;
int i = 0;
//
CI:soma_dos_quadrados
= (S k : 0 <= k <i
: k2) e 0 <=i
<=n
.while(i != n) {
soma_dos_quadrados += i * i;
++i;
}
return soma_dos_quadrados;
}
for
equivalente:
/**
Devolve a soma dos primeirosn
inteiros não-negativos.@pre (pré-condição da função) 0 <=
n
.@post (condição objectivo da função)
somaDosPrimeirosQuadrados
= (S k : 0 <= k <n
: k2).*/
int somaDosPrimeirosQuadrados(int const n)
{
int soma_dos_quadrados = 0;
int i = 0;
//
CI:soma_dos_quadrados
= (S k : 0 <= k <i
: k2) e 0 <=i
<=n
.for(int i = 0; i != n; ++i)
soma_dos_quadrados += i * i;
return soma_dos_quadrados;
}
raizInteiraDe()
raizInteiraDe
()
,
a guarda é
pelo que o progresso mais simples é a simples incrementação deG: (
r
+ 1)2 <=x
,
r
. Ou seja, o passo do ciclo é
onde se assume a condição invariante como verdadeira no antes da acção e se pretende escolher uma acção de modo a que o seja também depois do passo. Usando a semântica da operação de atribuição, pode-se deduzir a condição mais fraca antes do progresso de modo a que condição invariante seja verdadeira no final do passo:
//
CI e G: 0 <=r
2 er
2 <=x
e (r
+ 1)2 <=x
.
acção++r; // r = r + 1;
//
CI: 0 <=r
2 er
2 <=x
.
Como 0 <=
//
CI e.G: 0 <=r
2 er
2 <=x
e (r
+ 1)2 <=x
.
acção//
0 <= (r
+ 1)2 e (r
+ 1)2 <=x
++r; // r = r + 1;
//
CI: 0 <=r
2 er
2 <=x
r
2
implica 0 <= (r
+ 1)2,
então é evidente que neste caso não é necessária
qualquer acção! Assim, o ciclo (a função)
completo é:
/**
Devolve a melhor aproximação por defeito da raiz quadrada dex
.@pre (pré-condição da função) 0 <=
x
.@post (condição objectivo da função) 0 <=
raizInteiraDe
<=x
½ <raizInteiraDe
+ 1,ou seja, 0 <=
raizInteiraDe
2 <=x
< (raizInteiraDe
+ 1)2.*/
int raizInteiraDe(int const x)
{
int r = 0;
//
CI: 0 <=r
2 er
2 <=x
.while((r + 1) * (r + 1) <= x)
++r;
return r;
}