Entendendo a Aritmética em Ponto Fixo

Aritmética em Ponto Fixo

Em sistemas embarcados muitas vezes nos deparamos com a necessidade de representar quantidades fracionárias, seja para mostrar os valores reais em uma interface homem-máquina (IHM), ou para executar uma malha de controle, entre outras aplicações. No entanto, visto que os microcontroladores de baixo custo não possuem unidade de ponto flutuante (abreviado por FPU, do inglês Float Point Unit), esta tarefa se torna inviável para aplicações que necessitam de alto desempenho. Logo, para tais situações, deve-se utilizar a notação em ponto fixo, que permite representar frações através de variáveis inteiras.

 

Representação

 

Um valor em ponto fixo nada mais é do que um número real escalonado por um fator específico, que permite transformá-lo em um número inteiro. Por exemplo, o valor 5,25 pode ser representado como 5250 em ponto fixo após ser multiplicado por um fator de escala de 1000. Se todos os números do mesmo tipo forem multiplicados pelo mesmo fator de escala, o resultado final de um cálculo também estará escalonado por tal fator, bastando dividi-lo por 1000 para retornar ao valor real. Claro que algumas operações aritméticas requerem uma atenção maior, como veremos posteriormente neste artigo.

 

No exemplo acima o fator de escala é uma potência de 10, o que facilita a compreensão humana. Entretanto, visando eficiência computacional, é mais vantajoso utilizar uma potência de 2. Sendo assim, algumas notações foram criadas para representar os tipos de formatos em ponto fixo.

 

Notação para ponto fixo

 

A notação mais comum para representar um número em ponto fixo é denotada Qm.n, onde ‘m’ indica a quantidade de bits para a parte inteira e ‘n’ a quantidade de bits para a parte fracionária, tendo um bit de sinal, conforme mostra a Figura 1. Ou seja, o formato de ponto fixo reserva certa quantidade de bits de uma variável do tipo inteiro para a parte fracionária e outra para a parte inteira.

 

Representação do formato de ponto fixo.
Figura 1: Representação do formato de ponto fixo.

 

Dessa forma, é possível determinar a faixa de números representáveis pelo formato em ponto fixo e sua resolução, como segue:

 

  • Faixa de valores: 

 

  • Resolução: 

 

Exemplo: o formato Q1.14 requer 1+1+14 = 16 bits, consegue representar valores dentro do intervalo    e possui uma resolução de .

 

O formato em ponto fixo também pode ser utilizado somente para valores unsigned. Neste caso, utiliza-se o prefixo U para diferenciá-lo, como UQm.n. A faixa de valores representáveis por esse tipo de formato e a sua resolução são dadas por:

 

  • Faixa de valores: 

 

  • Resolução: 

 

Exemplo: o formato UQ1.15 necessita de 1+15=16bits, representa valores entre 0 e e possui uma resolução de .

 

Conversão

 

A conversão entre os formatos de ponto fixo e ponto flutuante é bem simples. Contudo, deve-se atentar para escolher um formato de ponto fixo capaz de representar os valores reais desejados. O formato Q1.14, por exemplo, não consegue representar o valor 14,75 pois sua faixa se limita ao intervalo de -2 a 1,999938965, como foi visto. Então, neste caso, o formato Q4.11 poderia ser utilizado, já que sua faixa de valores se estende entre -16 e 15,999511719.

 

Conversão de Float para Q

 

Dado um número ‘x’ em ponto flutuante, a sua conversão para o formato Qm.n (ou UQm.n) resultada em ‘X’, conforme a equação abaixo:

 

Equação (1)

 

A Equação (1) é facilmente implementada em código utilizando-se macros, como pode ser visto a seguir:

 

 

 

Conversão de Q para Float

 

Para converter do formato Qm.n (ou UQm.n) para ponto flutuante, basta fazer o processo inverso:

 

Equação (2)

 

A Equação (2) também pode ser convertida em código através de uma macro, resultando em:

 

 

Conversão de Q para inteiro

 

