
Passo 1 de 9
Organize a expressão antes de calcular
Aplique precedência e parênteses para calcular expressões numéricas simples em C/C++.
Trilha de aprendizado · Nível 9 · Tutorial 3
Escreva cálculos em C/C++ que respeitem a faixa dos tipos e a ordem das operações, evitando truncamentos e resultados intermediários inadequados.
Organize a expressão antes de calcular
Aplique precedência e parênteses para calcular expressões numéricas simples em C/C++. 2 min
Atualize valores com += e -=
Use += e -= para atualizar uma variável e acompanhe seu valor após cada instrução. 2 min
Separe quociente e resto
Use / para encontrar o quociente inteiro, % para encontrar o resto e confirme que o divisor não é zero. 3 min
Escolha quando preservar a fração
Faça uma divisão em ponto flutuante alterando um operando antes do operador `/`, preservando a parte fracionária quando ela for necessária. 3 min
Converta valores de forma intencional
Use casts explícitos para controlar quando uma expressão passa a usar ponto flutuante e reconheça o truncamento ao converter um float para inteiro. 3 min
Amplie o tipo antes da operação
Corrija multiplicações de inteiros de 16 bits ampliando um operando antes do resultado intermediário. 3 min
Diferencie retorno modular e estouro com sinal
Classifique cálculos próximos aos limites, distinguindo retorno modular sem sinal de estouro com sinal sem resultado garantido. 3 min
Evite perdas ao misturar ou reduzir tipos
Reconheça conversões implícitas que alteram valores negativos e evite reduzir resultados para destinos que não comportam sua faixa. 3 min
Revise e valide um cálculo completo
Corrija um cálculo de tempo, teste valores de fronteira e aplique critérios para reconhecer resultados válidos, aproximações, perdas e operações inválidas. 4 min

Passo 1 de 9
Aplique precedência e parênteses para calcular expressões numéricas simples em C/C++.
Em C/C++, uma expressão numérica pode combinar +, -, * e /. Neste início, use exemplos em que toda divisão é exata e todos os resultados cabem nos tipos do Uno R3.
A regra principal é:
* e / têm precedência sobre + e -.Assim, 2 + 3 * 4 não é (2 + 3) * 4: primeiro ocorre a multiplicação, depois a soma.

A árvore evidencia o agrupamento: 2 + (3 * 4).
Considere esta atribuição:
int resultado = 18 - 8 / 2 + 3;
A divisão vem primeiro: 8 / 2 resulta em 4. Restam 18 - 4 + 3.
Subtração e adição têm a mesma precedência, portanto o agrupamento é da esquerda para a direita: (18 - 4) + 3. O valor armazenado é 17.
“Da esquerda para a direita” descreve o agrupamento desses operadores. Não use essa frase para tirar conclusões sobre uma ordem geral de avaliação de operandos; aqui os operandos são apenas valores e não têm efeitos colaterais.
Trecho para estudar
int resultado = 18 - 8 / 2 + 3;
// Agrupamento: (18 - (8 / 2)) + 3
// resultado recebe 17Dica
Use parênteses quando quiser mudar ou deixar explícito o cálculo pretendido. Por exemplo, (18 - 8) / 2 + 3 agrupa a subtração antes da divisão e resulta em 8.
Parênteses internos são resolvidos antes das operações externas. Eles tornam a intenção legível para você e para quem revisar o código.
Ordene as etapas usadas para calcular 20 - 6 / 3 + 2 de acordo com o agrupamento da expressão.
Coloque as etapas na ordem correta.
Você quer calcular primeiro a soma 7 + 5 e depois multiplicar o total por 3.
Complete as lacunas apenas com os parênteses necessários. A expressão final deve produzir 36.
Complete os parênteses para obter o agrupamento solicitado.
int total = 7 + 5 * 3;
Passo 2 de 9
Use += e -= para atualizar uma variável e acompanhe seu valor após cada instrução.
Uma atribuição composta atualiza a variável usando o valor que ela já armazena.
saldo += 3; soma 3 ao valor atual de saldo.saldo -= 2; subtrai 2 do valor atual de saldo.Em ambos os casos, o novo valor substitui o anterior na própria variável.

