Jump to content

Conversor de bases


BrNaka

Recommended Posts

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.

#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

Link to comment
Share on other sites

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

 

Link to comment
Share on other sites

  • Administrators

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!

Link to comment
Share on other sites

  • Supporter - Nibble

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. :)

Link to comment
Share on other sites

  • 8 months later...

Um exemplo do uso de strtoul() para conversão de bases (qualquer entre 2 e 36) para decimal:

/* base.c

   Exemplo de uso de strtoul() para converter uma string em um valor inteiro
   de acordo com uma base numérica.

   O programa aceita apenas 2 argumentos: A string contendo o "valor" e a base
   desejada (em decimal). Exemplo:

     $ ./base 1010 2
     10
*/

#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <errno.h>

int main ( int argc, char *argv[] )
{
  unsigned long base, n;
  char *p;

  if ( argc != 3 )
  {
    fprintf ( stderr, "Usage: %s <string> <base>\n", basename ( argv[0] ) );
    return EXIT_FAILURE;
  }

  errno = 0;
  base = strtoul ( argv[2], &p, 10 );
  if ( *p || errno == ERANGE || base < 2 || base > 36 )
  {
    fputs ( "ERRO: Base numérica precisa estar entre 2 e 36.\n", stderr );
    return EXIT_FAILURE;
  }

  errno = 0;
  n = strtoul ( argv[1], &p, base );
  if ( *p || errno == ERANGE )
  {
    fputs ( "ERRO: Valor a ser convertido errado ou fora da faixa.\n", stderr );
    return EXIT_FAILURE;
  }

  printf ( "%lu\n", n );

  return EXIT_SUCCESS;
}

Compilando e testando:
 

$ cc -o base base.c
$ ./base
Usage: base <string> <base>
$ ./base 1010 2
10
$ ./base 1a 2
ERRO: Valor a ser convertido errado ou fora da faixa.
$ ./base 1a 16
26

 

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...