Maneira mais rápida de obter mod inteiro 10 e dividir inteiro 10?


10

Se um hardware não suporta operações de módulo ou divisão, são necessários muito mais ciclos de CPU para simular módulo / divisão por software. Existe alguma maneira mais rápida de calcular a divisão e o módulo se o operando for 10?

No meu projeto, freqüentemente preciso calcular o módulo inteiro 10. Em particular, estou trabalhando no PIC16F e preciso mostrar um número em um LCD. Existem 4 dígitos para suportar, portanto, existem 4 chamadas para a função de módulo e divisão (implementação de software). Ou seja, como o seguinte:

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

Existem outras áreas que usam código semelhante.


Por que algumas dezenas de chamadas / s são um problema? Eu não me incomodaria a menos que o projeto seja totalmente funcional e livre de erros.
Nick T

Percebi que, se eu exibir continuamente algum número no loop ocupado principal, a resposta do botão se tornará lenta. Ou seja, para detectar que um botão foi pressionado, eu tenho que pressionar esse botão um pouco mais. Isso acontece quando o relógio do sistema está executando 32768 Hz.
Donotalo

Você está usando interrupções? Por que você está usando um xtal de 32kHz; Normalmente, você pode obter um desempenho mais baixo se operar mais rápido e dormir quando estiver ocioso.
Nick T

estou usando interrupções. mas apenas para atualizar a exibição, não vale a pena mudar para a oscilação de alta velocidade. em termos de poder. para o meu projeto. tem que ser executado relógio de baixa velocidade quase 90% do seu tempo de vida.
Donotalo

2
Como nota geral, o livro Hacker's Delight, de Henry S. Warren, Jr., é a fonte de truques inteligentes. Procurei sugestões de divisão e não há nada para dividir por 10 que seja superior a qualquer uma das respostas abaixo.
RBerteig 06/04

Respostas:


11

Heres um algoritmo binário para BCD que usei há vários anos, com base em um encontrado aqui . Eu estava usando um driver de vídeo BCD externo para 7 seg para que o resultado pudesse ser gravado nas portas apropriadas diretamente como BCD compactado para saída.

Isso é bastante rápido se você tiver um multiplicador de hardware no PIC, eu estava usando um PIC18F97J60. Se você não possui um multiplicador de hardware no seu PIC, considere usar shift + add para a multiplicação.

Isso leva um int de 16 bits não assinado e retorna um BCD empacotado com 5 dígitos, podendo ser modificado e acelerado para 4 dígitos. Ele usa adições shift + para aproximar a divisão por 10, mas dado o intervalo de entrada limitado, é exato para esse uso. Convém empacotar o resultado de maneira diferente e alinhar-se com a maneira como você está usando o resultado.

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

ótimo link, obrigado! ele não apenas otimiza a velocidade, mas também diminui o tamanho do código. Eu implementei "binário de 12 bits para 4 dígitos decimais ASCII" no seu link, porque isso não envolve nenhuma multiplicação.
Donotalo 6/04/11

8

Assumindo números inteiros não assinados, a divisão e a multiplicação podem ser formadas a partir de mudanças de bits. E da divisão (multiplicação) e multiplicação, o módulo pode ser derivado.

Para multiplicar por 10:

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

Dividir por 10 é mais difícil. Eu sei de vários algoritmos de divisão. Se bem me lembro, existe uma maneira de dividir por 10 rapidamente usando deslocamento de bits e subtração, mas não consigo lembrar o método exato. Se isso não for verdade, então esse é um algoritmo de divisão que gerencia <130 ciclos . Não tenho certeza de qual micro você está usando, mas você pode usá-lo de alguma forma, mesmo que seja necessário portá-lo.

Edição: Alguém diz no Stack Overflow , se você pode tolerar um pouco de erro e tem um grande registro temporário, isso funcionará:

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

Supondo que você tenha divisão e multiplicação, o módulo é simples:

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

6

Você pode converter de BCD para BCD empacotado sem nenhuma divisão usando o algoritmo de dabble duplo . Ele usa apenas shift e adiciona 3 .

Por exemplo, converta 243 10 = 11110011 2 em binário

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

Esse algoritmo é muito eficiente quando não há divisor de hardware disponível. Mais do que apenas o turno esquerdo de 1 é usado, por isso é rápido mesmo quando um deslocador de barril não está disponível


4

