FRACTRAN est un langage de programmation exotique et Turing-complet s'appliquant à des entiers naturels. Il a été inventé par le mathématicien John Conway qui en publie une description en 1987Julian Havil, , p. 162-163.Dans l'article , paru dans le livre de Cover et Gopinath, , Springer-Verlag, New York, (1987), p. 4-26. ; son objectif est de généraliser le problème de Syracuse, en montrant que des questions de ce type peuvent être indécidables, car se ramenant au problème de l'arrêt.

Fonctionnement d'un programme FRACTRAN

Description des programmes Un programme FRACTRAN est constitué d'une liste ordonnée de fractions, et d'un nombre entier donnée en entrée. Le programme fonctionne en répétant la règle suivante :Considérons la première fraction de la liste telle que soit entier ; remplacer par Le programme s'arrête lorsque toutes les fractions sont telles que ne soit pas un entier.

Exemples

Considérons le programme FRACTRAN correspondant à la liste de fractions et l'entier

Ni , ni ne sont entiers. Donc le programme s'arrête.

Considérons le programme FRACTRAN et

n'est pas un entier, mais est l'entier 20. Ensuite, est l'entier 6. n'est pas un entier mais est l'entier 8. Ni ni ne sont entiers. Le programme s'arrête.

Principe

Le principe s'appuie sur l'existence d'une décomposition en produit de facteurs premiers des entiers, et sur la notion de valuation p-adique.

Tout entier A possède une décomposition en produit de facteurs premiers unique, dans laquelle l'exposant du nombre premier p est appelé valuation de p dans A, et est noté . Pour un entier A donné, les sont tous nuls, sauf un nombre fini d'entre eux. Par exemple, et , , alors que pour tout nombre premier p plus grand que 7.

Un programme FRACTRAN peut être vu comme fonctionnant sur une machine disposant d'une infinité de registres, chaque registre contenant un nombre entier. L'état de la machine est alors donné par le codage de Gödel, où les registres successifs représentent l'exposant des nombres premiers successifs. Par exemple, l'état 60 est donné par sa décomposition en facteurs premiers :

Il représente l'état où le premier registre (celui de 2) contient la valeur 2, le second (celui de 3) contient la valeur 1, le troisième (celui de 5) contient la valeur 1, et tous les autres registres sont à zéro. Ou encore, en termes de valuation, v2=2, v3=1 et v5=1.

Multiplier l'entier A par une fraction N/D permettant d'obtenir un entier B, consiste à opérer, en parallèle sur l'ensemble des p, des additions et des soustractions sur les : la valeur du registre est augmentée de la valuation de p dans N, et diminuée de celle de p dans D.

Analyse de l'exemple

Ainsi, la liste précédente (3/10, 4/3) n'opère que sur les valuations de 2, 3 et 5, qui sont les seuls facteurs premiers présents dans les deux fractions : La première fraction () enlève 1 aux valuations de 2 et 5, lorsque cela est possible, et dans ce cas augmente de 1 la valuation de 3. Cette première fraction agit donc tant que les valuations de 2 et de 5 sont strictement positives. La seconde fraction () ne prend le relais que lorsque la première ne peut plus opérer, c'est-à-dire quand la valuation de 2 ou de 5 est nulle. Dans ce cas, elle augmente la valuation de 2 de deux unités, et baisse celle de 3 d'une unité. Lorsque A s'écrit (multiplié par un facteur A’ quelconque premier avec 30), si aucune des deux fractions ne peut s'appliquer et le programme s'arrête, la valeur restant inchangée sinon si , avec la première fraction la procédure transforme A en , puis avec la seconde fraction transforme le résultat en , et la procédure s'arrête. si , le comportement de la suite est plus complexe : La première fraction donnera ; la seconde fraction prend le relais et transforme le résultat en . On peut alors appliquer à nouveau la première fraction pour obtenir . Ces deux actions ont ainsi augmenté de 1 l'exposant de 2, laissé inchangé celui de 3 et diminué celui de 5 ... et ainsi de suite, les fractions alternant leur transformation jusqu'à ce que l'exposant de 5 soit nul. La procédure s'arrête au nombre

