9 votes

Une implémentation astucieuse du modulo faite maison

Je programme un automate programmable avec un logiciel obsolète (RSLogix 500, ne demandez pas) et il ne prend pas en charge nativement une opération de modulo, mais j'en ai besoin. Je n'ai pas accès à : modulo, division entière, variables locales, une opération de troncature (bien que je puisse le contourner avec un arrondi). De plus, toutes les variables disponibles pour moi sont disposées dans des tables triées par type de données. Enfin, cela devrait fonctionner pour les décimales en virgule flottante, par exemple 12345.678 MOD 10000 = 2345.678.

Si nous formulons notre équation :

dividende / diviseur = quotient entier, reste

Il existe deux implémentations évidentes.

Implémentation 1 : Effectuer une division en virgule flottante : dividende / diviseur = quotient décimal. Ensuite, bricoler une opération de troncature pour trouver le quotient entier. Multipliez-le par le diviseur et trouvez la différence entre le dividende et cela, ce qui donne le reste.

Je n'aime pas cela car cela implique un tas de variables de différents types. Je ne peux pas 'passer' de variables à une sous-routine, donc je dois simplement allouer certaines des variables globales situées dans plusieurs tables de variables différentes, et c'est difficile à suivre. Malheureusement, le 'difficile à suivre' compte, car cela doit être assez simple pour qu'un travailleur de maintenance puisse s'en occuper.

Implémentation 2 : Créez une boucle de sorte que tant que dividende > diviseur diviseur = dividende - diviseur. C'est très propre, mais cela enfreint l'une des grandes règles de la programmation d'automates, qui est de ne jamais utiliser de boucles, car si quelqu'un modifie involontairement un compteur d'index, vous pourriez vous retrouver dans une boucle infinie et la machine serait en folie ou se bloquerait de manière irrécupérable. De plus, les boucles sont difficiles à dépanner pour la maintenance. De plus, je n'ai même pas d'instructions de bouclage, je dois utiliser des labels et des sauts. Beurk.

Je me demande donc si quelqu'un a des astuces mathématiques intelligentes ou des implémentations plus intelligentes du modulo que celles-ci. J'ai accès à + - * /, aux exposants, à la racine carrée, aux fonctions trigonométriques, au logarithme, à la valeur absolue, et et/OR/NOT/XOR.

6voto

Keith Randall Points 17518

De combien de bits traitez-vous? Vous pourriez faire quelque chose comme:

if dividend > 32 * divisor  dividend -= 32 * divisor
if dividend > 16 * divisor  dividend -= 16 * divisor
if dividend > 8 * divisor  dividend -= 8 * divisor
if dividend > 4 * divisor  dividend -= 4 * divisor
if dividend > 2 * divisor  dividend -= 2 * divisor
if dividend > 1 * divisor  dividend -= 1 * divisor
quotient = dividend

Déroulez simplement autant de fois qu'il y a de bits dans dividende. Assurez-vous d'être prudent en ce qui concerne les débordements des multiplications. C'est exactement comme votre #2 sauf que cela prend le log(n) au lieu de n itérations, donc il est faisable de tout dérouler.

5voto

Joni Points 46728

Si vous ne vous souciez pas de compliquer les choses et de gaspiller du temps d'ordinateur, vous pouvez calculer le modulo avec des fonctions trigonométriques périodiques :

atan(tan((12345.678 -5000)*pi/10000))*10000/pi+5000   =   2345.678

Plus sérieusement, soustraire 10000 une fois ou deux fois (votre "implémentation 2") est mieux. Les algorithmes habituels pour calculer le modulo sur des nombres à virgule flottante nécessitent un certain nombre de manipulations au niveau des bits qui sont probablement irréalisables pour vous. Voir par exemple http://www.netlib.org/fdlibm/e_fmod.c (L'algorithme est simple mais le code est complexe en raison de cas spéciaux et parce qu'il est écrit pour des nombres en double précision de l'IEEE 754 en supposant qu'il n'y a pas de type entier 64 bits)

3voto

J... Points 8994

Tout cela semble complètement surcompliqué. Vous avez un index d'encodeur qui bascule à 10000 et des objets qui roulent le long de la ligne dont vous suivez les positions à un instant donné. Si vous devez projeter des points d'arrêt ou des points d'action le long de la ligne, ajoutez simplement autant de pouces que nécessaire et soustrayez immédiatement 10000 si le résultat cible est supérieur à 10000.

