Neste artigo vamos entender como calcular x^y (x elevado a y) no MIPS usando o MARS simulator. Pra fazer isso precisaremos usar recursão. Caso ainda não saiba sobre o assunto, recomendo ler os artigos a seguir antes de prosseguir aqui:
Compilando Procedimentos Recursivos e Aninhados no MIPS
Função Potência
Quando fazemos um número x elevado a um número y, basicamente estamos dizendo que o número x deve ser multiplicado por ele mesmo uma quantidade de vezes em y. Exemplo: 2^5 = 2 * 2 * 2 * 2 * 2 = 32. Por isso a função em C é recursiva. O trecho de código a seguir apresenta a função potência em linguagem C. Esta será a função que vamos implementar no MIPS.
main(){
x = 2
y = 10
potencia(x, y)
}
int potencia(int x, int y) {
if (y != 0)
return (x * potencia(x, y - 1));
else
return 1;
}
Essa função solicita 2 inteiros e depois verifica se y é diferente de zero. Se este não é o caso, então executa o que está no ELSE, que é apenas retornar o valor 1 pois qualquer número elevado a zero é 1. Se y é de fato diferente de zero, então podemos fazer a operação. A Figura a seguir decupa como ocorre a execução:
Como podemos ver, é possível abstrair o cálculo de forma que resulta na equação que está no return da função em C. O resultado da primeira multiplicação pode ser usado para calcular o valor de x^y até chegar no y. Por exemplo, se você solicitar o valor de 2^10, a função será chamada 10 vezes recursivamente de forma que: o resultado de 2^1 é armazenado e usado para calcular 2^2; o resultado de 2^2 é armazenado e usado para calcular 2^3 e assim por diante até chegar no 2^10. Deu pra compreender pessoal? Caso vocês não tenham entendido o processo, por favor, deixem ali nos comentários o que não fico claro que responderei.
Implementação do calculo de Potência no MIPS
Agora que já vimos como funciona a função em C, vamos codificá-la em MIPS. Aqui vamos passar os valores de x e y diretamente no código e também vamos usar funções, assim como strings para apresentar o resultado no console. Essas informações vão no segmento de dados identificado como .data no código.
.data
x: .word 2
y: .word 10
Resultado: .word 0
Mensagem: .asciiz "\nO valor de x^y é: "
Usamos o .word para dizer que o número 2, o número 10 e a variável Resultado tem o tamanho de uma palavra, que é 32 bits. A mensagem que será impressa no console deve ser indicada com a diretiva .asciiz. Aqui podemos usar \n e \t normalmente para pular linha e dar espaço entre textos.
Agora que finalizamos o segmento de dados, vamos começar o segmento de texto, que é o espaço reservado para o código MIPS propriamente dito. Como vamos usar funções, então vamos usar a diretiva .globl pra indicar que as funções ficarão visíveis globalmente. Usaremos o rótulo main para indicar o programa principal e o rótulo potencia para indicar a função potencia.
.text
.globl main
main:
.end main
.globl potencia
potencia:
.end potencia
Devo reforçar que aqui as funções no MARS devem ser implementadas depois do programa MAIN. Vamos começar implementando a função principal ok. Primeiro vamos carregar os valores de x e y:
lw $a0, x # carrega o valor de x em a0
lw $a1, y # carrega o valor de y em a1
Usamos a instrução LW pra carregar 2 e 10 nos registradores de argumentos da função, já que eles são os dois parâmetros x e y. Portanto, x será carregado em $a0 que é o primeiro argumento da função, e y será carregado em $a1 que é o segundo argumento da função. Agora que já temos os argumentos da função, podemos chamá-la:
jal potencia
Depois que a função é chamada, executada e finalizada, é possível obter e imprimir o resultado no console usando uma chamada do sistema. Para obter o resultado usamos a instrução sw que salva o resultado retornado da função que está em $v0 na variável Resultado. Somente assim é que conseguiremos imprimir depois.
sw $v0, Resultado # salva o resultado da função
Usamos o inteiro 4 para indicar que queremos imprimir no console uma string, o qual é passado para $v0 que é o registrador que será usado na chamada do sistema. A instrução li (load immediate) é usada pois estamos carregando um número diretamente (imediatamente) no registrador. Em seguida usamos a instrução la para carregar o endereço da string que está armazenada em MENSAGEM no registrador $a0. Por último, chamamos syscall que de fato imprimirá a mensagem na tela.
# chama o sistema para imprimir no console
li $v0, 4 # codigo 4 para imprimir texto
la $a0, Mensagem # passa o endereço da memória de dados onde está armazenada a mensagem
syscall # chama o sistema
Precisamos mandar imprimir o resultado também, para isso usamos outra chamada de sistema. O código 1 é passado como valor para impressão de números inteiros no registrador $v0 e a variável Resultado é carregada no registrador $a0. Agora com os dois parâmetros da chamada do sistema carregados podemos de falto chamar o sistema para imprimir no console:
li $v0, 1
lw $a0, Resultado
syscall
Agora tudo o que precisamos fazer é finalizar corretamente o programa:
# sai do programa
li $v0, 10 # codigo 10 para encerrar
syscall # chama o sistema
A função completa fica da seguinte forma:
# programa principal
main:
lw $a0, x # carrega o valor de x em a0
lw $a1, y # carrega o valor de y em a1
jal potencia # chama a função
sw $v0, result # salva o resultado da função
# imprime o resultado no console
li $v0, 4
la $a0, Mensagem
syscall
li $v0, 1
lw $a0, Resultado
syscall
# sai do programa
li $v0, 10
syscall
.end main
A função principal está codificada. Vamos agora codificar a função potencia. Como é uma função recursiva precisaremos usar o recurso de LOOP para que a função funcione corretamente e por isto damos o nome de potencia ao rotulo da função e também ao rótulo do LOOP. Começamos inicializando y e o contador do loop, assim, o registrador $t0 é o contador e $v0 é nosso y.
add $t0, $zero, $zero # inicializa $t0 = 0
addi $v0,$v0,1 # valor inicial de $v0 = 1
Agora começamos a codificar o loop. Primeiro precisamos verificar se y é igual é zero. Se este for o caso, então o LOOP não ocorre, então devemos sair do LOOP e continuar com a execução do resto do programa, ou apenas sair do programa. Lembram que quando um número x é elevado a zero o resultado é 1. Esta é a parte que decodificamos agora.
potencia_loop:
beq $t0, $a1, exit_L # desvia se $a1 == $t0
exit_L:
jr $ra
A instrução BEQ é responsável por fazer esta comparação. A lógica nos diz que se o segundo argumento da função $a1, que é nosso y, for igual a zero então, vá para o ELSE. Note que no meu código não estou fazendo uma comparação diretamente com zero, mas sim com dez. O que estou fazendo na verdade é comparar o valor do registrador $a1 com o valor do registrador $t0 e verificando se naquele momento $a1==10. Quando $a1==10 o LOOP termina. O que vai acontecer no LOOP nesta comparação é o seguinte:
| Iteração | $a1 | $t0 | Desvia? |
| 1 | 10 | 0 | não |
| 2 | 10 | 1 | não |
| 3 | 10 | 2 | não |
| 4 | 10 | 3 | não |
| 5 | 10 | 4 | não |
| 6 | 10 | 5 | não |
| 7 | 10 | 6 | não |
| 8 | 10 | 7 | não |
| 9 | 10 | 8 | não |
| 10 | 10 | 9 | não |
| 11 | 10 | 10 | sim |
O LOOP começa com $a1 valendo 10 e $t0 valendo zero. Cada vez que entra no LOOP o contador $t0 é incrementado em 1 mas o valor de $a1 nunca muda. Dessa forma, o LOOP executará corretamente a quantidade de vezes que são necessárias para realizar a potência. Note que x é o argumento $a0 por isso o multiplicamos por $v0 que é o parâmetro de retorno da função. Bom, agora temos que codificar a multiplicação de x por ele mesmo e também o incremento do contador:
mul $v0, $v0, $a0 # a0 = 2 -> x = x * x * x * x .....
ddi $t0, $t0, 1 # atualiza o valor de $t0
O corpo do LOOP está pronto só falta agora o retorno para o LOOP enquanto $a1 != $t0
j potencia_loop
j potencia_loop volta para o inicio do loop. O código completo da função fica como a seguir:
.globl potencia
potencia:
add $t0, $zero, $zero # inicializa $t0 = 0
addi $v0,$v0,1 # valor inicial de $v0 = 1
potencia_loop:
beq $t0, $a1, exit_L # desvia quando $a1 == 10
mul $v0, $v0, $a0 # a0 = 2 -> x = x * x * x * x .....
addi $t0, $t0, 1 # incrementa em 1 o valor de $t0
j potencia_loop
exit_L:
jr $ra
.end potencia
O código completo do problema ficará da seguinte forma:
# POTÊNCIA
# Segmentos de Dados
.data
x: .word 2
y: .word 10
Resultado: .word 0
Mensagem: .asciiz "\nO valor de x^y é: "
# Segmento de Texto
.text
.globl main
# programa principal
main:
lw $a0, x # carrega o valor de x em a0
lw $a1, y # carrega o valor de y em a1
jal potencia # chama a função
sw $v0, Resultado # salva o resultado da função
# imprime o resultado no console
li $v0, 4
la $a0, Mensagem
syscall
li $v0, 1
lw $a0, Resultado
syscall
# sai do programa
li $v0, 10
syscall
.end main
# ----------------------------------
# potencia
# argumentos: $a0 = x
# $a1 = y
# retorno: $v0 = x^y
# ----------------------------------
.globl potencia
potencia:
add $t0, $zero, $zero # inicializa $t0 = 0 e $t0 = y
addi $v0,$v0,1 # valor inicial de $v0 = 1
potencia_loop:
beq $t0, $a1, exit_L # a1 = y = 10
mul $v0, $v0, $a0 # a0 = 2 -> x = x * x * x * x .....
addi $t0, $t0, 1 # atualiza o valor de $t0
j potencia_loop
exit_L:
jr $ra
.end potencia
Finalizando
Pessoal, espero que vocês tenham entendido. Caso tenham ficado dúvidas, por favor, deixem nos comentários. Sugiro que vocês tentem implementar outras operações matemáticas recursivas para treinar. Agradeço muito por terem lido este artigo até o final. Lembrem de recomendá-lo para seus amigos tá ok?!!! Até o próximo