Le « programme » que constitue la liste des fractions permet ainsi de définir des opérations sur les valuations.

Opérations basiques

Addition

La liste contenant l'unique fraction permet de faire la somme des valuations de 2 et de 3. Partons d'un nombre qui s'écrit . Les nombres du calcul sont : , , ..., . Ainsi, le programme sur N donne le nombre .

Soustraction

De manière similaire à l'addition, la liste contenant l'unique fraction transforme le nombre en , si ; , si ; si .

Boucle infinie

Les exemples précédents donnent des séries finies, mais ce n'est pas nécessairement le cas. Un programme comme (2/3 ; 3/2) appliqué à n'importe quel nombre d'entrée commencera dans un premier temps par transférer la valuation de 3 en l'additionnant à celle de 2.

À ce stade, si l'une ou l'autre des valuations n'est pas nulle au départ, le programme bouclera : Le registre des 3 étant épuisé, le programme ne peut appliquer [2/3] et appliquera [3/2], déplaçant une unité du registre des deux vers celui des 3. L'étape suivante, le registre des 3 contenant une unité, [2/3] s'appliquera en priorité, transférant l'unité en sens inverse. Ce système de bouclage est cependant très utile pour gérer des boucles de programmes grâce à des couples d'indicateurs d'états, comme le montre l'exemple suivant de la multiplication.

Transfert d'un nombre

Le transfert d'un nombre d'un registre à l'autre s'effectue comme une addition.

Par exemple, le transfert du registre des 2 (v2) vers celui des 3 (v3) s'effectuera par la réitération de l'opération (3/2) sur le nombre

Deux points importants sont à noter pour de telles opérations : Un transfert ne se fait (généralement) pas en une opération unique, mais constitue souvent une étape dans un programme. De ce fait, il faut garder en tête que sans autres précautions, la procédure permet que d'autres opérations sont susceptibles de s'intercaler pendant un transfert, lesquelles peuvent modifier ou compromettre le résultat. Bien entendu, pour leur bon fonctionnement, ces opérations supposent que le registre d'accueil ou intermédiaire est initialement à zéro. Cette condition est normalement assurée en imposant des conditions au nombre de départ.

Duplication et indicateurs d'états Il est aisé de dupliquer la valeur du registre v2 en la transférant dans v3 et v5 grâce à la fraction 15/2 mais cela détruit la valeur a du registre v2 qui devient seulement un registre de décompte.

Si l'on cherche à conserver la valeur dans v2, il faut utiliser un autre registre de décompte, par exemple le registre v5 en y transférant la valeur (multiplication par 5/2) puis transférer la valeur de v5 dans v2 et v3 (multiplication par 6/5). Cette notion d'actions successives nécessite d'indiquer au programme quelle étape il doit effectuer (transfert de v2 dans v5 ou transfert de v5 dans v2 et v3). Dans le cas présent il ne faut pas qu'il retourne à la première étape quand celle-ci est terminée. Cela nécessite deux autres registres v7 et v11 dits indicateurs d'états. v7, mis à 1 en début de programme, sert à indiquer que l'on est dans l'étape 1. Pour effectuer le transfert de v2 vers 5, en multipliant par 5/14, on serait sûr que le transfert ne s'effectue que si v7 est à 1 mais ce test est destructif car v7 passe à zéro. Il est donc nécessaire d'utiliser un registre supplémentaire v11 pour signifier que l'on est encore dans l'étape 1. On multiplie alors, non pas par 5/2, ni 5/14 mais par 55/14. Le registre d'états v7 passe à 0 mais le registre d'états v11 passe à 1. Au passage suivant, le programme ne peut plus effectuer l'action 55/14, mais si la seconde fraction du programme est 7/11, celle-ci remet le registre v7 à 1 et v11 à 0 et on peut continuer le transfert jusqu'à ce que le registre v2 soit vide. A ce stade là, v2 et v3 sont vides, v5 est plein, v7 est à 1 et v11 à 0. Le programme ne peut effectuer ni 55/14, ni 7/11, il est temps de passer à l'étape 2 en mettant définitivement v7 à 0 par la fraction 1/7. le programme se termine par la fraction 6/5, qui, ne touchant ni à v7 ni à v11 empêche l'application des trois premières fractions Le programme transforme alors en