Cada instrução usa o valor armazenado naquele instante e grava o resultado de volta na variável.
Dica
Leia += como “some e guarde” e -= como “subtraia e guarde”. A atualização ocorre quando a instrução é executada; ela não é uma igualdade matemática.
As duas formas são alternativas: cada bloco começa com o mesmo valor inicial e executa somente uma atualização.
// Forma composta
int pontosA = 12;
pontosA += 5; // pontosA passa a valer 17
// Forma expandida equivalente
int pontosB = 12;
pontosB = pontosB + 5; // pontosB passa a valer 17Exemplo
Com uma variável simples, pontos += 5; tem o mesmo efeito pretendido de pontos = pontos + 5;. Para subtrair, pontos -= 4; corresponde a pontos = pontos - 4;.
A forma composta evita repetir o nome da variável e evidencia que ela será atualizada. Nos exemplos deste step, os resultados cabem no tipo int do Uno R3.
Acompanhe o valor após cada linha. Comece em 10, some 6 e depois subtraia 4.
int nivel = 10;
nivel += 6; // nivel: 16
nivel -= 4; // nivel: 12Dica
Uma linha depende do valor deixado pela linha anterior. Portanto, não calcule cada atualização a partir do valor inicial.
Qual valor está armazenado em nivel ao final do código mostrado?
Complete a atualização para que tentativas passe de 2 para 9.
int tentativas = 2;
tentativas 7;Qual é o valor final de contador?
int contador = 20;
contador += 5;
contador -= 8;
contador += 3;
Passo 3 de 9
Use / para encontrar o quociente inteiro, % para encontrar o resto e confirme que o divisor não é zero.
Quando os dois operandos são inteiros não negativos, / produz o quociente inteiro: a parte que sobraria como fração é descartada.
Em 17 / 5, cabem 3 grupos completos de 5 em 17. Portanto, o resultado é 3, não 3,4.
Use % para obter o resto da mesma divisão: 17 % 5 resulta em 2.

O quociente conta os grupos completos; o resto mostra o que não completou outro grupo.
Exemplo
Para operandos não negativos, a relação é:
dividendo = divisor × quociente + resto
Além disso, o resto vai de 0 até um valor menor que o divisor. No exemplo: 17 = 5 × 3 + 2.
Em C/C++, / e % usam os mesmos dois valores, mas respondem perguntas diferentes:
17 / 5 → quantos grupos completos? Resultado: 3.17 % 5 → quanto sobrou? Resultado: 2.Os valores abaixo são inteiros, portanto ambos os resultados também são inteiros.
Exemplo com 17 dividido em grupos de 5
int quantidade = 17;
int porGrupo = 5;
int gruposCompletos = quantidade / porGrupo;
int sobra = quantidade % porGrupo;Dica
Se quantidade fosse 0 e porGrupo fosse 5, os cálculos seriam válidos: 0 / 5 é 0 e 0 % 5 é 0. O dividendo pode ser zero.
Complete os operadores para guardar o número de grupos completos e a quantidade que sobra. Considere apenas valores inteiros não negativos.
Preencha os operadores adequados.
int itens = 23;
int tamanhoGrupo = 4;
int grupos = itens tamanhoGrupo;
int restantes = itens tamanhoGrupo;Atenção
Antes de calcular com / ou %, confira o divisor: o valor à direita do operador. Ele precisa ser diferente de zero.
Uma operação como 8 / 0 ou 8 % 0 é inválida. Não tente prever um resultado numérico para ela.
Já 0 / 8 e 0 % 8 são válidas: o divisor é 8, não zero.
Qual expressão é inválida por ter divisor igual a zero?

Passo 4 de 9
Faça uma divisão em ponto flutuante alterando um operando antes do operador /, preservando a parte fracionária quando ela for necessária.
Em uma divisão, dois operandos inteiros produzem uma divisão inteira, que descarta a parte fracionária. Para preservá-la, faça pelo menos um operando ser float.
float metade = 5.0f / 2;Como 5.0f é float, o 2 é convertido implicitamente para float nesta operação. O resultado é aproximadamente 2.5f.
Use ponto em literais no código, nunca vírgula. O sufixo f deixa explícito que o literal é float.