Dependendo da quantidade de dígitos necessários, você poderá usar o método de força bruta ( d- número de entrada, t- sequência ASCII de saída):

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;

Você também pode alterar os múltiplos ifs em um loop, com potências de dez obtidas por multiplicação ou uma tabela de pesquisa.


2

Esta nota de aplicação descreve algoritmos para aritmética do BCD, incluindo a conversão de binário para BCD e vice-versa. A appnote é da Atmel, que é o AVR, mas os algoritmos descritos são independentes do processador.


1

Não tenho uma boa resposta, mas há uma grande discussão em nosso site irmão Stack Overflow sobre o mesmo assunto de otimização de divisão e módulo.

Você tem memória suficiente para implementar uma tabela de pesquisa?

O Hackers Delight tem um artigo sobre algoritmos de divisão ideais.


não, não tem memória suficiente. Eu quero fazer isso usando adição, subtração e deslocamento de bits.
Donotalo

1

Você já pensou em manter esse valor como BCD o tempo todo (usando sub-rotinas especiais simples de "incremento de BCD" e "BCD add"), em vez de manter esse valor em formato binário e convertê-lo em BCD conforme necessário (usando uma conversão mais difícil de entender) de binário para BCD "sub-rotina)?

Ao mesmo tempo, todos os computadores armazenavam todos os dados como dígitos decimais (marchas de dez posições, tubos de vácuo de código de duas em cinco, BCD etc.), e esse legado ainda permanece hoje. (consulte Por que os chips de relógio em tempo real usam BCD ).


O número a ser exibido no LCD é uma variável, variando de -1999 a 1999. Indica uma temperatura e é calculada em formato binário.
Donotalo

1

O PICList é um recurso incrível para pessoas que programam processadores PIC.

Conversão BCD

Você já pensou em usar uma sub-rotina de binário para BCD já testada e testada, otimizada especificamente para o PIC16F?

Em particular, as pessoas na lista PICL gastaram muito tempo otimizando as conversões de binário em BCD em um PIC16F. Essas rotinas (cada uma otimizada manualmente para um tamanho específico) estão resumidas em "Métodos matemáticos de conversão de microcontolador PIC" http://www.piclist.com/techref/microchip/math/radix/index.htm

divisão inteira e mod

Em uma CPU como o PIC16F, uma sub-rotina especializada para dividir por uma constante geralmente é muito mais rápida do que uma rotina de uso geral "dividir variável A por variável B". Você pode colocar sua constante (nesse caso, "0,1") em "Geração de código para divisão / multiplicação constante" http://www.piclist.com/techref/piclist/codegen/constdivmul.htm ou confira o rotinas enlatadas próximas a http://www.piclist.com/techref/microchip/math/basic.htm .


1

Dada uma multiplicação de hardware 8x8, pode-se calcular um divmod-10 de um número de tamanho arbitrário usando uma rotina que o calcula para um número de 12 bits no intervalo de 0 a 2559, através do procedimento:

  1. Suponha o número original no OrigH: OrigL
  2. Divida o número original por dois e armazene-o em TempH: TempL
  3. Adicione o MSB de TempL * 51 ao LSB de TempH * 51. Esse é o quociente aproximado
  4. Multiplique o quociente aproximado por 10, descartando o MSB do valor.
  5. Subtraia o LSB desse resultado do LSB do número original.
  6. Se esse valor for 10 ou superior (o máximo será 19), subtraia 10 e adicione 1 ao quociente aproximado

Eu sugeriria escrever uma rotina divmod em que o MSB do número estará em W e o LSB apontado pelo FSR; a rotina deve armazenar o quociente no FSR com pós-decréscimo e deixar o restante em W. Para dividir um comprimento de 32 bits por 10, usaria-se algo como:

  movlw 0
  lfsr 0, _número + 3; Aponte para MSB
  chamar _divmod10_step
  chamar _divmod10_step
  chamar _divmod10_step
  chamar _divmod10_step

Um passo divmod-6 seria muito semelhante, exceto usando constantes de 85 e 6 em vez de 51 e 10. Em ambos os casos, eu esperaria que o divmod10_step fosse 20 ciclos (mais quatro para a chamada / retorno), portanto um divmod10 curto seria cerca de 50 ciclos e um divmod10 longo seria de cerca de 100 (se um caso especial for o primeiro passo, poderá-se economizar alguns ciclos).


1

isso pode não ser o mais rápido, mas é uma maneira simples.

 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;
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.