Ir para conteúdo
  • Cadastre-se
BrNaka

Conversor de bases

Posts Recomendados

Postado (editado)

Fala, galera.
Boa noite!

Como trabalho da faculdade, fiz este conversor de bases em C. Queria saber a opinião de vocês sobre o código em si, se estou cometendo erros ou práticas ruins ao programar e etc. 
Os vídeos do Fernando ("Programação Moderna em C") me ajudaram muito mesmo! Vlw, Fernando. 

O código está aqui em baixo, mas também vou deixar o arquivo.c em anexo!

Obrigado!
Bruno.

// CALCULADORA CONVERSORA DE BASES NÚMERICAS IMPLEMENTADA EM C
// ALUNO        :       Bruno Nakayabu
// DATA         :       01/05/2018
// COMENTÁRIOS  :       Programa criado como trabalho para a disciplina "Introdução à Arquitetura de Computadores" da UFPR.
//              :       Caso esteja usando Linux, compilar com: 'gcc numBases_conv.c -o numBases_conv -lm'.

#include <stdio.h>
#include <math.h>
#include <stdlib.h>

#define SIZE 255
#define ALG 17

// Esta função converte um número decimal para a base 2, 8 ou 16.
// Recebe como parâmetro um número decimal (num_dec), e a base (base) cujo número será convertido.
void decToBase(int num_dec, int base) {
   char resto[ALG] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}, num_conv[SIZE];
   int indx = 0;

   // Este loop realiza as operações de divisão. Os restos são guardados no vetor 'num_conv'
   while(num_dec > 0) {
       num_conv[indx] = resto[num_dec % base];
       num_dec /= base;
       indx++;
   }
   
   // Exclui o caractere especial '\0' do final da string
   indx -= 1;
    
   // Utilizado para identificar a saída do programa
   switch(base) {
       case 2 : printf("BINÁŔIO: "); break;
       case 8 : printf("OCTAL: "); break;
       case 16 : printf("HEXADECIMAL: "); break;
   }
    
   // Inverte a string contida no vetor num_conv
   for(indx; indx >= 0; indx--) 
       printf("%c", num_conv[indx]);
   printf("\n");

}

// Esta função permite converter um número na base 2, 8 ou 16 para a base decimal. 
// Recebe como parâmetro uma string (representação do número), e um número inteiro que 
// determina em qual base está o número do primeiro parâmetro.
void baseToDec(char* num_base, int base) {
    int indx = 0, exp = 0, len = 0, dec = 0, num = 0;
    
    // Este loop determina o tamanho da string
    while(num_base[len] != '\0') {
        len++;
    }
    
    // Neste loop, a string é percorrida de trás para frente. Cada caractere é convertido
    // em um número inteiro, multiplicado pela base elevada ao expoente (0..Comprimento da string) 
    // e somado ao valor da variável 'dec', que recebe as somas sucessivas. 
    for(exp, indx=(len-1); exp < len; exp++, indx--) {
        if(base == 16) {
            switch(num_base[indx]) {
                case 'A' : num = 10; break;
                case 'a' : num = 10; break;
                case 'B' : num = 11; break;
                case 'b' : num = 11; break;
                case 'C' : num = 12; break;
                case 'c' : num = 12; break;
                case 'D' : num = 13; break;
                case 'd' : num = 13; break;
                case 'E' : num = 14; break;
                case 'e' : num = 14; break;
                case 'F' : num = 15; break;
                case 'f' : num = 15; break;
                default : num = (int)num_base[indx] - (int)'0';
            }
        }
        else
            num = (int)num_base[indx] - (int)'0';

        // Aqui avalia se o usuário digitou um número válido ou não!
        if(num > (base-1) || num < 0) {
            printf("Você informou um número inválido!\n");
            exit(0);
        }
        dec += num * pow(base, exp);
    }

    // O número convertido para decimal é exibido
    printf("DECIMAL: %d\n", dec);
}