O tipo dos operandos determina se a divisão descarta ou preserva a fração.
Compare as duas declarações. As duas guardam o resultado em uma variável float, mas somente uma expressão calcula a divisão em ponto flutuante.
Compare o tipo dos operandos antes de olhar para a variável de destino.
float a = 5 / 2; // a recebe 2.0f
float b = 5.0f / 2; // b recebe aproximadamente 2.5fDica
Em a, a expressão 5 / 2 ocorre somente entre inteiros e vale 2. Só então esse valor é armazenado como 2.0f.
Em b, 5.0f já é um operando float; por isso, a divisão preserva a fração. O inteiro 2 é convertido implicitamente para participar dessa operação.
Considere int pulsos = 7; e int partes = 2;. Complete a expressão para calcular a média como aproximadamente 3.5f. A mudança precisa ocorrer em um operando antes de /.
Preencha o literal adequado antes do operador /.
int pulsos = 7;
int partes = 2;
float media = / partes;Associe cada expressão ao valor que chega à variável. Observe primeiro os operandos usados em /; o tipo da variável de destino não refaz uma operação que já terminou.
Relacione cada declaração ao resultado correspondente.
Toque em um item e depois no par correspondente.
Atenção
Antes de qualquer divisão, confirme que o divisor não é zero. Isso vale para divisões inteiras e para ponto flutuante. Não use um resultado especial como forma de tratar um cálculo que começou com divisor igual a zero.

Passo 5 de 9
Use casts explícitos para controlar quando uma expressão passa a usar ponto flutuante e reconheça o truncamento ao converter um float para inteiro.
Uma conversão pode ser implícita, quando a linguagem ajusta um valor para participar de uma expressão ou atribuição, ou explícita, quando você escreve o tipo desejado.
A sintaxe (tipo)expressão funciona nos exemplos de C e C++. Use-a para tornar clara uma decisão importante: converter um operando para float antes de / faz a divisão preservar a fração. Uma atribuição como float valor = 3; também converte 3 implicitamente para 3.0f, mas isso acontece depois que o valor da expressão já foi obtido.
Compare o ponto em que cada conversão ocorre.
int total = 7;
int partes = 2;
float quocienteCorreto = (float)total / partes; // aproximadamente 3.5f
float quocienteTardio = (float)(total / partes); // 3.0fDica
No primeiro caso, total já é convertido para float antes de /; então partes também participa como float e a fração é preservada. No segundo, total / partes acontece primeiro entre inteiros e produz 3. Converter esse resultado para float gera 3.0f: a fração que não foi calculada não pode ser recuperada.
Ao converter um float para um tipo inteiro, a parte fracionária é descartada em direção a zero. Isso não é arredondamento.
Assim, (int)3.8f produz 3 e (int)-3.8f produz -3. Nos dois exemplos, o resultado cabe em int. Antes de fazer esse tipo de conversão, confira se o valor pode ser representado pelo destino: um cast não valida a faixa nem garante que toda informação seja preservada.

Converter para inteiro remove a fração; não escolhe o inteiro mais próximo.
Atenção
Os inteiros pequenos usados neste step podem ser representados exatamente em float. Não generalize isso para qualquer inteiro: números maiores podem não ter representação exata em ponto flutuante. Escolha o tipo conforme a faixa e a precisão necessárias para o cálculo.
Preencha a lacuna com o tipo que deve ser aplicado a total antes da divisão.
int total = 9;
int partes = 4;
float media = ()total / partes;Verdadeiro ou falso: supondo que o valor caiba em int, (int)-3.8f produz -4 porque o cast arredonda para baixo.

Passo 6 de 9
Corrija multiplicações de inteiros de 16 bits ampliando um operando antes do resultado intermediário.
Em uma expressão, primeiro ocorre o cálculo; só depois o valor é atribuído à variável de destino. Portanto, declarar o destino como int32_t não altera automaticamente a faixa usada pela multiplicação.
No Uno R3, int tem 16 bits. Quando linhas e colunas são inteiros com sinal de 16 bits, a multiplicação entre eles precisa caber nessa faixa. Se o resultado matemático não couber, a expressão já tem estouro com sinal: não há resultado garantido pela linguagem, mesmo que o destino tenha 32 bits.