Também é possível converter o formato de ponto fixo para um formato do tipo inteiro. Para isso, utiliza-se a mesma equação que converte ponto fixo em ponto flutuante, contudo, o resultado é armazenado em uma variável do tipo inteiro. Dessa forma, preserva-se apenas a parte inteira do valor real, descartando as casas decimais. Este tipo de conversão pode ser facilmente implementada utilizando-se o operador right shift de acordo com o número de bits da parte fracionária, como mostrado no código abaixo.

 

 

Embora esta implementação seja simples, ela pode apresentar uma perda de resolução devido ao deslocamento dos bits e, consequentemente, erros de arredondamento. Uma explicação mais detalhada a este respeito pode ser vista mais à frente neste artigo. Por agora,  deixo um exemplo de código mais exato para a conversão de ponto fixo em inteiro.

 

 

Aritmética em ponto fixo

 

Nesta seção são apresentadas as principais operações aritméticas em ponto fixo. Para todos os casos considera-se ‘x’, ‘y’ e ‘z’ valores em ponto flutuante e ‘X’, ‘Y’ e ‘Z’ valores em ponto fixo.

 

Como o foco principal deste artigo é a utilização do ponto fixo em microcontroladores de baixo custo, os códigos apresentados aqui foram feitos considerando-se um MCU de 16 bits.

 

Adição

 

Para somar dois valores em um mesmo formato de ponto fixo não tem segredo, o cálculo é feito normalmente, já que o fator de escala se mantém o mesmo, como pode ser visto abaixo:

 

Sejam,

 

Equação (3)
Equação (4)

 

 

Logo,

 

Equação (5)

 

 

Observa-se claramente que o resultado da soma ‘Z’ equivale à soma dos valores em ponto flutuante x+y, convertidos para o formato Qm.n pelo fator de escala .

 

No entanto, quando os valores estão em formatos de ponto fixo diferentes, é necessário converter um deles para o formato do outro antes da soma. Para converter um número do formato Qm1.n1 para o formato Qm2.n2, basta multiplicá-lo pelo fator de escala novo, neste caso e dividi-lo pelo fator de escala antigo, .

 

Vale ressaltar que na prática isto é feito aplicando-se um right shift, cujo valor é dado pela diferença de bits da parte fracionária dos formatos em questão (veremos isto com mais clareza no exemplo a seguir).

 

Para facilitar a compreensão nada melhor do que exemplo numérico. Vamos supor que temos o número x = 12,75 representado pelo formato Q4.11 e o número y = 1,25 representado pelo formato Q1.14. Sendo assim, os respectivos valores em ponto fixo valem:

 

Equação (6)
Equação (7)

 

Como sabemos, o resultado da soma de x com y é 14,0 e não pode ser representado pelo formato Q1.14, mas pode ser representado pelo formato Q4.11. Então, precisamos converter para este formato, resultando em segundo a Equação (8). A implementação prática, neste caso, é feita utilizando-se um right shift de 3, como explicado anteriormente, ou seja, .

 

Equação (8)

 

Agora que ambos estão no mesmo formato podemos somá-los sem problemas. O resultado é representado por , conforme a equação a seguir:

 

Equação (9)

 

Por fim, convertendo de volta para ponto flutuante, obtemos o valor esperado:

 

Equação (10)

 

Traduzindo para código

 

Apesar da operação de adição ser simples, quando implementamos isso em um microcontrolador, devemos nos atentar para problemas de overflow. Na adição, isto ocorre quando somamos números de mesmo sinal, o que pode resultar em um valor maior ou menor do que a capacidade de armazenamento da variável. Em razão disso, é recomendável utilizar uma variável temporária de capacidade maior para armazenar o resultado da soma e, então, saturar se for necessário. Isto pode ser visto no código a seguir.

 

 

Subtração

 

A subtração segue o mesmo princípio da adição. Uma vez que ambos os valores estejam no mesmo formato de ponto fixo, a operação é realizada normalmente, como a subtração de dois inteiros.

 

Problemas de overflow neste caso são mais raros, visto que isto só pode acontecer se um número negativo for subtraído de um número positivo, ou se um número positivo for subtraído de um número negativo. Então, podemos deixar o código bem mais simples, como segue:

 

 

Multiplicação

 

A operação de multiplicação é mais complexa do que as anteriores. Para começar a explicação, vamos supor dois números no formato Q15, dados pelas equações abaixo:

 

Equação (11)
Equação (12)

 