// Função que imprime o menu da calculadora
void menu(void) {
    printf("\n===========================================================\n");
    printf("\nBEM-VINDO AO CONVERSOR DE BASES NUMÉRICAS\n\n");
    printf("QUAL OPERAÇÃO DESEJA REALIZAR? \n\n");
    printf("[ 1 ] BINÁRIO     ->  DECIMAL\n");
    printf("[ 2 ] DECIMAL     ->  BINÁRIO\n");
    printf("[ 3 ] OCTAL       ->  DECIMAL\n");
    printf("[ 4 ] DECIMAL     ->  OCTAL\n");
    printf("[ 5 ] HEXADECIMAL ->  DECIMAL\n");
    printf("[ 6 ] DECIMAL     ->  HEXADECIMAL\n");
    printf("[ 0 ] SAIR\n");
    printf("\n===========================================================\n");
}

// Função que chama as funções correspondentes à opção escolhida
void process_calc(int opt) {
    int number;
    char str[SIZE];
    switch(opt) {
        case 0 :
            printf("CALCULADORA ENCERRADA!\n");
            exit(0);
        case 1 : 
            printf("BINÁRIO: ");
            scanf("%s", str);
            baseToDec(str, 2);
            break;
        case 2 : 
            printf("DECIMAL: ");
            scanf("%d", &number);
            decToBase(number, 2);
            break;
        case 3 : 
            printf("OCTAL = ");
            scanf("%s", str);
            baseToDec(str, 8);
            break;
        case 4 : 
            printf("DECIMAL = ");
            scanf("%d", &number);
            decToBase(number, 8);
            break;
        case 5 : 
            printf("HEXADECIMAL = ");
            scanf("%s", str);
            baseToDec(str, 16);
            break;
        case 6 : 
            printf("DECIMAL: ");
            scanf("%d", &number);
            decToBase(number, 16);
            break;
        default :
            printf("OPÇÃO NÃO PERMITIDA!\nTENTE NOVAMENTE\n");
            break;
    }
}

int main(void) {
    int opcao;
    menu();
    printf("QUAL OPERAÇÃO DESEJA REALIZAR? ");
    scanf("%d", &opcao);
    process_calc(opcao);
    
    return 0; 
}




 

numBases_conv.c

Editado por BrNaka
  • Agradecer 1

Compartilhar este post


Link para o post
Compartilhar em outros sites
Postado (editado)

Ficou bom o código, obrigado por compartilhar!

No trecho:

case 'A' : num = 10; break;
case 'a' : num = 10; break;

Será que daria para usar só um break?

case 'A' : num = 10; 
case 'a' : num = 10; break;

Ou então talvez usar a função tolower/toupper (ctype.h) . Talvez ficaria mais elegante, não sei rsrs

Ah, também poderia ter usado funções como strlen:

size_t tam = strlen(array);

 

Editado por gzn
+strlen
  • Curtir 1

Compartilhar este post


Link para o post
Compartilhar em outros sites

Dá pra tratar os dois casos juntos também:

case 'a':
case 'A':
	num=10;
	break;

Sendo minimalista hehe:

case 'a': case 'A':
	num=10; break;

Mas a ideia de converter pra maiúscula ou minúscula antes de comparar é boa também.

Outra ideia é, sabendo que 'a' é 97, você pode substituir todo esse switch por uma única linha:

num = tolower(num_base[indx]) - 87;

Se quiser pode tratar num if entre 'a' e 'f' também. Só lembrar que tudo é número😜

Abraço!

Compartilhar este post


Link para o post
Compartilhar em outros sites

Obrigado pelas dicas, Fernando e gzn! 

Eu também achei que o uso desse 'switch' ficou um pouco deselegante haha
Mas estava funcionando, então nem toquei kkk 

Abraço! 😃

Compartilhar este post


Link para o post
Compartilhar em outros sites
Postado (editado)