A conversão deve acontecer antes da operação que pode exigir uma faixa maior.
Considere valores de 16 bits no Uno R3. Matematicamente, 300 × 200 vale 60000. Esse valor é maior que o máximo de int16_t, que é 32767, mas cabe tranquilamente em int32_t.
A primeira versão abaixo continua inadequada: o cast acontece depois de uma multiplicação que já precisava de mais faixa. Na segunda, o cast de um operando faz a multiplicação ocorrer com a faixa ampliada.
Compare as duas posições possíveis para a conversão.
#include <stdint.h>
int16_t linhas = 300;
int16_t colunas = 200;
// Inadequado: a multiplicação em 16 bits já pode estourar.
int32_t areaInadequada = (int32_t)(linhas * colunas);
// Adequado para estes valores: um operando é ampliado antes.
int32_t area = (int32_t)linhas * colunas;Dica
O cast não “conserta” um valor depois que a operação problemática ocorreu. Ele deve mudar o tipo de pelo menos um operando antes de *.
Ainda é preciso conferir a faixa: int32_t também tem limite. A conversão anterior apenas torna a multiplicação adequada quando o resultado matemático cabe no tipo ampliado e no destino.
Use a previsão matemática para decidir a faixa necessária antes de escrever o cálculo. Neste exemplo, não tente prever um valor produzido pela versão inadequada: com estouro de inteiro com sinal, o programa não tem resultado numérico garantido.
Faixas e resultado matemático no exemplo
| Etapa | Tipo ou faixa | Valor relevante |
|---|---|---|
| Operandos | int16_t | 300 e 200 |
| Resultado matemático | — | 60000 |
| Multiplicação em 16 bits | máx. 32767 | não comporta 60000 |
| Multiplicação ampliada | int32_t | comporta 60000 |
| Destino | int32_t | recebe 60000 |
Complete o tipo da conversão para que linhas seja ampliado antes da multiplicação. Considere que linhas e colunas são int16_t e que o resultado precisa ser armazenado em int32_t.
Preencha a lacuna com o tipo de destino apropriado para ampliar o operando.
int32_t area = ()linhas * colunas;Por que esta declaração não protege, sozinha, a multiplicação?
int32_t resultado = linhas * colunas;

Passo 7 de 9
Classifique cálculos próximos aos limites, distinguindo retorno modular sem sinal de estouro com sinal sem resultado garantido.
No Uno R3, unsigned int tem 16 bits: sua faixa vai de 0 a 65535. Quando uma operação efetiva nesse tipo passa de um limite, o resultado volta à faixa como se ela fosse circular: o cálculo é feito em módulo 2¹⁶, isto é, módulo 65536.
Assim, 65535U + 1U tem resultado matemático 65536, mas o resultado em unsigned int é 0. Da mesma forma, 0U - 1U resulta em 65535.
Isso é uma regra da aritmética sem sinal; não é um erro que o compilador precise impedir.

Em um unsigned int de 16 bits, somar ou subtrair além dos limites retorna à faixa de 0 a 65535.
Dica
Registre sempre duas coisas: o resultado matemático e o resultado conforme o tipo usado. Eles podem ser diferentes, mesmo quando a instrução compila normalmente.
Estes exemplos usam unsigned int de 16 bits, como no Uno R3. Os sufixos U deixam os literais sem sinal e tornam o tipo da operação explícito.
Exemplos de resultados em unsigned int de 16 bits
| Expressão | Resultado matemático | Resultado segundo o tipo |
|---|---|---|
| 65535U + 1U | 65536 | 0 |
| 65534U + 3U | 65537 | 1 |
| 0U - 1U | -1 | 65535 |
| 2U - 5U | -3 | 65533 |
Atenção
Não trate esse retorno como uma maneira genérica de lidar com valores fora da faixa. Primeiro defina se o cálculo desejado realmente é modular; caso contrário, escolha uma faixa que comporte o resultado matemático.
Para inteiros com sinal, ultrapassar a faixa não produz um retorno modular garantido por C/C++. Por exemplo, no Uno R3, int tem faixa de -32768 a 32767. Portanto, 32767 + 1 não tem resultado garantido pela linguagem: é estouro com sinal, ou comportamento não definido.
Não conclua que o resultado será -32768 porque isso parece ocorrer em determinada placa ou teste. Uma compilação bem-sucedida e uma observação isolada não tornam o cálculo válido.