Alternativement, ou en outre, vous obtenez toujours une nouvelle valeur d'encodeur à chaque scan PLC. Dans le cas où la différence entre la valeur actuelle et la dernière valeur est négative, vous pouvez activer un contact de travail pour signaler l'événement de rebouclage et apporter les corrections appropriées pour tout calcul sur ce scan. (**ou incrémentez un compteur secondaire comme ci-dessous)

Sans en savoir plus sur le problème réel, il est difficile de suggérer une solution plus spécifique mais il y a certainement de meilleures solutions. Je ne vois aucune nécessité de MOD ici. De plus, les gars sur le terrain vous remercieront de ne pas remplir la machine avec des trucs de sorcier obfusqués.

Je cite :

Enfin, cela doit fonctionner pour les décimales en virgule flottante, par exemple 12345,678 MOD 10000 = 2345,678

Il existe une fonction brillante pour cela - c'est une soustraction. Pourquoi cela doit-il être plus compliqué que ça? Si votre ligne de convoyage est en fait plus longue que 833 pieds, faites alors un second compteur qui s'incrémente à chaque rebouclage de l'index principal jusqu'à ce que vous ayez assez de distance pour couvrir la distance requise.

Par exemple, si vous avez besoin de 100000 pouces de mémoire de convoyeur, vous pouvez avoir un compteur secondaire qui bascule à 10. Les rebouclages de l'encodeur principal peuvent être facilement détectés comme ci-dessus et vous incrémentez le compteur secondaire à chaque fois. Votre position d'encodeur de travail est donc 10000 fois la valeur du compteur plus la valeur actuelle de l'encodeur. Travaillez uniquement avec les unités étendues et faites basculer le compteur secondaire à la valeur requise pour ne perdre aucune pièce. Le problème, ensuite, se réduit à une simple soustraction (comme ci-dessus).