Se multiplicarmos estes dois valores em ponto fixo, obtemos como resultado, conforme a Equação (13).

 

Equação (13)

 

Observa-se que a multiplicação entre dois números no formato Q15 resulta em um valor no formato Q30. Para exemplificar, vamos supor x = 0,25 e y = 0,5. Assim, os valores em ponto fixo são  e  .

 

Portanto, o resultado da multiplicação é que equivale a Mas, se dividirmos por obtemos 4096, que vale ou seja, voltamos para o formato Q15. Isto pode ser resumido pela Equação (14), onde a divisão é facilmente substituível por uma operação de right shift já que estamos trabalhando com potências de 2.

 

Equação (14)

 

A regra geral quando se multiplica um valor no formato Qm1.n1 por um valor no formato Qm2.n2, é que o resultado do produto será um número no formato Q(m1+m2).(n1+n2). Então, o número de bits necessário para armazenar o resultado do produto é no mínimo (m1+m2+n1+n2+1) para multiplicações com sinal, e um bit a menos para multiplicações em unsigned. Muitos microcontroladores possuem um ou mais acumuladores dedicados para este propósito, geralmente de 32 ou 40 bits.

 

Outro aspecto importante neste tipo de operação é o arredondamento. Ao converter o resultado do produto para outro tipo de formato através da operação right shift, alguns bits são eliminados, podendo resultar num pequeno erro. Este erro fica ainda mais evidente quando a operação é feita com números negativos (representados por complemento de 2), como pode ser visto com mais detalhes aqui. Para evitar isso, é recomendável realizar uma operação de arredondamento antes de efetuar o shift.

 

Uma técnica simples para fazer isso é simplesmente somar (o que equivale a 0,5 em ponto fixo) antes de realizar a operação de right shift por ‘n’.

 

O código abaixo exemplifica a implementação da operação de multiplicação para valores no formato Q15.

 

 

Divisão

 

A divisão em ponto fixo é um pouco mais complicada do que a multiplicação e leva mais ciclos de máquina para ser executada. O seu princípio de funcionamento é exatamente o oposto da multiplicação, isto é, o formato do resultado será a diferença de bits dos formatos dos operandos. De maneira geral, o resultado da divisão entre um número no formato Qm1.n1 e um número no formato Qm2.n2 é Q(m1-m2).(n1-n2).  

 

Contudo, seguindo esta regra fica claro que o formato do resultado pode assumir números negativos. Por exemplo, se dividirmos um número em Q10.5 por um número em Q1.14, o resultado será Q9.-9, ou seja, a parte fracionária terá -9 bits, o que não faz sentido algum.

 

Além disso, outro problema é verificado neste tipo de operação. Vamos supor uma divisão entre 100,5 em Q7.8 e 10,2 também em Q7.8. Abaixo seguem as equações para esta operação.

 

Equação (15)
Equação (16)
Equação (17)

 

Observa-se pelo resultado da divisão (Z) que a parte fracionária foi totalmente truncada, restando apenas a parte inteira, que por sua vez está com um right shift de 8. Para contornar este problema, vamos aplicar um left shift de 8 no dividendo antes de efetuar a divisão. Dessa forma, os shifts vão se cancelar resultando no formato desejado.

 

Para deixar isso mais claro, a Equação (18) mostra esse raciocínio aplicado no exemplo anterior. Nota-se que o resultado de 2522 equivale a que é exatamente o resultado da divisão de 100,5 por 10,2 no formato Q7.8.

 

Equação (18)

 

Agora que ficou claro como funciona a operação de divisão em ponto fixo, vamos transformar isto em código.

 

 

Vale ressaltar que existe uma restrição quando trabalhamos, principalmente, com o formato Q15 onde todos os valores são menores do que 1. Neste caso, quando o divisor é menor do que o dividendo sempre ocorrerá overflow. Por exemplo, se dividirmos 0,75 por 0,7 o resultado será 1,0714, que é maior do que o máximo valor representável em Q15. Por este motivo, é necessário acrescentar esta restrição no código, como segue:

 

 

Resultados

 

Em microcontroladores de 16 bits o formato de ponto fixo com maior resolução é o Q15, pois utiliza todos os bits disponíveis para a parte fracionária. Por este motivo, este formato é muito utilizado em aplicações com microcontroladores desse tipo.

 