Sem sinal há retorno modular definido; com sinal, ultrapassar a faixa não oferece resultado numérico garantido.
Dica
Quando o resultado matemático precisa ser maior que a faixa com sinal original, amplie um operando antes da operação. Converter apenas depois não recupera um resultado intermediário que já sofreu estouro.
Relacione cada caso ao resultado ou à classificação correta. Considere os tipos mostrados e não tente executar os casos com estouro para descobrir um suposto valor.
Toque em um item e depois no par correspondente.
Complete o tipo da conversão para que a multiplicação não ocorra na faixa de int16_t.
#include <stdint.h>
int16_t amostras = 30000;
int16_t ganho = 2;
int32_t produto = ()amostras * ganho;
Passo 8 de 9
Reconheça conversões implícitas que alteram valores negativos e evite reduzir resultados para destinos que não comportam sua faixa.
No Uno R3, int e unsigned int têm 16 bits. Em uma expressão que mistura esses dois tipos, o operando int é convertido para unsigned int nesse caso. Se o valor com sinal for negativo, ele deixa de representar um número negativo e passa a representar um valor alto na faixa sem sinal.
Evite combinar tipos com e sem sinal quando o cálculo ou seu resultado pode ser negativo. Escolha tipos compatíveis com todos os valores que precisam fazer parte do domínio do cálculo.

Com 16 bits, o padrão de -3 como int é interpretado como 65533 como unsigned int.
Neste cálculo, ajuste é convertido implicitamente para unsigned int antes da soma.
int ajuste = -3;
unsigned int pulsos = 2;
unsigned int total = pulsos + ajuste; // total recebe 65535 no Uno R3Atenção
Matematicamente, o cálculo pretendido seria 2 + (-3), isto é, -1. Mas, antes da soma, -3 passa a representar 65533 na faixa sem sinal de 16 bits. A expressão equivale a 2U + 65533U e resulta em 65535U.
O resultado não preserva um total negativo. Se valores negativos são válidos no problema, não use essa mistura de tipos para representá-los.
Uma operação intermediária pode ser válida e, ainda assim, a atribuição seguinte pode perder informação. Verifique dois pontos separadamente:
No Uno R3, valores uint8_t são promovidos a int em uma soma, pois todos os valores de uint8_t cabem em int. Portanto, 200 + 100 é calculado como int e resulta validamente em 300. O risco abaixo está na redução posterior para uint8_t.
A soma é válida como int, mas apenas uint16_t preserva o resultado 300 no destino.
#include <stdint.h>
uint8_t parteA = 200;
uint8_t parteB = 100;
int soma = parteA + parteB; // 300: válido em int no Uno R3
uint16_t total = soma; // preserva 300
uint8_t byte = soma; // não preserva 300; recebe 44Atenção
uint8_t comporta somente valores de 0 a 255. Ao converter 300 para esse tipo sem sinal de 8 bits, o valor é reduzido módulo 256 e se torna 44. Escrever (uint8_t)soma apenas deixa a conversão explícita: não aumenta a faixa do destino nem confirma que o valor cabe nele.
Dica
Determine matematicamente os valores mínimo e máximo que precisam ser preservados. Para guardar 300 sem perda, escolha ao menos um tipo que comporte 300, como uint16_t.
Evite também depender da conversão de um valor que não cabe em um tipo inteiro com sinal. Não há um resultado que seja seguro assumir nesse caso; escolha antes um tipo compatível com a faixa necessária.
Agora considere dois valores uint16_t que podem chegar a 40000. No Uno R3, uma soma desses valores pode precisar de mais de 16 bits: o máximo matemático é 80000.
Não basta declarar o destino como uint32_t: um operando deve ser convertido para uint32_t antes de +, para que a soma ocorra nessa faixa. O destino também precisa manter essa capacidade.
Preencha os dois tipos para que a soma de até 80000 seja calculada e armazenada sem perda.
Complete os tipos que preservam a soma.
#include <stdint.h>
uint16_t amostraA = 40000;
uint16_t amostraB = 40000;
total = ()amostraA + amostraB;Analise os tipos dos operandos antes de aceitar o resultado armazenado.
int saldo = -13;
unsigned int creditos = 10;
unsigned int resultado = saldo + creditos;Qual é o principal problema desse cálculo?