J'utilise cette technique avec un support de pièce rotatif à engrenage planétaire, par exemple. J'ai un encodeur qui bascule une fois par rotation principale tandis que les pièces satellites à engrenage planétaire (qui tournent autour d'un engrenage stator) nécessitent 43 rotations principales pour revenir à une orientation de départ identique. Avec un simple compteur qui s'incrémente (ou se décrémente, en fonction de la direction) au point de rebouclage de l'encodeur principal, cela vous donne une mesure entièrement absolue de l'emplacement des pièces. Dans ce cas, le compteur secondaire bascule à 43.

Cela fonctionnerait de manière identique pour un convoyeur linéaire, la seule différence étant qu'un convoyeur linéaire peut s'étendre sur une distance infinie. Le problème doit alors être limité par le chemin linéaire le plus long pris par la pièce en cas de figure défavorable.

Avec la réserve que je n'ai jamais utilisé RSLogix, voici l'idée générale (j'ai utilisé des symboles génériques et ma syntaxe est probablement un peu incorrecte mais vous devriez comprendre l'idée)

Exemple de RSLogix

Grâce à ce qui précède, vous obtenez une valeur ENC_EXT qui a essentiellement transformé votre encodeur d'un pouce de 10k en un pouce de 100k. Je ne sais pas si votre convoyeur peut fonctionner en sens inverse, si c'est le cas vous devriez également gérer le décompte. Si le reste de votre programme fonctionne uniquement avec la valeur ENC_EXT alors vous n'avez même pas à vous soucier du fait que votre codeur n'atteint que 10k. Il atteint désormais 100k (ou la valeur que vous souhaitez) et le rebouclage peut être géré avec une soustraction au lieu d'un modulo.

Après coup :

Les API sont avant tout des machines à état. Les meilleures solutions pour les programmes API sont généralement celles qui sont en harmonie avec cette idée. Si votre matériel ne permet pas de représenter pleinement l'état de la machine, le programme API doit faire de son mieux pour combler les lacunes de ces informations d'état manquantes avec les informations dont il dispose. La solution ci-dessus le fait - elle prend les 10000 pouces d'informations d'état insuffisantes et les étend pour répondre aux exigences du processus.

L'avantage de cette approche est que vous avez désormais préservé des informations d'état absolues, non seulement pour le convoyeur, mais aussi pour toutes les pièces sur la ligne. Vous pouvez les suivre en avant et en arrière pour le dépannage et le débogage et vous disposez d'un système de coordonnées beaucoup plus simple et clair pour les futures extensions. Avec un calcul de modulo, vous jetez des informations d'état et essayez de résoudre des problèmes individuels de manière fonctionnelle - ce n'est souvent pas la meilleure façon de travailler avec les API. Vous devez un peu oublier ce que vous savez des autres langages de programmation et travailler différemment. Les API sont une bête différente et elles fonctionnent le mieux lorsqu'elles sont traitées comme telles.

3voto

Jason Kennaly Points 572

Vous pouvez utiliser une sous-routine pour faire exactement ce dont vous parlez. Vous pouvez cacher le code complexe pour que les techniciens de maintenance ne le rencontrent jamais. C'est presque certainement le plus facile pour vous et votre équipe de maintenance à comprendre.

Cela fait un moment que je n'ai pas utilisé RSLogix500, donc je pourrais me tromper sur quelques termes, mais vous comprenez le principe.

Définissez un fichier de données pour vos nombres à virgule flottante et vos entiers, et donnez-leur des symboles comme MOD_F et MOD_N. Si vous les rendez assez intimidants, les techniciens de maintenance ne les toucheront pas, et vous n'aurez besoin d'eux que pour passer des paramètres et des espaces de travail pendant vos calculs.

Si vous êtes vraiment préoccupé par le fait qu'ils puissent altérer les tables de données, il existe des moyens de les protéger, mais j'ai oublié ce qu'ils sont sur un SLC/500.

Ensuite, définissez une sous-routine, éloignée numériquement de celles utilisées actuellement, si possible. Appelez-la quelque chose comme MODULUS. Encore une fois, les techniciens de maintenance restent presque toujours à l'écart des SBR s'ils ressemblent à des noms de programmation.

Dans les rails immédiatement avant votre instruction JSR, chargez les variables que vous voulez traiter dans les fichiers de données MOD_N et MOD_F. Commentez ces rails en indiquant qu'ils chargent des données pour MODULUS SBR. Rendez les commentaires clairs pour quiconque ayant des antécédents en programmation.

Appelez votre JSR de manière conditionnelle, seulement lorsque vous en avez besoin. Les techniciens de maintenance ne se soucient pas du dépannage de la logique non exécutée, donc si votre JSR n'est pas active, ils regarderont rarement.

Maintenant, vous avez votre propre petit jardin clos où vous pouvez écrire votre boucle sans que la maintenance s'en mêle. Utilisez uniquement ces fichiers de données, et ne supposez pas que l'état de tout sauf ces fichiers est ce que vous attendez. En d'autres termes, vous ne pouvez pas faire confiance à l'adressage indirect. L'adressage indexé est OK, tant que vous définissez l'index dans votre MODULUS JSR. Ne faites confiance à aucun index entrant. Il est assez facile d'écrire une boucle FOR avec un mot de votre fichier MOD_N, un saut et une étiquette. Votre Implémentation #2 devrait compter moins de dix rails environ. Je envisagerais d'utiliser une instruction d'expression ou quelque chose... celle qui vous permet d'entrer simplement une expression. Vous pourriez avoir besoin d'un 504 ou 505 pour cette instruction. Cela fonctionne bien pour les calculs combinés en virgule flottante/entier. Vérifiez quand même les résultats pour vous assurer que l'arrondi ne vous pose pas problème.

Après avoir terminé, validez votre code, parfaitement si possible. Si ce code provoque un dépassement mathématique et met en défaut le processeur, vous n'entendrez jamais la fin. Exécutez-le sur un simulateur si vous en avez un, avec des valeurs étranges (au cas où ils perturberaient d'une manière ou d'une autre le chargement des entrées de la fonction) et assurez-vous que le PLC ne met pas en défaut.

Si vous faites tout cela, personne ne se rendra jamais compte que vous avez utilisé des techniques de programmation régulières dans le PLC, et tout ira bien. TANT QUE ÇA FONCTIONNE.

0voto

wildplasser Points 17900

Ceci est une boucle basée sur la réponse de @Keith Randall, mais elle maintient également le résultat de la division par soustraction. J'ai gardé les printf pour plus de clarté.

#include <stdio.h>
#include <limits.h>
#define NBIT (CHAR_BIT * sizeof (unsigned int))

unsigned modulo(unsigned dividend, unsigned divisor)
{
unsigned quotient, bit;

printf("%u / %u:", dividend, divisor);

for (bit = NBIT, quotient=0; bit-- && dividend >= divisor; ) {
        if (dividend < (1ul << bit) * divisor) continue;
        dividend -= (1ul << bit) * divisor;
        quotient += (1ul << bit);
        }
printf("%u, %u\n", quotient, dividend);
return dividend; // le reste *est* le modulo
}

int main(void)
{
modulo( 13,5);
modulo( 33,11);
return 0;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X