Tem uma outra forma de fazer essa conversão que acho sensacional, que simplesmente usa bitwise e codifica em HEX baseado no ASCII. Essa forma é mais segura contra cache timing attack, inclusive versões similares são usados pela BoringSSL para decodificar os certificados e tal, o negativo é a performance, que é bizarramente mais lenta.

Eu não sou nada bom em C, mas fiz um esforço para sair alguma coisa razoável, se tiver sugestão de melhorias eu agradeço.

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <stdint.h>

#define SIZE 255

struct hexResult {
    uint64_t i;
    uint64_t e;
};

struct hexResult hexToDec(char str[]) {
    struct hexResult r = {0, 0};

    for (int i = 0; i < _mbstrlen(str); i++) {
        uint64_t b = str[i] - (uint64_t) 48;
        b -= (((uint64_t) 9 - b) >> (uint64_t) 8) & (uint64_t) 7;
        b -= (((uint64_t) 41 - b) >> (uint64_t) 8) & (uint64_t) 32;


        r.e |= ((b >> (uint64_t) 8) | ((uint64_t) 15 - b) >> (uint64_t) 8) & (uint64_t) 1;
        r.i |= b << (uint64_t) (4 * i);
    }

    return r;
}

int main(void) {
    int opcao;
    char str[SIZE];

    printf("Insira o hexadecimal: ");
    scanf("%s", str);

    struct hexResult result = hexToDec(str);
    if (result.e) {
        printf("%s", "Caracteres invalidos foram detectados");
    }

    printf("%lld", result.i);

    return 0;
}

 

Isso é só para decodificar o hexadecimal para uint64. Acredito que o resto possa ser feito da mesma forma. A ideia é bem simples, basicamente. 

Assumimos que "A" é 10, assim como "1" é 1. O "A" no ASCII é o 65 (em inteiro), portanto se fizermos 65-55 teremos exatamente o 10, assim como "1" que em ASCII é o 49, podemos fazer simplesmente 49-48 que daria 1, exatamente o mesmo valor que o "1" vale em hexadecimal.

 

Para evitar usar o if, para mitigar timing-attack (e similares) o que se faz é compara se o número é menor ou maior  usando bitwise. Neste trecho especifico:

b = str[i] - (uint64_t) 48;

Todos os caracteres serão subtraídos por 48. Dessa forma o "1" será 1, assim com o "9" será 9. Porém, o "A" será 17, porque existe uma lacuna no ASCII entre o números e letras, por isso existe a segunda linha:

b -= (((uint64_t) 9 - b) >> (uint64_t) 8) & (uint64_t) 7;

 

Então, se o número for maior que 9 ele será um número negativo. Veja que 9-10 = -1, mas 9 - 9 = 0 e 9-8 = 1. Então o que queremos é saber se ele é ou não maior do que 9. Para isso fazemos o shift para pegar o resto dos bits, após os 7 primeiros bits, já que o numero nunca irá exceder 7 bits. Se ele for negativo  o bit do sinal de 1 será propagado pelo shift, ficando 11111111.... O and entre o -1 & 7 irá funcionar, assim ficará b -= 7. Porém, se for positivo, todos os bits a esquerda serão 0, então o shift também sera tudo 0, e consequentemente o and não irá funcionar, assim ficará b -= 0 & 7 que será b -= 0;

 

A mesma lógica se aplica aos demais casos. Isso é apenas uma outra forma de resolver, sem usar if e nem tabelas. Eu não sei há outros erros, lembre-se que não estou acostumado com o C, e não uso ele. :)

Editado por Lucas Rodrigues
  • l33t 2

Compartilhar este post


Link para o post
Compartilhar em outros sites

Obrigado pela resposta, Lucas Rodrigues! 

 

Compartilhar este post


Link para o post
Compartilhar em outros sites

Crie uma conta ou entre para comentar

Você precisar ser um membro para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar Agora

  • Quem Está Navegando   0 membros estão online

    Nenhum usuário registrado visualizando esta página.

×