Passo 9 de 9
Corrija um cálculo de tempo, teste valores de fronteira e aplique critérios para reconhecer resultados válidos, aproximações, perdas e operações inválidas.
Você vai revisar um cálculo de tempo para um valor armazenado em uint16_t no Uno R3. O programa precisa produzir:
tempoUs: microssegundos inteiros, isto é, milissegundos × 1000;tempoS: segundos com fração, isto é, milissegundos ÷ 1000.Defina primeiro o resultado e a unidade desejados. Depois, confira o tipo em que cada operação ocorre. No Uno R3, uint16_t corresponde a unsigned int de 16 bits. Assim, sem uma ampliação prévia, tempoMs * 1000 ocorre como aritmética sem sinal de 16 bits.
Até 65 ms, o produto 65000 cabe nessa faixa. Em 66 ms, o resultado matemático 66000 ultrapassa 65535 e retorna modularmente para 464. Por isso, converta um operando para int32_t antes de multiplicar.

A conversão antes da multiplicação evita o retorno modular; um operando float torna a divisão fracionária.
Trecho completo para analisar. O cast faz a multiplicação ocorrer em int32_t; o literal 1000.0f faz a divisão ocorrer em ponto flutuante.
#include <stdint.h>
const uint16_t tempoMs = 500;
const int32_t tempoUs = (int32_t)tempoMs * 1000;
const float tempoS = (float)tempoMs / 1000.0f;Teste um valor central e pontos de fronteira. A multiplicação corrigida ocorre em int32_t e, para qualquer valor possível de uint16_t, o produto por 1000 cabe em int32_t.
Os valores 65 e 66 revelam o limite da expressão original. Sem o cast, 65U * 1000U resulta em 65000, ainda dentro de unsigned int de 16 bits. Já 66U * 1000U tem previsão matemática de 66000, mas a aritmética sem sinal retorna módulo 65536: o resultado passa a ser 464.
Na coluna de segundos, ≈ indica uma aproximação de float. A divisão não sofre truncamento inteiro, mas várias frações decimais não possuem representação binária exata.
Casos para tempoUs = (int32_t)tempoMs * 1000 e tempoS = (float)tempoMs / 1000.0f.
| tempoMs | Previsão matemática | Intermediário corrigido | Resultado segundo os tipos | Perda ou observação |
|---|---|---|---|---|
| 0 | 0 µs; 0 s | int32_t; float | 0; 0,0 | Não |
| 65 | 65000 µs; 0,065 s | int32_t; float | 65000; ≈ 0,065 | Original ainda dá 65000 |
| 66 | 66000 µs; 0,066 s | int32_t; float | 66000; ≈ 0,066 | Original sem cast retorna 464 |
| 500 | 500000 µs; 0,5 s | int32_t; float | 500000; 0,5 | Não nesta fração |
| 65534 | 65534000 µs; 65,534 s | int32_t; float | 65534000; ≈ 65,534 | Sem truncamento; float aprox. |
| 65535 | 65535000 µs; 65,535 s | int32_t; float | 65535000; ≈ 65,535 | Sem truncamento; float aprox. |
Dica
Não escreva ((int32_t)(tempoMs * 1000)). Nessa versão, a multiplicação ocorre antes do cast como unsigned int de 16 bits e pode retornar modularmente. Também não substitua 1000.0f por 1000 quando precisar da parte fracionária: isso faria a divisão ocorrer entre inteiros.
Complete as expressões para que 66 ms gere 66000 µs e aproximadamente 0,066 s, sem retorno modular nem truncamento inteiro.
const uint16_t tempoMs = 66;
const int32_t tempoUs = ()tempoMs * 1000;
const float tempoS = (float)tempoMs / ;Relacione cada situação ao diagnóstico correto. Considere unsigned int de 16 bits no Uno R3.
Toque em um item e depois no par correspondente.
Explique por que a conversão para int32_t deve ficar antes de tempoMs * 1000, considerando o caso de 66 ms.
Escreva pelo menos 80 caracteres (0/80).
Resumo
Use esta sequência ao escrever ou revisar um cálculo numérico:
Parabéns! Você concluiu: Cálculos e conversões numéricas em C/C++
Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks