Le moyen le plus rapide pour obtenir un mod entier 10 et une division entière 10?

10

Si un matériel ne prend pas en charge les opérations de module ou de division, il faut beaucoup plus de cycles CPU pour simuler le module / division par logiciel. Existe-t-il un moyen plus rapide de calculer la division et le module si l'opérande vaut 10?

Dans mon projet, j'ai souvent besoin de calculer le module entier 10. En particulier, je travaille sur PIC16F et j'ai besoin d'afficher un nombre sur un écran LCD. Il y a 4 chiffres à prendre en charge, il y a donc 4 appels à la fonction module et division (implémentation logicielle). Autrement dit, comme suit:

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

Il existe d'autres domaines qui utilisent un code similaire.

Donotalo
la source
Pourquoi quelques dizaines d'appels / sec sont-ils un problème? Je ne m'embêterais pas à moins que le projet ne soit entièrement fonctionnel et sans bogue.
Nick T
J'ai remarqué que si j'affiche continuellement un certain nombre dans la boucle occupée principale, la réponse du bouton devient lente. C'est-à-dire, pour détecter qu'un bouton a été enfoncé, je dois appuyer sur ce bouton un peu plus longtemps. Cela se produit lorsque l'horloge système fonctionne à 32 768 Hz.
Donotalo
Utilisez-vous des interruptions? Pourquoi utilisez-vous un xtal 32 kHz; généralement, vous pouvez obtenir des performances de puissance plus faibles si vous travaillez plus rapidement et vous endormez lorsque vous êtes au ralenti.
Nick T
j'utilise des interruptions. mais juste pour mettre à jour l'affichage, il ne vaut pas la peine de passer à une oscillation à haute vitesse. en termes de puissance. pour mon projet. il doit être exécuté horloge à basse vitesse près de 90% de sa durée de vie.
Donotalo
2
Comme note générale, le livre Hacker's Delight de Henry S. Warren, Jr. est la source d'une astuce astucieuse. J'ai cherché des suggestions de division, et il n'y a rien à diviser par 10 qui soit supérieur à l'une des réponses ci-dessous.
RBerteig

Réponses:

11

Voici un algorithme binaire vers BCD que j'ai utilisé il y a plusieurs années basé sur celui trouvé ici . J'utilisais un pilote d'affichage BCD externe à 7 segments afin que le résultat puisse être écrit sur les ports appropriés directement sous forme de BCD compressé pour la sortie.

C'est assez rapide si vous avez un multiplicateur matériel dans le PIC, j'utilisais un PIC18F97J60. Si vous n'avez pas de multiplicateur matériel sur votre PIC, envisagez d'utiliser shift + add pour la multiplication.

Cela prend un entier 16 bits non signé et renvoie un BCD emballé à 5 chiffres, il pourrait être modifié et rendu plus rapide pour 4 chiffres. Il utilise shift + additions pour approximer la division par 10 mais étant donné la plage d'entrée limitée, il est exact pour cette utilisation. Vous voudrez peut-être emballer le résultat différemment ainsi pour aligner la façon dont vous utilisez le résultat.

void intToPackedBCD( uint16_t n, uint8_t *digits ) {

    uint8_t d4, d3, d2, d1, d0, q;  //d4 MSD, d0 LSD

    d1 = (n>>4)  & 0xF;
    d2 = (n>>8)  & 0xF;
    d3 = (n>>12) & 0xF;

    d0 = 6*(d3 + d2 + d1) + (n & 0xF);
    q = (d0 * 0xCD) >> 11;
    d0 = d0 - 10*q;

    d1 = q + 9*d3 + 5*d2 + d1;
    q = (d1 * 0xCD) >> 11;
    d1 = d1 - 10*q;

    d2 = q + 2*d2;
    q = (d2 * 0x1A) >> 8;
    d2 = d2 - 10*q;

    d3 = q + 4*d3;
    d4 = (d3 * 0x1A) >> 8;
    d3 = d3 - 10*d4;

    digits[0] = (d4<<4) | (d3);
    digits[1] = (d2<<4) | (d1);
    digits[2] = (d0<<4);
}
marque
la source
grand lien, merci! il optimise non seulement la vitesse, mais diminue également la taille du code. J'ai implémenté "12 bits binaires à 4 chiffres décimaux ASCII" à partir de votre lien car cela n'implique aucune multiplication.
Donotalo
8

