On se demande si un algorithme – ou, cela revient au même, une fonction – est correct. C'est à dire que l'on veut savoir si l'algorithme fait bien ce qu'il est censé faire et dans quelles conditions il fonctionne correctement.
Précondition
Il s'agit d'un condition portant sur les entrées. Pour que l'algorithme fonctionne correctement, il faut que les préconditions soient respectées.
Postcondition
Ce sont les conditions que nous voulons voir réalisées à la fin de l'exécution de l'algorithme. C'est en vérifiant que les postconditions sont respectées que l'on pourra juger que l'algorithme a fait ce qu'il devait.
Terminaison
On veut que l'algorithme se termine, c'est à dire que son temps d'exécution soit fini. En pratique, on a besoin que ce temps ne soit pas trop long. En théorie, il suffit que ce temps soit fini, même s'il est très grand. Un algorithme termine si son temps d'exécution est fini chaque fois que les préconditions sont vérifiées.
Correction
On dira qu'un algorithme (ou fonction) est correct s'il termine ET que préconditions $\Rightarrow$ postconditions.
Je considère une fonction solve(A:int) → int qui renvoie le plus petit entier N tel que N**3 >= A.
def solve(A:int) -> int:
'''
renvoie le plus petit entier N tel que N**3 >= A
'''
n = 0
while n**3 < A:
n += 1
return n
Cette fonction contient une boucle while. Il faut regarder ces boucles avec soin car elles sont potentiellement illimités.
Ici, n augmente de 1 à chaque boucle, donc n**3 augmente d'au moins 1 à chaque boucle, et donc n**3 finira par dépasser A.
La fonction termine donc.
On a déjà vérifié que la fonction termine.
Fait-elle ce qu'elle doit ?
Non ! En effet, si A = -10, la fonction renvoie 0 alors que la réponse aurait dû être -2.
La fonction a été écrite en supposant implicitement que A serait positif. Si A >= 0, on n'a plus de problème. Il faut faire attention à ces implicites. Les préconditions sont là justement pour expliciter.
On ajoute donc la précondition : A >= 0. Réécrivons la fonction :
def solve(A:int) -> int:
'''
A entier positif
renvoie le plus petit entier N tel que N**3 >= A
'''
n = 0
while n**3 < A:
n += 1
return n
La précondition a été écrite dans le commentaire. Dans certains cas, cela peut suffire. Mais rien n'empêche ici de demander :
>>> solve(-10) 0
La précondition écrite dans le commentaire n'est qu'informative et n'interdit pas un mauvais usage de la fonction.
On peut être plus contraignant :
def solve(A:int) -> int:
'''
A entier positif
renvoie le plus petit entier N tel que N**3 >= A
'''
assert A >= 0
n = 0
while n**3 < A:
n += 1
return n
assert énonce une condition qui doit être valide. Si elle ne l'est pas, l'exécution s'interrompt sur une erreur.
Avec la précondition A >= 0, la fonction est correcte.
La commande assert a d'autres usages :
assert et de s'en servir pour vérifier la cohérence de l'ensemble. Ainsi, si on a affirmé par assert que telle variable devait être entière mais qu'ailleurs on écrit un float dans cette variable, l'environnement nous préviendra de l'incohérence.