// Adiciona macros para adicionar tipos e valores booleanos no código, é // equivalente a manualmente definir macros como: // #define true 1 // #define false 0 #include // Adiciona funções para interagir com entrada e saída. #include // Adiciona funções e macros comuns como o EXIT_FAILURE e EXIT_SUCCESS // para deixar o código mais legível e seguindo padrões da linguagem. // É equivalente a definir manualmente com: // #define EXIT_FAILURE 1 // #define EXIT_SUCCESS 0 #include // Mínimo e Máximo número de leituras que podem ser feitas pelo usuário, // definidas aqui como macro para deixar o código mais légivel e esses // valores mais facilmente explicados e mudáveis. #define MIN_READS 3 #define MAX_READS 100 #define MAX_READ_VALUE 2000 // Coisas que forão levadas em consideração a esse código: // - Checagem de erro do usuário e de funções. // - Fazer o código ser legível e facilmente modificável. // - Seguir as convenções (que conheço e/ou estou aprendendo) da linguagem C. // - Uso de nome de variáveis e funções em inglês, para seguir convenções. // get_avarage retorna a média dos valores dentro do array `arr`. // `size` é obrigatório e precisa ser o tamanho `arr`. double get_avarage(double arr[], int size) { double sum = 0; for (int i = 0; i < size; i++) { sum += arr[i]; } return sum / size; } // get_min retorna o menor valor dentro do array `arr`. // `size` é obrigatório e precisa ser o tamanho `arr`. double get_min(double arr[], int size) { // Caso o array não tenha nenhum elemento, não teremos nenhum // valor para poder iniciar a variável `min` e teremos um erro // por tentar acessar o array. Então retornamos 0 como "valor" padrão // para a função. // // Tecnicamente, poderiamos retornar o menor valor possível que pode // ser armazenado numa double na casa dos negativos, usando `-DBL_MAX`, // mas sinto que isso seria confuso para o usuário. Como o usuário nesse // sentido colocou valor nenhum, 0 parece um valor esperável para essa função. if (size == 0) { return 0; } double min = arr[0]; for (int i = 1; i < size; i++) { if (arr[i] < 0) { min = arr[i]; } } return min; } // get_max retorna o maior valor dentro do array `arr`. // `size` é obrigatório e precisa ser o tamanho `arr`. double get_max(double arr[], int size) { double max = 0; for (int i = 0; i < size; i++) { if (arr[i] > max) { max = arr[i]; } } return max; } // show_differences imprime as diferenças de uma leitura para a outra, // o "desvio", passadas pelo array `reads`. `size` é obrigatório e precisa // ser o tamanho de `reads`. void show_differences(double reads[], int size) { double av = get_avarage(reads, size); for (int i = 0; i < size; i++) { printf("Leitura %d: %.2f Desvio: %.2f\n", i + 1, reads[i], reads[i] - av); } } // verify_reads recebe o mínimo e máximo aceitável do usuário e imprime quais // são as leituras dentro do array `reads` que estão dentro ou não dos limites. // `size` é obrigatório e precisa ser o tamanho `reads`. // // retorna EXIT_FAILURE (1) em caso de erro durante a leitura de entradas do // usuário. int verify_reads(double reads[], int size) { printf("Digite o mínimo aceitável: "); double min = 0.0; // Checagem de erro do scanf. Scanf retorna o número de valores // que foram corretamente pegos do STDIN, ou seja, se esse valor // não for igual a um, algum erro ou ação inesperada aconteceu. // // Essa checagem se repete pelo código todo quando o scanf é utilizado. // Assim, o código é mais seguro e resiliênte. if (scanf("%lf", &min) != 1) { printf("ERROR: Falha ao ler a entrada do usuário.\n"); return EXIT_FAILURE; } printf("Digite o máximo aceitável: "); double max = 0.0; if (scanf("%lf", &max) != 1) { printf("ERROR: Falha ao ler a entrada do usuário.\n"); return EXIT_FAILURE; } if (max < min) { printf("ERROR: Máximo é menor que o mínimo.\n"); return EXIT_FAILURE; } for (int i = 0; i < size; i++) { if (min > reads[i]) { printf("Leitura %d: ABAIXO DO LIMITE\n", i + 1); } else if (max < reads[i]) { printf("Leitura %d: ACIMA DO LIMITE\n", i + 1); } else { printf("Leitura %d: OK\n", i + 1); } } return EXIT_SUCCESS; } // print_bar imprime uma barra de asterísticos para mostrar o valor provido // via `value` de forma visual. void print_bar(int value) { printf("["); for (int i = 0; i < value; i++) { printf("*"); } printf("]\n"); } // show_intensity imprimime a intensidade das leituras em forma de uma barra // de asterítsticos. void show_intensity(double arr[], int size) { double av = get_avarage(arr, size); printf("Intensidade: %.2f\n", av); print_bar(av / MAX_READ_VALUE * 20); } int show_full_results(double reads[], int size) { printf("Média das leituras: %.2f\n", get_avarage(reads, size)); printf("Valor mínimo encontrado nas leituras: %.2f\n", get_min(reads, size)); printf("Valor máximo encontrado nas leituras: %.2f\n", get_max(reads, size)); if (verify_reads(reads, size) != EXIT_SUCCESS) { printf("ERROR: Falha ao verificar faixas das leituras.\n"); return EXIT_FAILURE; } show_intensity(reads, size); return EXIT_SUCCESS; } // imortal_process é a lógica principal do programa e que simula o IMORTAL-1. // // retorna EXIT_FAILURE (1) em caso de erro durante sua execução. int imortal_process() { printf("Quantas leituras serão realizadas? (min %d, max %d) ", MIN_READS, MAX_READS); int readCount = 0; if (scanf("%d", &readCount) != 1) { printf("ERROR: Falha ao ler a entrada do usuário.\n"); return EXIT_FAILURE; } if (readCount < MIN_READS || readCount > MAX_READS) { printf("ERROR: Número de leituras precisa estar entre %d e %d\n", MIN_READS, MAX_READS); return EXIT_FAILURE; } // Dinamicamente alocar memória para as leituras double *reads = malloc(readCount * sizeof(double)); for (int i = 0; i < readCount; i++) { // rand é uma função de que retorna um número inteiro aleatório // entre 0 e RAND_MAX, nessa linha, ele está sendo dividio por RAND_MAX para // poder virar um double entre 0 e 1, e depois multíplicado por 100 para // virar um número entre 0 e 100. reads[i] = ((double)rand() / (double)RAND_MAX) * MAX_READ_VALUE; printf("Leitura %d: %.2f\n", i + 1, reads[i]); } printf("\n--- OPERAÇÕES ---\n"); printf("1 - Média\n"); printf("2 - Máx/Mín\n"); printf("3 - Desvios\n"); printf("4 - Verificão de faixa\n"); printf("5 - Barra gráfica\n"); printf("6 - Relatório\n"); printf("0 - Sair\n"); printf("\n"); // loop while sendo usado para poder haver mais de uma operação por simulação, // ele irá rodar continuamente até uma declaração `return` ser chamada. while (true) { int opt = 0; printf("Escolha uma opção: "); if (scanf("%d", &opt) != 1) { printf("ERROR: Falha ao ler a entrada do usuário.\n"); free(reads); // Qualquer return tem que antes liberar a memória das leituras return EXIT_FAILURE; } // switch case sendo usado para deixar o código mais legível, ele é o mesmo // que utilizar declarações `if` e `else if` em sequência. Como estamos // apenas checando um valor `opt`, acredito que faz mais sentido o uso de // `switch` ao invés de `else if`. switch (opt) { case 1: { printf("Média das leituras: %.2f\n", get_avarage(reads, readCount)); break; // Esse `break` statement, e ademais dentro de blocos `case`, são // para o `switch` e não o `while` loop. } case 2: { printf("Valor mínimo encontrado nas leituras: %.2f\n", get_min(reads, readCount)); printf("Valor máximo encontrado nas leituras: %.2f\n", get_max(reads, readCount)); break; } case 3: { show_differences(reads, readCount); break; } case 4: { // Verificação de falha para a função `verify_reads` if (verify_reads(reads, readCount) != EXIT_SUCCESS) { printf("ERROR: Falha ao verificar faixas das leituras.\n"); free(reads); return EXIT_FAILURE; } break; } case 5: { show_intensity(reads, readCount); break; } case 6: { printf("\n--- Relatório Completo ---\n\n"); show_full_results(reads, readCount); printf("\n--- Relatório Completo ---\n"); break; } case 0: { printf("Saindo do programa.\n"); free(reads); return EXIT_SUCCESS; } // Checagem caso o usuário coloque uma operação que não existe, ou // em outras palavras, um valor que não esteja coberto por alguma // das declarações `case`. default: { printf("INFO: %d não é uma operação válida.", opt); break; } } printf("\nDeseja realizar outra operação? (s/n): "); char res = 'n'; if (scanf(" %c", &res) != 1) { printf("ERROR: Falha ao ler a entrada do usuário.\n"); free(reads); return EXIT_FAILURE; } if (res == 'n') { free(reads); return EXIT_SUCCESS; } } free(reads); return EXIT_SUCCESS; } // Processo inicial do programa, que inicia a simulação // do satélite IMORTAL-1. // // Acredito que provavelmente em um programa real essa função seria a que // contém o código da função `imortal_process`, e a reiniciação da simulação // ficaria em mãos de quem iniciou o programa (algum script bash, serviço // systemd, outro programa, etc). int main() { while (true) { printf("\n=== IMORTAL-1 - SISTEMA DE BORDO ===\n"); // Iniciação da simulação do IMORTAL-1, com checagem de // erro para caso o processo tenha falhado por alguma razão. if (imortal_process() != EXIT_SUCCESS) { printf("Erro ao executar IMORTAL-1"); return EXIT_FAILURE; }; printf("Deseja iniciar nova simulação? (s/n): "); char res = 'n'; if (scanf(" %c", &res) != 1) { printf("ERROR: Falha ao ler entrada do usuário.\n"); return EXIT_FAILURE; } if (res == 'n') { // Sai do loop e termina o programa. break; } } return EXIT_SUCCESS; }