Il modo più veloce per ottenere numeri interi 10 e numero intero 10?


10

Se un hardware non supporta le operazioni di modulo o divisione, sono necessari molti più cicli della CPU per simulare il modulo / divisione tramite software. Esiste un modo più veloce per calcolare la divisione e il modulo se l'operando è 10?

Nel mio progetto ho spesso bisogno di calcolare il modulo intero 10. In particolare, sto lavorando su PIC16F e devo mostrare un numero su un LCD. Ci sono 4 cifre da supportare, quindi ci sono 4 chiamate al modulo e alla funzione di divisione (implementazione del software). Cioè, come il seguente:

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();

Ci sono altre aree che usano un codice simile.


Perché alcune dozzine di chiamate / sec sono un problema? Non mi preoccuperei se il progetto non fosse completamente funzionale e privo di bug.
Nick T

Ho notato che se visualizzo continuamente un numero nel circuito principale occupato, la risposta del pulsante diventa lenta. Cioè, per rilevare che un pulsante è stato premuto, devo premere quel pulsante un po 'più a lungo. Ciò accade quando l'orologio di sistema esegue 32768 Hz.
Donotalo,

Stai usando gli interrupt? Perché stai usando un xtal a 32kHz; di solito è possibile ottenere prestazioni di potenza inferiori se si opera più velocemente e si va a dormire quando è inattivo.
Nick T

sto usando gli interrupt. ma solo per aggiornare il display non vale la pena passare all'oscillazione ad alta velocità. potere-saggio. per il mio progetto. deve funzionare a bassa velocità per quasi il 90% della sua durata.
Donotalo,

2
Come nota generale, il libro Hacker's Delight di Henry S. Warren, Jr. è la fonte di ingannevoli inganno. Ho cercato suggerimenti di divisione e non ha nulla da dividere per 10 che è superiore a nessuna delle risposte seguenti.
RBerteig,

Risposte:


11

Ecco un algoritmo binario a BCD che ho usato diversi anni fa basato su uno trovato qui . Stavo usando un BCD esterno a 7 seg driver di visualizzazione in modo che il risultato potesse essere scritto direttamente sulle porte appropriate come BCD compresso per l'output.

Questo è abbastanza veloce se hai un moltiplicatore hardware nel PIC, stavo usando un PIC18F97J60. Se non hai un moltiplicatore hardware sul tuo PIC, considera l'utilizzo di shift + add per la moltiplicazione.

Questo accetta un int senza segno a 16 bit e restituisce un BCD compresso con 5 cifre, potrebbe essere modificato e reso più veloce per 4 cifre. Usa shift + aggiunte alla divisione approssimativa per 10 ma dato l'intervallo di input limitato è esatto per questo uso. Potresti voler impacchettare il risultato in modo diverso e allinearti a come utilizzi il risultato.

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);
}

ottimo collegamento, grazie! non solo ottimizza la velocità, ma riduce anche la dimensione del codice. Ho implementato "Binario da 12 bit a 4 cifre decimali ASCII" dal tuo link perché ciò non comporta alcuna moltiplicazione.
Donotalo,

8

Supponendo numeri interi senza segno, divisione e moltiplicazione possono essere formati da spostamenti di bit. E dalla divisione (numero intero) e dalla moltiplicazione, si può derivare modulo.

Per moltiplicare per 10:

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

Dividere per 10 è più difficile. Conosco diversi algoritmi di divisione. Se ricordo bene, c'è un modo per dividere rapidamente per 10 usando i bit shift e la sottrazione, ma non ricordo il metodo esatto. Se questo non è vero, allora questo è un algoritmo di divisione che gestisce <130 cicli . Non sono sicuro di quale micro stai usando, ma puoi usarlo in qualche modo, anche se devi portarlo.

EDIT: qualcuno dice su Stack Overflow , se puoi tollerare un po 'di errore e avere un registro temporaneo di grandi dimensioni, funzionerà:

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

Supponendo di avere divisione e moltiplicazione, modulo è semplice:

mod = x - ((x / z) * z)

6

Puoi convertire da binario a BCD compresso senza alcuna divisione usando l' algoritmo double dabble . Usa solo shift e aggiungi 3 .

Ad esempio, converti 243 10 = 11110011 2 in binario

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

Questo algoritmo è molto efficiente quando non è disponibile un divisore hardware. Più viene utilizzato solo il cambio sinistro di 1, quindi è veloce anche quando non è disponibile un cambio a barilotto


4

A seconda della quantità di cifre necessarie, potresti essere in grado di utilizzare il metodo della forza bruta ( d- numero di input, t- stringa ASCII di output):

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;