En supposant que des entiers non signés, la division et la multiplication peuvent être formées à partir de décalages de bits. Et à partir de la division et de la multiplication (entières), modulo peut être dérivé.

Pour multiplier par 10:

y = (x << 3) + (x << 1);

Diviser par 10 est plus difficile. Je connais plusieurs algorithmes de division. Si je me souviens bien, il existe un moyen de diviser par 10 rapidement en utilisant les décalages de bits et la soustraction, mais je ne me souviens pas de la méthode exacte. Si ce n'est pas vrai, il s'agit d'un algorithme de division qui gère <130 cycles . Je ne sais pas quel micro vous utilisez, mais vous pouvez l'utiliser d'une certaine manière, même si vous devez le porter.

EDIT: Quelqu'un dit plus à Stack Overflow , si vous pouvez tolérer un peu d'erreur et avoir un grand registre temporaire, cela fonctionnera:

temp = (ms * 205) >> 11;  // 205/2048 is nearly the same as /10

En supposant que vous ayez la division et la multiplication, modulo est simple:

mod = x - ((x / z) * z)
Thomas O
la source
6

Vous pouvez convertir du binaire en BCD compressé sans aucune division en utilisant l' algorithme de double dabble . Il utilise uniquement shift et add 3 .

Par exemple, convertir 243 10 = 11110011 2 en binaire

0000 0000 0000   11110011   Initialization
0000 0000 0001   11100110   Shift
0000 0000 0011   11001100   Shift
0000 0000 0111   10011000   Shift
0000 0000 1010   10011000   Add 3 to ONES, since it was 7
0000 0001 0101   00110000   Shift
0000 0001 1000   00110000   Add 3 to ONES, since it was 5
0000 0011 0000   01100000   Shift
0000 0110 0000   11000000   Shift
0000 1001 0000   11000000   Add 3 to TENS, since it was 6
0001 0010 0001   10000000   Shift
0010 0100 0011   00000000   Shift
   2    4    3
       BCD

Cet algorithme est très efficace lorsqu'il n'y a pas de diviseur matériel disponible. De plus, seul le décalage à gauche de 1 est utilisé, donc c'est rapide même quand un levier de vitesses n'est pas disponible

phuclv
la source
4

Selon la quantité de chiffres dont vous avez besoin, vous pourrez peut-être utiliser la méthode de la force brute ( d- numéro d'entrée, t- chaîne ASCII de sortie):

t--;
if (d >= 1000) t++; *t = '0'; while (d >= 1000) { d -= 1000; *t += 1; }
if (d >= 100) t++; *t = '0'; while (d >= 100) { d -= 100; *t += 1;}
if (d >= 10) t++; *t = '0'; while (d >= 10) { d -= 10; *t += 1;}
t++; *t = '0' + d;

Vous pouvez également changer les multiples ifs en une boucle, avec des puissances de dix obtenues par multiplication ou une table de recherche.

jpc
la source
2

Cette note d'application décrit les algorithmes pour l'arithmétique BCD, y compris la conversion du binaire en BCD et vice versa. L'Appnote est d'Atmel, qui est AVR, mais les algorithmes décrits sont indépendants du processeur.

stevenvh
la source
1

Je n'ai pas de bonne réponse, mais il y a une grande discussion sur notre site soeur Stack Overflow sur le même sujet exact de la division et de l'optimisation modulo.

Avez-vous suffisamment de mémoire pour implémenter une table de recherche?

Hackers Delight a un article sur les algorithmes de division optimaux.

Adam Lawrence
la source
non, je n'ai pas assez de mémoire. Je veux le faire en utilisant l'addition, la soustraction et le décalage de bits.
Donotalo
1

Avez-vous envisagé de conserver cette valeur comme BCD tout le temps (en utilisant de simples sous-programmes spéciaux "BCD increment" et "BCD add"), plutôt que de conserver cette valeur sous forme binaire et de la convertir en BCD si nécessaire (en utilisant une conversion plus difficile à comprendre) du binaire au sous-programme BCD)?