Sendo assim, para validar os códigos aqui apresentados, desenvolveu-se uma aplicação bem simples utilizando o formato Q15, onde duas variáveis do tipo float ‘a’ e ‘b’ são convertidas para Q15, resultando em ‘A’ e ‘B’, e posteriormente são aplicadas nas quatro operações básicas. Por fim, os resultados em ponto fixo são convertidos novamente para ponto flutuante, para verificar sua veracidade.

 

A Figura 2 mostra o resultado da aplicação, validando o funcionamento das funções de aritmética em ponto fixo. Vale acrescentar que testes mais exaustivos foram feitos para averiguar possíveis erros nos cálculos, mas em todos os casos as funções se comportaram como esperado.

 

Resultados para o formato Q15. para aritmética em Ponto Fixo
Figura 2 – Resultados para o formato Q15.

 

Com objetivo de avaliar a possibilidade de se utilizar diferentes formatos em ponto fixo, o teste acima foi repetido para o formato Q4.11. A Figura 3 mostra os resultados para esse caso.

 

Resultados para o formato Q4.11 para aritmética de ponto fixo.
Figura 3: Resultados para o formato Q4.11.

 

Conclusão

 

Neste artigo foi apresentada uma breve introdução sobre a representação de números em ponto fixo, bem como as quatro operações aritméticas básicas utilizando este tipo de formato. A grande vantagem de se utilizar ponto fixo é a possibilidade de representar números fracionários através de variáveis inteiras, o que é muito útil para microcontroladores que não possuem unidades de ponto flutuante, ou para aplicações que exigem alto desempenho. Além disso, é possível criar diferentes formatos de ponto fixo de acordo com as suas necessidades, tornando-o muito mais flexível do que a notação em ponto flutuante. Por fim, ressalta-se que existem diversas outras operações matemáticas que podem ser aplicadas em ponto fixo, tais como raiz quadrada, funções trigonométricas, exponenciais, etc, o que pode ser abordado em artigos futuros.

 

Saiba mais

 

Introdução ao i.MX6Q / D (GC2000) Vivante OpenCL Embedded Profile

CORDIC - Introdução

Tutorial de Verilog - Ponto Fixo e Ponto Flutuante em Verilog - Parte I

NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Caio Moraes
Atualmente é mestrando em Engenharia Elétrica com ênfase em Eletrônica de Potência na Universidade Federal de Santa Catarina (UFSC). Possui graduação em Engenharia Elétrica pela Universidade Federal de Mato Grosso do Sul (UFMS) e é formado em Eletrotécnica com enfase em Controle e Processos Industriais, pela Escola Técnica Estadual (ETEC) de Ilha Solteira. Tem experiência e interesse nas áreas de eletrônica de potência, sistemas de controle e sistemas embarcados, mais especificamente nos temas modelagem dinâmica de conversores CC-CC, inversores conectados à rede e controle digital aplicado a conversores estáticos.

5
Deixe um comentário

avatar
 
3 Comment threads
2 Thread replies
2 Followers
 
Most reacted comment
Hottest comment thread
4 Comment authors
Augusto BuboltzCaio MoraesFernandoPedro Ferreira dos Santos Neto Recent comment authors
  Notificações  
recentes antigos mais votados
Notificar
Augusto Buboltz
Visitante
Augusto Buboltz

Olá amigo, agradeço pelo seu texto muito bem descrito.
Você poderia me dar uma referência de livro sobre abordagem mais ampla sobre essa área?
Muito Obrigado.

Fernando
Visitante
Fernando

Caio,

parabéns pelo artigo, bem escrito e claro. Vou indicar como referência nas aulas de Processamento Digital de Sinais II que ministro no IFSC.

Pedro Ferreira dos Santos Neto
Visitante
Pedro Neto

Colega bacana o artigo. Me diga: eu preciso representar na tela e também efetuar uma malha de controle de temperatura com uma casa decimal usando um MCU de 16 bitis sem FPU, como faria a equação? vou usar um sinal 4-20 com um conversor A/D de 10 bitis (1024 pontos) e uma linearização 4=20 mA / -5 à 40 C. Como seria a equação?
Obrigado