Puoi anche cambiare i molteplici if in un ciclo, con potenze di dieci ottenute per moltiplicazione o una tabella di ricerca.


2

Questa nota applicativa descrive algoritmi per l'aritmetica BCD, inclusa la conversione da binario a BCD e viceversa. L'appnote è di Atmel, che è AVR, ma gli algoritmi descritti sono indipendenti dal processore.


1

Non ho una buona risposta, ma c'è una bella discussione sul nostro sito gemello Stack Overflow sullo stesso identico argomento di divisione e ottimizzazione del modulo.

Hai memoria sufficiente per implementare una tabella di ricerca?

Hackers Delight ha un documento sugli algoritmi di divisione ottimali.


no, non ho abbastanza memoria. Voglio farlo usando addizione, sottrazione e bit shift.
Donotalo,

1

Hai mai considerato di mantenere quel valore come BCD per tutto il tempo (usando semplicemente speciali subroutine "Incremento BCD" e "BCD aggiungere"), piuttosto che mantenere quel valore in forma binaria e convertirlo in BCD secondo necessità (usando un "convertitore più difficile da capire" dalla "subroutine binaria a BCD"?

Un tempo, tutti i computer memorizzavano tutti i dati come cifre decimali (ingranaggi a dieci posizioni, valvole a vuoto a due codici su cinque, BCD, ecc.) E quell'eredità persiste ancora oggi. (vedere Perché i chip di clock in tempo reale usano BCD ).


Il numero da visualizzare sul display LCD è una variabile, compresa tra -1999 e 1999. Indica una temperatura e viene calcolato in formato binario.
Donotalo,

1

L' elenco PIC è una risorsa straordinaria per la gente di programmazione processori PIC.

Conversione BCD

Hai preso in considerazione l'utilizzo di una subroutine binaria-BCD collaudata e ottimizzata appositamente ottimizzata per PIC16F?

In particolare, le persone presenti nell'elenco PICList hanno trascorso molto tempo a ottimizzare le conversioni da binario a BCD su un PIC16F. Queste routine (ognuna ottimizzata a mano per una dimensione specifica) sono riassunte in "Metodi matematici di conversione di Radix per microcontoller PIC" http://www.piclist.com/techref/microchip/math/radix/index.htm

divisione intera e mod

Su una CPU come PIC16F, una subroutine specializzata per dividere per una costante è spesso molto più veloce di una routine "divide variabile A per variabile B" per scopi generici. Potresti voler inserire la tua costante (in questo caso, "0.1") in "Generazione di codice per la moltiplicazione / divisione costante" http://www.piclist.com/techref/piclist/codegen/constdivmul.htm o controllare il routine in scatola vicino a http://www.piclist.com/techref/microchip/math/basic.htm .


1

Dato un moltiplicazione hardware 8x8, è possibile calcolare un divmod-10 di un numero di dimensioni arbitrarie utilizzando una routine che lo calcola per un numero di 12 bit nell'intervallo 0-2559 tramite la procedura:

  1. Assumi il numero originale in OrigH: OrigL
  2. Dividi il numero originale per due e memorizzalo in TempH: TempL
  3. Aggiungi il MSB di TempL * 51 al LSB di TempH * 51. Questo è il quoziente approssimativo
  4. Moltiplicare il quoziente approssimativo per 10, scartando l'MSB del valore.
  5. Sottrai l'LSB di quel risultato dall'LSB del numero originale.
  6. Se quel valore è 10 o maggiore (massimo sarà 19), sottrarre 10 e aggiungere 1 al quoziente approssimativo

Suggerirei di scrivere una routine divmod che l'MSB del numero sarà in W, e l'LSB indicato da FSR; la routine dovrebbe memorizzare il quoziente in FSR con post-decremento e lasciare il resto in W. Per dividere un 32 bit lungo per 10, si userebbe quindi qualcosa del tipo:

  movlw 0
  lfsr 0, _number + 3; Indica MSB
  chiama _divmod10_step
  chiama _divmod10_step
  chiama _divmod10_step
  chiama _divmod10_step

Un passaggio divmod-6 sarebbe molto simile, ad eccezione dell'uso delle costanti 85 e 6 anziché 51 e 10. In entrambi i casi, mi aspetto che divmod10_step sarebbe 20 cicli (più quattro per la chiamata / ritorno), quindi un breve divmod10 sarebbe essere circa 50 cicli e un divmod lungo 10 sarebbe circa 100 (se un caso speciale il primo passo, si potrebbero salvare alcuni cicli).


1

questo potrebbe non essere il più veloce ma è un modo semplice.

 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;
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.