Pour la multiplication décrite ci-après, par exemple, le transfert du produit des registres des 2 et des 3 vers celui des 5 nécessite l'utilisation d'un registre supplémentaire (assigné à celui des 7), pour réaliser des duplications, et deux autres (ceux des 11 et 13), pour réaliser un indicateur d'états.

Multiplication

On peut créer une fonction multiplicative à l'aide d'une boucle additive. L'idée centrale d'un tel algorithme est que partant d'un résultat nul, calculer consiste à additionner fois la valeur au résultat. Cet algorithme s'applique à un nombre et le transforme en .

En termes de registres, on peut coder par v2, par v3 et le résultat par v5. Fondamentalement, l'opération consiste à additionner v3 à v5, et répéter l'opération v2 fois.

Restauration du registre v3 Comme l'addition de v3 à v5 « consomme » la valeur de v3, il nous faudra également un registre v7 pour mémoriser cette valeur et la restaurer d'une addition à l'autre. En notant les fractions qui réalisent les différents transferts, l'algorithme devient : [ 1/2 ] Si v2 n'est pas nul, le décrémenter (pour compter un passage) ; [ 5x7 / 3 ] Une boucle intérieure ajoute v3 à v5, et en parallèle, recopie aussi v3 en v7 (ce faisant, en mettant v3 à zéro) ; [ 3/7 ] Quand la boucle intérieure est terminée (v3 est nul), on remet v3 la valeur initiale (présente dans v7, ce faisant, mettant v7 à zéro). Puis on recommence (tant que v2 n'est pas nul).

États successifs de l'algorithme Cependant, on voit que la règle disant que les fractions sont examinées dans un ordre fixe ne permet pas simplement de recopier d'un côté v3 en v7, puis v7 en v3. La présence simultanée d'un facteur [7/3] et d'un facteur [3/7] dans la liste risque d'engendrer le même type de boucle infinie que précédemment. Suivant la fraction rencontrée en premier, l'une des opérations sera prioritaire sur l'autre, et l'un ou l'autre de ces deux registre ne pourra pas être vidé jusqu'au bout.

Pour retenir l'une des actions tant que l'autre n'est pas terminée, il faut introduire la notion d'état dans l'algorithme, et distinguer entre un état A, où v7 est transféré vers v3 pour le réinitialiser, et un état B, où l'addition de v3 à v5 est réalisée tout en transférant v3 à v7. L'algorithme devient :

État courant Étape Condition Action élémentaire Opérateur État suivant A Réinitialisation : transférer v7 à v3 v7 > 0 Soustraire1 à v7ajouter 1 à v3 3/7 A Si v2 positif, décrémentet entrée en boucle B v7 = 0 etv2 > 0 Soustraire 1 à v2 1/2 B Vider v3 v7 = 0 etv2 = 0 etv3 > 0 Soustraire 1 à v3 1/3 A Fin du programme v7 = 0 etv2 = 0 etv3 = 0 Stop(résultat dans v5) ∅ ∅ B Ajouter v3 à v5 et v7 v3 > 0 Soustraire 1 à v3Ajouter 1 à v5Ajouter 1 à v7 5*7 / 3 B Réinitialiser v3 = 0 Rien A

Indicateurs d'état couplés Pour gérer ces états, de nouvelles variables sont nécessaires : elles jouent le rôle d'indicateurs d'états. L'idée de base est que si la valeur d'un indicateur (v11) est nulle dans l'état courant, toute fraction présentant cet indicateur au dénominateur ne pourra pas être exécutée. Cependant, deux indicateurs d'états sont nécessaires pour la seule boucle B : en effet, le premier indicateur (v11) étant consommé par le test, il est nécessaire d'en posséder un second (v13) pour indiquer au programme qu'il doit continuer dans le même état.

Les indicateurs d'état pour la boucle B seront donc v11 et v13. Tant que ces deux indicateurs sont nuls, les fractions de la boucle B (qui doivent donc être examinées en premier) ne peuvent pas s'exécuter, et les fractions de l'état A (qui sont examinées ensuite) sont exécutées. [ 3/7 ] v7 est retranscrit dans v3.