LẶP.
Cho : P, Q là các tân từ trên các biến của chương trình , S là một lệnh tổ hợp từ các lệnh gán với cấu trúc điều kiện và tuần tự. Chứng minh đặc tả : { P } S { Q} đúng đầy đủ .
Ở đây vì mỗi lệnh chỉ được thi hành một lần nên tính dừng của đoạn lệnh S được suy ra từ tính dừng của lệnh gán mà luôn được xem là hiển nhiên . Vì vậy trong trường hợp này tính đúng có điều kiện trùng với tính đúng đầu đủ.
1) Bài toán 1 : S là dãy tuần tự các lệnh gán .
Ví dụ1 : Kiểm chứng tính đúng của đoạn lệnh hoán đổi nội dung 2 biến x và y a) {(x=xo) and ( y = yo ) }
t := x ; x := y ; y := t ; {(x=yo ) and (y = xo ) } Chứng minh
{(x = yo ) and ( t = xo) } y := t {(x = yo ) and ( y = xo ) } (a) // tiên đề gán {(y = yo ) and ( t = xo ) } x := y {(x = yo ) and ( t = xo ) } (b ) // tiên đề gán {(y = yo ) and ( t = xo ) } x := y ; y := t {(x = yo) and ( y = xo ) } (c) // a , b , luật tuần tự
{(y = yo ) and ( x = xo ) } t := x {(y = yo ) and ( t = xo ) } (d) // tiên đề gán ( (x = xo ) and (y = yo ) ) ≡ ( ( y = yo ) and ( x = xo ) } (e ) // giao hoán {( x = xo ) and (y = yo ) } t := x {(y = yo ) and ( t = xo ) } (g) // d ,e, luật hệ quả
Kỹ thuật lập trình nâng cao - 65 -
{(x = xo ) and ( y = yo ) } t := x ; x := y ; y := t {(x = yo ) and ( y = xo ) (h) // c ,g , luật tuần tự Ví dụ1 : Kiểm chứng tính đúng của đặc tả : { (m :: k=2*m) and (y * zk = c)} k := k div 2 ; z := z * z ; { y * zk = c} Chứng minh :
(a) {y * (z*z)k = c} z := z * z {y*zk = c} (tiên đề gán) (b) {y * (z*z)k div 2 = c} k := k div 2 {y*(z*z)k = c} (tiên đề gán) (c) {y * (z*z)k div 2 = c} k := k div 2 ; z := z*z {y*z)k = c} (a ,b , luật tuần tự) (d) (m :: k = 2*m) and ( y * zk = c ) ==> (y*z2m = c) and ( m = k div 2 )
==> y * (z*z)k div 2 = c c ,d , luật hệ quả suy ra ĐPCM.
Nhận xét :
Với dẫy tuần tự các lệnh gán, việc chứng minh {P} S1; ...;Sn {Q} thướng được bắt đầu từ lệnh cuối cùng, dùng tiên đề gán để được đkđ, rồi cứ thế lần ngược về đến S1.
{Pn} Sn {Q} (n) tìm Pn từ Sn ,Q và tiên đề gán {Pn-1 } Sn-1 {Pn } (n-1) tìm Pn-1 từ Sn-1 , Pn và tiên đề gán {Pn-1 } Sn-1 ; Sn {Q} luật về dãy lệnh tuần tự
... ...
{P1 } S 1 ;...; S n {Q} (1) sau n-1 lần tương tự như trên. Sau đó dùng các tính chất của dữ kiện chứng minh logic rằng : P ==> P1 (0)
Từ (1) , (0) ,dựa vào luật hệ quả ta có : {P} S1 ; ... ; Sn {Q} ( ĐPCM ) 2) Bài toán 2 :
a) Kiểm chứng đặc tả : {P} if B then S1 else S2 {Q} Với S1, S2 là nhóm các lệnh gán , B là biểu thức boolean. Cách chứng minh :
+ Bước 1 : Tìm P1, P2 thỏa : {P1} S1 {Q} (1a) {P2} S2 {Q} (1b)
+ Bước 2 : Chứng minh ( dùng các tính chất logic và đại số ) P and B ==> P1 (2a)
P and (not B) ==> P2 (2b)
+ Bước 3 : Dùng luật hệ quả suy ra :
{P and B} S1 {Q} ( 3a) // 1a ,2a , và luật hệ qủa {P and (not B)} S2 {Q} ( 3b) // 1b ,2b , và luật hệ qủa + Bước 4 : Dũng (3a) , (3b) , luật điều kiện suy ra :
Kỹ thuật lập trình nâng cao - 66 -
{P} if B then S1 else S2 {Q} ( ĐPCM )
b) Kiểm chứng đặc tả : {P} S0 ; if B then S1 else S2 {Q} (*) với S1, S2, S0 là dẫy các lệnh gán Ví dụ : Kiểm chứng đặc tả : {y > 0} x := y-1 ; if (y > 3) then x := x*x else y := y-1 {x >= y} Để khẳng định được (*) ta cần chỉ ra 1 khẳng định R mà : {P} S0 {R}
và {R} if B then S1 else S2 {Q} rồi dùng luật hệ quả để có (*)
Làm thế nào để tìm được R ? Do S1 và S2 là nhóm lệnh gán tuần tự nên ta có thể tìm được (bằng tiên đề gán và luật về dãy lệnh tuần tự ) U và V để :
{U} S2 {Q} và {V} S3 {Q} .
Dĩ nhiên ta muốn U và V là các điều kiện tổng quát nhất có thể (ở đây là yếu nhất). R được xây dựng thế nào từ U và V ? Khả năng tổng quát nhất cho R để sau khi điểm điều kiện B sẽ có được U hoặc V là : R ≡ (B ==> U) and (not B ==> V)
Như sau này sẻ chỉ ra U , V , R được xây dựng như vậy là yếu nhất (weakest precondition) để đạt được Q tương ứng với lần lượt các lệnh S1 , S2 và if B then S1 else S2 , và được ký hiệu là : WP(S2,Q) ,WP(S3,Q) và WP(if B then S2 else S3, Q) tương ứng. Ví dụ 1 : Kiểm chứng đặc tả : { y > 0 } x := y - 1 ; if ( y > 3 ) then x := x * x else y := y - 1 ; { x >= y } Trong ví dụ này : P là tân từ : ( y > 0 ) ; Q là tân từ : ( x >= y ) B là biểu thức boolean : ( y > 3 ) S0 là lệnh gán : x := y - 1 ; Do S1 và S2 là lệnh gán : x := x * x ; S2 là lệnh gán : y := y - 1 ; Ta có : {x2 >= y} x := x*x {x >= y} suy ra U ≡ WP( S1 , Q ) x≡ 2 >= y (a)
{x >= y-1} y := y-1 {x >= y} suy ra V ≡ WP( S2 , Q ) x >= y-1
Kỹ thuật lập trình nâng cao - 67 -
Đặt R (B ==> U) and (not B ==> V) ≡
≡ ((y > 3) ==> (x2 >= y)) and ((y <= 3) ==> (x >= y-1))
Ta chứng minh được dễ dàng.
R and (y>3) ==> (x2 >= y) (c) R and (not(y>3)) ==> (x >= y-1) (d) nên theo luật hệ quả
{R and y>3} S2 {x >= y} ( {R and B} S2 {Q} ) (e) {R and not(y>3)} S3 {x >= y} ( {R and (not B)} S3 {Q} ) (g) Theo luật về lệnh chọn
{R} if ( y>3) then x := x*x else y := y-1 {x >=y} (h) theo tiên đề gán.
{ ((y>3) ==> ((y-1) 2 >=y)) and ((y <=3) ==> ((y-1) >= (y-1))) } x := y -1
{ R } (i) Dễ kiểm chứng được
(y>3) ==> ((y-1) 2 >= y ≡ true (j) (y<=3) ==> (y-1) >= y-1 ≡ true (k) nên
(y >0) ==> ((y>3) ==>((y-1)2 >=y)) and ((y<=3) ==> ((y-1) >=(y-1))) (l) Theo luật hệ quả
{y > 0} x := y-1;
if (y>3) then x := x*x else y := y-1 { x >= y} (m) // ĐPCM
Ví dụ 2 : kiểm chứng đặctả : { true }
if ( i <= j) then if (j<k ) then m := k else m := j else if( i<k) then m := k else m := i {(m >= i) and (m >= j) and (m >= k)}
Đặt Q(m) ( m >= i) and (m >= j) and (m >= k) ≡ Ta có :
(a) {Q(i)} m := i {Q(m)} (tiên đề gán) (b) {Q(k)} m := k {Q(m)} (tiên đề gán)
Đặt R1 ((i < k) ==> Q(k)) and ( (i >= k) ==>Q(i)) ≡ dùng luật về lệnh chọn ta sẽ chứng minh được :
{R1} if ( i < k) then ... {Q(m)} (c)
Kỹ thuật lập trình nâng cao - 68 -
Ta có: {R2} if (j < k ) then ... {Q(m)} (d) Dùng biến đổi đại số và logic để chứng minh ( true and ( i <= j)) ==> R2 (e)
( true and ( i > j )) ==> R1 (g)
Từ c , d, e ,g , theo luật về lệnh chọn ta có ĐPCM. Nhận xét :
Để chứng minh đặc tả {P} S {Q} với S là tổ hợp lệnh gồm chỉ các lệnh gán và điều
kiện đúng đầy đủ ,ta thực hiện công việc xây dựng điều kiện đầu yếu nhất P1 của S ứng với Q , sau đó bước kiểm chứng cuối cùng chỉ đơn giản là chứng minh : P ==> P1. Công việc trên được trình bày dưới dạng một hàm đệ quy như sau :
function DKDYN (S : nhóm_lệnh ; Q : tân_từ ) : tân_từ ;
var t : câu lệnh ; begin
if (S <> rỗng ) then begin
t := lệnh_cuối(S); S := S – t ;
if ( t = lệnh_gán(x:=bt)) then DKDYN := DKDYN(S,Q( x=bt) )
else (* t là lệnh if *)
DKDYN := (điều_kiện(t)==>DKDYN(phần_đúng(t),Q)) and not (điều_kiện(t)==>DKDYN(phần_khong đúng(t),Q)) end
else DKDYN := Q end ;