À un moment donné, tous les ordinateurs stockaient toutes les données sous forme de chiffres décimaux (engrenages à dix positions, tubes à vide à code sur deux, BCD, etc.), et cet héritage persiste encore aujourd'hui. (voir Pourquoi les puces d'horloge en temps réel utilisent BCD ).

davidcary
la source
Le nombre à afficher sur l'écran LCD est une variable, allant de -1999 à 1999. Il indique une température et est calculé au format binaire.
Donotalo
1

La PICList est une ressource incroyable pour les personnes qui programment des processeurs PIC.

Conversion BCD

Avez-vous envisagé d'utiliser une sous-routine binaire vers BCD éprouvée et prête à l'emploi spécialement optimisée pour le PIC16F?

En particulier, les utilisateurs de la PICList ont passé beaucoup de temps à optimiser les conversions binaires en BCD sur un PIC16F. Ces routines (chacune optimisée à la main pour une taille spécifique) sont résumées dans "PIC Microcontoller Radix Conversion Math Methods" http://www.piclist.com/techref/microchip/math/radix/index.htm

division entière et mod

Sur un processeur comme le PIC16F, un sous-programme spécialisé pour diviser par une constante est souvent beaucoup plus rapide qu'une routine à usage général "diviser la variable A par la variable B". Vous voudrez peut-être mettre votre constante (dans ce cas, "0,1") dans la "Génération de code pour la multiplication / division constante" http://www.piclist.com/techref/piclist/codegen/constdivmul.htm ou consultez le routines en conserve près de http://www.piclist.com/techref/microchip/math/basic.htm .

davidcary
la source
1

Étant donné une multiplication matérielle 8x8, on peut calculer un divmod-10 d'un nombre de taille arbitraire en utilisant une routine qui le calcule pour un nombre de 12 bits dans la plage 0-2559 via la procédure:

  1. Supposons le numéro d'origine dans OrigH: OrigL
  2. Divisez le nombre d'origine par deux et stockez-le dans TempH: TempL
  3. Ajoutez le MSB de TempL * 51 au LSB de TempH * 51. C'est le quotient approximatif
  4. Multipliez le quotient approximatif par 10, en ignorant le MSB de la valeur.
  5. Soustrayez le LSB de ce résultat du LSB du nombre d'origine.
  6. Si cette valeur est de 10 ou plus (max sera 19), soustrayez 10 et ajoutez 1 au quotient approximatif

Je suggère d'écrire une routine divmod dont le MSB du nombre sera en W et le LSB pointé par FSR; la routine doit stocker le quotient dans FSR avec post-décrémentation et laisser le reste dans W. Pour diviser un 32 bits par 10, on utiliserait alors quelque chose comme:

  movlw 0
  lfsr 0, _number + 3; Pointez sur MSB
  appeler _divmod10_step
  appeler _divmod10_step
  appeler _divmod10_step
  appeler _divmod10_step

Une étape divmod-6 serait très similaire, sauf en utilisant des constantes de 85 et 6 plutôt que de 51 et 10. Dans les deux cas, je m'attendrais à ce que divmod10_step soit de 20 cycles (plus quatre pour l'appel / retour), donc un divmod10 court serait être d'environ 50 cycles et un long divmod10 serait d'environ 100 (si l'on cas particulier la première étape, on pourrait économiser quelques cycles).

supercat
la source
1

ce n'est peut-être pas le plus rapide mais c'est un moyen simple.

 a = 65535;

    l = 0;
    m = 0;
    n = 0;
    o = 0;
    p = 0;

    while (a >= 10000)
    {   a -= 10000;
        l += 1;
    }
     while (a >= 1000)
    {   a -= 1000;
        m += 1;
    }
     while (a >= 100)
    {   a -= 100;
        n += 1;
    }
     while (a >= 10)
    {   a -= 10;
        o += 1;
    }
     while (a > 0)
    {   a -= 1;
        p += 1;
    }
sergiu
la source