Semana 4 — Decisiones y lógica
Objetivos
- Escribir programas que toman decisiones con
if,elseyswitch. - Combinar condiciones con operadores lógicos y evaluarlas con tablas de verdad.
- Explicar con un ejemplo cómo las compuertas implementan la lógica en hardware.
Teoría — Decisiones y lógica booleana · ✂️ Compuertas
Lectura: Apuntes, semana 4.
Temas:
- Decisiones en pseudocódigo y en diagramas de flujo. Operadores relacionales.
if,else,else ify decisiones anidadas. Error común:=en lugar de==.switch,case,breakydefault.&&,||y!. Tablas de verdad, cortocircuito y leyes de De Morgan.- ✂️ Compuertas AND, OR, NOT y XOR. Un sumador de un bit como ejemplo.
Laboratorio — Decisiones
Esta semana la sesión de laboratorio es para la Práctica 1. Las actividades de abajo son trabajo autónomo y no se entregan. Conviene hacerlas antes de la práctica.
- Escribir un programa que lea una nota de 0 a 20 y diga si aprobó, rechazando las notas fuera de rango.
- Escribir un programa que diga si un año es bisiesto. Probarlo con 1900, 2000, 2024 y 2026.
- Escribir una calculadora con
switch, que avise si se divide entre 0.
Entregas
- Práctica 1: Algoritmos, variables, representación de datos y decisiones. Ver
practicas/README.md.
Preguntas de repaso
- ¿Qué escribe
if (x = 0) printf("cero"); else printf("otro");? ¿Por qué? - Simplifiquen
!(a > 3 && b != 2). - En
n != 0 && 10 / n > 1, ¿qué evita el orden de las condiciones? - ¿Qué pasa en un
switchsi se olvida elbreak? - Sumen en binario
0110 + 0111indicando el acarreo de cada columna.
Código de la semana
Compilar y probar dentro del contenedor del curso con make y make test.
Ejemplos (18)Errores (15)Pruebas (3)Laboratorio (3)Compilación (1)
Ejemplos
Los programas que usan los apuntes y la clase.
#include <stdio.h>
// Suma dos números de 4 bits con cuatro sumadores de un bit en cadena.
// Sin ciclos, la misma cuenta se repite para cada columna.
int main(void) {
int a3, a2, a1, a0;
int b3, b2, b1, b0;
// %1d lee un solo dígito, así que 0101 se lee como cuatro números.
if (scanf("%1d%1d%1d%1d %1d%1d%1d%1d",
&a3, &a2, &a1, &a0, &b3, &b2, &b1, &b0) != 8) {
printf("Entrada inválida\n");
return 1;
}
if (a3 > 1 || a2 > 1 || a1 > 1 || a0 > 1 ||
b3 > 1 || b2 > 1 || b1 > 1 || b0 > 1) {
printf("Los bits deben ser 0 o 1\n");
return 1;
}
int carry0 = 0;
int partial0 = a0 != b0;
int sum0 = partial0 != carry0;
int carry1 = (a0 && b0) || (partial0 && carry0);
int partial1 = a1 != b1;
int sum1 = partial1 != carry1;
int carry2 = (a1 && b1) || (partial1 && carry1);
int partial2 = a2 != b2;
int sum2 = partial2 != carry2;
int carry3 = (a2 && b2) || (partial2 && carry2);
int partial3 = a3 != b3;
int sum3 = partial3 != carry3;
int carry4 = (a3 && b3) || (partial3 && carry3);
printf("acarreos: %d %d %d %d\n", carry4, carry3, carry2, carry1);
printf("suma: %d%d%d%d\n", sum3, sum2, sum1, sum0);
return 0;
}#include <stdio.h>
// Precio en céntimos de un menú según la opción elegida.
int main(void) {
int option;
int price;
if (scanf("%d", &option) != 1) {
printf("Entrada inválida\n");
return 1;
}
switch (option) {
case 1:
price = 1200;
break;
case 2:
price = 1500;
break;
case 3:
price = 1800;
break;
default:
printf("Opción inválida\n");
return 1;
}
printf("Precio: %d céntimos\n", price);
return 0;
}#include <math.h>
#include <stdio.h>
// Dos double casi nunca se comparan con ==. Se pregunta si están muy cerca.
int main(void) {
double sum = 0.1 + 0.2;
if (sum == 0.3) {
printf("Con ==: iguales\n");
} else {
printf("Con ==: distintos\n");
}
if (fabs(sum - 0.3) < 0.000001) {
printf("Con tolerancia: iguales\n");
} else {
printf("Con tolerancia: distintos\n");
}
return 0;
}#include <stdio.h>
// Evalúa las dos formas de cada ley de De Morgan con los valores leídos.
int main(void) {
int x, y;
if (scanf("%d %d", &x, &y) != 2) {
printf("Entrada inválida\n");
return 1;
}
printf("!(x >= 0 && y < 10) vale %d\n", !(x >= 0 && y < 10));
printf("x < 0 || y >= 10 vale %d\n", x < 0 || y >= 10);
printf("!(x == 0 || y == 0) vale %d\n", !(x == 0 || y == 0));
printf("x != 0 && y != 0 vale %d\n", x != 0 && y != 0);
return 0;
}#include <stdio.h>
// Lee el total de una compra en céntimos. Desde 100 soles se descuenta el 10 %.
int main(void) {
int total;
if (scanf("%d", &total) != 1) {
printf("Entrada inválida\n");
return 1;
}
if (total < 0) {
printf("El total no puede ser negativo\n");
return 1;
}
if (total >= 10000) {
total = total - total / 10;
}
printf("A pagar: %d.%02d soles\n", total / 100, total % 100);
return 0;
}#include <stdio.h>
// Sumador de un bit: suma a, b y el acarreo que llega.
int main(void) {
int a, b, carry_in;
if (scanf("%d %d %d", &a, &b, &carry_in) != 3) {
printf("Entrada inválida\n");
return 1;
}
if (a < 0 || a > 1 || b < 0 || b > 1 || carry_in < 0 || carry_in > 1) {
printf("Los bits deben ser 0 o 1\n");
return 1;
}
int partial = a != b;
int sum = partial != carry_in;
int carry_out = (a && b) || (partial && carry_in);
printf("suma %d, acarreo %d\n", sum, carry_out);
return 0;
}#include <stdio.h>
// Las cuatro compuertas, para dos bits a y b (cada uno 0 o 1).
int main(void) {
int a, b;
if (scanf("%d %d", &a, &b) != 2) {
printf("Entrada inválida\n");
return 1;
}
if (a < 0 || a > 1 || b < 0 || b > 1) {
printf("Los bits deben ser 0 o 1\n");
return 1;
}
printf("AND %d\n", a && b);
printf("OR %d\n", a || b);
printf("NOT %d\n", !a);
printf("XOR %d\n", a != b);
return 0;
}#include <stdio.h>
// Escribe el mayor de tres enteros, con decisiones anidadas.
int main(void) {
int a, b, c;
if (scanf("%d %d %d", &a, &b, &c) != 3) {
printf("Entrada inválida\n");
return 1;
}
if (a > b) {
if (a > c) {
printf("%d\n", a);
} else {
printf("%d\n", c);
}
} else {
if (b > c) {
printf("%d\n", b);
} else {
printf("%d\n", c);
}
}
return 0;
}#include <stdio.h>
// Convierte un promedio entero de 0 a 20 en una mención.
// La escala es un ejemplo de la clase, no un reglamento.
int main(void) {
int average;
if (scanf("%d", &average) != 1) {
printf("Entrada inválida\n");
return 1;
}
if (average < 0 || average > 20) {
printf("Promedio fuera de rango\n");
} else if (average >= 17) {
printf("Excelente\n");
} else if (average >= 14) {
printf("Bueno\n");
} else if (average >= 11) {
printf("Regular\n");
} else {
printf("Insuficiente\n");
}
return 0;
}#include <stdio.h>
// Escribe los días de un mes de un año no bisiesto.
int main(void) {
int month;
int days;
if (scanf("%d", &month) != 1) {
printf("Entrada inválida\n");
return 1;
}
switch (month) {
case 4:
case 6:
case 9:
case 11:
days = 30;
break;
case 2:
days = 28;
break;
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
days = 31;
break;
default:
printf("Mes inválido\n");
return 1;
}
printf("%d días\n", days);
return 0;
}#include <stdio.h>
// Dice si un entero es par o impar.
int main(void) {
int n;
if (scanf("%d", &n) != 1) {
printf("Entrada inválida\n");
return 1;
}
if (n % 2 == 0) {
printf("%d es par\n", n);
} else {
printf("%d es impar\n", n);
}
return 0;
}#include <stdio.h>
// La aritmética va antes que las comparaciones, y estas antes que && y ||.
int main(void) {
int x = 5;
int y = 2;
printf("%d\n", x + 1 > y * 3);
printf("%d\n", x > 3 && x < 10);
printf("%d\n", (x > 3 && y > 3) || y == 2);
printf("%d\n", x > 3 && (y > 3 || y == 2));
printf("%d\n", !(x > 3));
return 0;
}#include <stdio.h>
// Lee y valida en una sola condición: si scanf falla, n no se llega a mirar.
int main(void) {
int n;
if (scanf("%d", &n) == 1 && n >= 1 && n <= 12) {
printf("Mes válido: %d\n", n);
} else {
printf("Mes inválido\n");
return 1;
}
return 0;
}#include <stdio.h>
// Una comparación es una expresión: vale 1 si es cierta y 0 si es falsa.
int main(void) {
int a = 5;
int b = 3;
printf("a > b vale %d\n", a > b);
printf("a < b vale %d\n", a < b);
printf("a == b vale %d\n", a == b);
printf("a != b vale %d\n", a != b);
printf("a >= 5 vale %d\n", a >= 5);
printf("a <= 4 vale %d\n", a <= 4);
return 0;
}#include <stdio.h>
// Si n es 0, && no evalúa su lado derecho y la división nunca ocurre.
int main(void) {
int n;
if (scanf("%d", &n) != 1) {
printf("Entrada inválida\n");
return 1;
}
if (n != 0 && 10 / n > 1) {
printf("10 / %d es mayor que 1\n", n);
} else {
printf("No se cumple\n");
}
return 0;
}#include <stdio.h>
// Las tablas de verdad de &&, || y !, calculadas por el propio C.
int main(void) {
printf("a b | a&&b a||b !a\n");
printf("0 0 | %4d %4d %2d\n", 0 && 0, 0 || 0, !0);
printf("0 1 | %4d %4d %2d\n", 0 && 1, 0 || 1, !0);
printf("1 0 | %4d %4d %2d\n", 1 && 0, 1 || 0, !1);
printf("1 1 | %4d %4d %2d\n", 1 && 1, 1 || 1, !1);
return 0;
}#include <stdio.h>
// En C, 0 es falso y cualquier otro número es verdadero.
int main(void) {
int n;
if (scanf("%d", &n) != 1) {
printf("Entrada inválida\n");
return 1;
}
if (n) {
printf("%d cuenta como verdadero\n", n);
} else {
printf("%d cuenta como falso\n", n);
}
printf("!n vale %d\n", !n);
return 0;
}#include <stdio.h>
// Dice si un carácter es una vocal sin tilde.
int main(void) {
char letter;
if (scanf(" %c", &letter) != 1) {
printf("Entrada inválida\n");
return 1;
}
switch (letter) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
case 'A':
case 'E':
case 'I':
case 'O':
case 'U':
printf("Vocal\n");
break;
default:
printf("No es vocal\n");
}
return 0;
}Errores
Ejemplos con errores a propósito: no compilan, fallan o dan un resultado incorrecto. Los apuntes explican cada uno.
#include <stdio.h>
// Funciona, pero se lee mal: && se agrupa antes que ||.
int main(void) {
int a = 1;
int b = 0;
int c = 1;
printf("%d\n", a && b || c);
return 0;
}#include <stdio.h>
// Error a propósito: = asigna, == compara.
int main(void) {
int x = 5;
if (x = 0) {
printf("cero\n");
} else {
printf("otro\n");
}
printf("x vale %d\n", x);
return 0;
}#include <stdio.h>
// Error a propósito: una variable declarada entre llaves solo existe ahí dentro.
int main(void) {
int n = 4;
if (n > 0) {
int twice = n * 2;
printf("%d\n", twice);
}
printf("%d\n", twice);
return 0;
}#include <stdio.h>
// Error a propósito: en C, 0 <= grade <= 20 no significa lo que parece.
int main(void) {
int grade;
if (scanf("%d", &grade) != 1) {
printf("Entrada inválida\n");
return 1;
}
if (0 <= grade <= 20) {
printf("En rango\n");
} else {
printf("Fuera de rango\n");
}
return 0;
}#include <stdio.h>
// Error a propósito: 'a' es un carácter, "a" es otra cosa, una cadena.
int main(void) {
char letter = 'a';
if (letter == "a") {
printf("Es la letra a\n");
}
return 0;
}#include <stdio.h>
// Error a propósito: sin break, la ejecución sigue en el case de abajo.
int main(void) {
int option;
int price = 0;
if (scanf("%d", &option) != 1) {
printf("Entrada inválida\n");
return 1;
}
switch (option) {
case 1:
price = 1200;
case 2:
price = 1500;
case 3:
price = 1800;
break;
default:
printf("Opción inválida\n");
return 1;
}
printf("Precio: %d céntimos\n", price);
return 0;
}#include <stdio.h>
// Error a propósito: el else va con el if más cercano, no con el de su sangría.
int main(void) {
int a, b;
if (scanf("%d %d", &a, &b) != 2) {
printf("Entrada inválida\n");
return 1;
}
if (a > 0)
if (b > 0)
printf("Ambos positivos\n");
else
printf("a no es positivo\n");
return 0;
}#include <stdio.h>
// Error a propósito: la primera rama cierta gana, y aquí va primero la más amplia.
int main(void) {
int average;
if (scanf("%d", &average) != 1) {
printf("Entrada inválida\n");
return 1;
}
if (average >= 11) {
printf("Regular\n");
} else if (average >= 14) {
printf("Bueno\n");
} else if (average >= 17) {
printf("Excelente\n");
} else {
printf("Insuficiente\n");
}
return 0;
}#include <stdio.h>
// Error a propósito: sin llaves, el if solo gobierna una instrucción.
int main(void) {
int grade;
if (scanf("%d", &grade) != 1) {
printf("Entrada inválida\n");
return 1;
}
if (grade < 0)
printf("Nota negativa\n");
printf("Rechazada\n");
return 0;
}#include <stdio.h>
// Error a propósito: ! se aplica antes que ==, solo a la variable a.
int main(void) {
int a = 5;
int b = 3;
if (!a == b) {
printf("a es distinto de b\n");
} else {
printf("a es igual a b\n");
}
return 0;
}#include <stdio.h>
// Error a propósito: || necesita una condición completa a cada lado.
int main(void) {
int option;
if (scanf("%d", &option) != 1) {
printf("Entrada inválida\n");
return 1;
}
if (option == 1 || 2) {
printf("Opción 1 o 2\n");
} else {
printf("Otra opción\n");
}
return 0;
}#include <stdio.h>
// Error a propósito: con negativos, el resto puede ser -1.
int main(void) {
int n;
if (scanf("%d", &n) != 1) {
printf("Entrada inválida\n");
return 1;
}
if (n % 2 == 1) {
printf("%d es impar\n", n);
} else {
printf("%d es par\n", n);
}
return 0;
}#include <stdio.h>
// Error a propósito: el ; después del if termina la decisión.
int main(void) {
int n;
if (scanf("%d", &n) != 1) {
printf("Entrada inválida\n");
return 1;
}
if (n > 0);
printf("%d es positivo\n", n);
return 0;
}#include <stdio.h>
// Error a propósito: switch solo trabaja con enteros, y char es un entero.
int main(void) {
double grade = 10.5;
switch (grade) {
case 10:
printf("Diez\n");
break;
default:
printf("Otra\n");
}
return 0;
}#include <stdio.h>
// Error a propósito: primero divide y recién después pregunta si n es 0.
int main(void) {
int n;
if (scanf("%d", &n) != 1) {
printf("Entrada inválida\n");
return 1;
}
if (10 / n > 1 && n != 0) {
printf("10 / %d es mayor que 1\n", n);
} else {
printf("No se cumple\n");
}
return 0;
}Pruebas
Comprueban lo que afirman los apuntes; se ejecutan con make test.
"""Comprueba lo que los apuntes afirman sobre los programas de la semana 4."""
import pathlib
import subprocess
import tempfile
import unittest
HERE = pathlib.Path(__file__).parent
BUILD = HERE / "build"
FLAGS = ["-Wall", "-Wextra", "-std=c17"]
def run(program, stdin=""):
"""Ejecuta un programa de build/; devuelve su salida (con errores) y su estado."""
done = subprocess.run(
[str(BUILD / program)], input=stdin, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=10,
)
return done.stdout, done.returncode
def compile_error_demo(name, *extra):
"""Compila un programa de errors/; devuelve los mensajes, el estado y el ejecutable."""
out = pathlib.Path(tempfile.mkdtemp()) / name
done = subprocess.run(
["gcc", *FLAGS, *extra, f"errors/{name}.c", "-o", str(out)], cwd=HERE,
text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
)
return done.stdout, done.returncode, out
def run_demo(binary, stdin=""):
done = subprocess.run(
[str(binary)], input=stdin, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=10,
)
return done.stdout, done.returncode
class CleanCompile(unittest.TestCase):
def test_examples_have_no_warnings(self):
for source in sorted(HERE.glob("*.c")):
with self.subTest(source=source.name):
done = subprocess.run(
["gcc", *FLAGS, "-Werror", "-fsyntax-only", str(source)],
text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
)
self.assertEqual(done.returncode, 0, done.stdout)
class Decisions(unittest.TestCase):
def test_discount_boundary(self):
self.assertEqual(run("discount", "9999")[0], "A pagar: 99.99 soles\n")
self.assertEqual(run("discount", "10000")[0], "A pagar: 90.00 soles\n")
self.assertEqual(run("discount", "12050")[0], "A pagar: 108.45 soles\n")
def test_discount_rejects(self):
self.assertEqual(run("discount", "abc"), ("Entrada inválida\n", 1))
self.assertEqual(run("discount", ""), ("Entrada inválida\n", 1))
self.assertEqual(run("discount", "-5")[1], 1)
def test_parity_with_negatives(self):
self.assertEqual(run("parity", "7")[0], "7 es impar\n")
self.assertEqual(run("parity", "-3")[0], "-3 es impar\n")
self.assertEqual(run("parity", "-4")[0], "-4 es par\n")
self.assertEqual(run("parity", "0")[0], "0 es par\n")
def test_parity_wrong_fails_on_negatives(self):
_, _, binary = compile_error_demo("parity_wrong")
self.assertEqual(run_demo(binary, "-3")[0], "-3 es par\n")
def test_max_of_three_all_orders_and_ties(self):
for a, b, c in [(4, 9, 2), (9, 4, 2), (2, 4, 9), (5, 5, 1), (1, 5, 5), (5, 1, 5), (3, 3, 3), (-1, -2, -3)]:
with self.subTest(values=(a, b, c)):
self.assertEqual(run("max_of_three", f"{a} {b} {c}")[0], f"{max(a, b, c)}\n")
def test_mention_boundaries(self):
expected = {-1: "Promedio fuera de rango", 0: "Insuficiente", 10: "Insuficiente",
11: "Regular", 13: "Regular", 14: "Bueno", 16: "Bueno",
17: "Excelente", 20: "Excelente", 21: "Promedio fuera de rango"}
for grade, text in expected.items():
with self.subTest(grade=grade):
self.assertEqual(run("mention", str(grade))[0], text + "\n")
def test_mention_wrong_order_says_regular(self):
_, _, binary = compile_error_demo("mention_wrong_order")
self.assertEqual(run_demo(binary, "18")[0], "Regular\n")
class Logic(unittest.TestCase):
def test_relational_are_one_or_zero(self):
lines = run("relational")[0].splitlines()
self.assertEqual([line.split()[-1] for line in lines], ["1", "0", "0", "1", "1", "0"])
def test_nonzero_is_true(self):
self.assertEqual(run("truthy", "-7")[0], "-7 cuenta como verdadero\n!n vale 0\n")
self.assertEqual(run("truthy", "0")[0], "0 cuenta como falso\n!n vale 1\n")
def test_precedence_values(self):
self.assertEqual(run("precedence")[0].split(), ["0", "1", "1", "1", "0"])
def test_truth_table(self):
rows = run("truth_table")[0].splitlines()[1:]
cells = [row.replace("|", " ").split() for row in rows]
for a, b, conj, disj, neg in (map(int, cell) for cell in cells):
self.assertEqual(conj, a and b)
self.assertEqual(disj, a or b)
self.assertEqual(neg, 1 - a)
def test_de_morgan_on_a_grid(self):
for x in range(-2, 13):
for y in [-1, 0, 1, 9, 10, 11]:
values = [line.split()[-1] for line in run("de_morgan", f"{x} {y}")[0].splitlines()]
with self.subTest(x=x, y=y):
self.assertEqual(values[0], values[1])
self.assertEqual(values[2], values[3])
def test_short_circuit_avoids_division(self):
output, status = run("short_circuit", "0")
self.assertEqual((output, status), ("No se cumple\n", 0))
self.assertEqual(run("short_circuit", "5")[0], "10 / 5 es mayor que 1\n")
self.assertEqual(run("short_circuit", "20")[0], "No se cumple\n")
def test_swapped_operands_divide_by_zero(self):
_, _, binary = compile_error_demo("unsafe_division", "-g", "-fsanitize=undefined")
self.assertIn("runtime error: division by zero", run_demo(binary, "0")[0])
def test_read_in_range(self):
self.assertEqual(run("read_in_range", "12"), ("Mes válido: 12\n", 0))
for text in ["0", "13", "abc", ""]:
with self.subTest(text=text):
self.assertEqual(run("read_in_range", text), ("Mes inválido\n", 1))
def test_double_comparison(self):
self.assertEqual(run("compare_double")[0], "Con ==: distintos\nCon tolerancia: iguales\n")
class Switch(unittest.TestCase):
def test_month_days(self):
days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
for month, count in enumerate(days, start=1):
with self.subTest(month=month):
self.assertEqual(run("month_days", str(month)), (f"{count} días\n", 0))
self.assertEqual(run("month_days", "13"), ("Mes inválido\n", 1))
self.assertEqual(run("month_days", "0"), ("Mes inválido\n", 1))
def test_combo(self):
self.assertEqual(run("combo", "1")[0], "Precio: 1200 céntimos\n")
self.assertEqual(run("combo", "2")[0], "Precio: 1500 céntimos\n")
self.assertEqual(run("combo", "3")[0], "Precio: 1800 céntimos\n")
self.assertEqual(run("combo", "4"), ("Opción inválida\n", 1))
def test_missing_break_falls_through(self):
messages, status, binary = compile_error_demo("combo_no_break")
self.assertEqual(status, 0)
self.assertIn("-Wimplicit-fallthrough", messages)
self.assertEqual(run_demo(binary, "1")[0], "Precio: 1800 céntimos\n")
def test_vowel(self):
for text in ["a", "E", " u"]:
with self.subTest(text=text):
self.assertEqual(run("vowel", text)[0], "Vocal\n")
for text in ["b", "7", "ñ", "á"]:
with self.subTest(text=text):
self.assertEqual(run("vowel", text)[0], "No es vocal\n")
class Gates(unittest.TestCase):
def test_gates(self):
for a in (0, 1):
for b in (0, 1):
with self.subTest(a=a, b=b):
self.assertEqual(
run("gates", f"{a} {b}")[0],
f"AND {a & b}\nOR {a | b}\nNOT {1 - a}\nXOR {a ^ b}\n",
)
self.assertEqual(run("gates", "2 0"), ("Los bits deben ser 0 o 1\n", 1))
def test_full_adder_all_inputs(self):
for a in (0, 1):
for b in (0, 1):
for c in (0, 1):
total = a + b + c
with self.subTest(a=a, b=b, c=c):
self.assertEqual(run("full_adder", f"{a} {b} {c}")[0],
f"suma {total % 2}, acarreo {total // 2}\n")
def test_adder4_all_inputs(self):
for a in range(16):
for b in range(16):
text = f"{a:04b} {b:04b}"
lines = run("adder4", text)[0].splitlines()
with self.subTest(text=text):
self.assertEqual(lines[1], f"suma: {(a + b) % 16:04b}")
self.assertEqual(lines[0].split()[1], str((a + b) // 16))
def test_adder4_rejects(self):
self.assertEqual(run("adder4", "0102 0011"), ("Los bits deben ser 0 o 1\n", 1))
self.assertEqual(run("adder4", "01 01"), ("Entrada inválida\n", 1))
class CompilerMessages(unittest.TestCase):
"""Cada error a propósito da el aviso que dicen los apuntes y se comporta como dicen."""
def check(self, name, message, stdin, expected):
messages, status, binary = compile_error_demo(name)
self.assertEqual(status, 0, messages)
self.assertIn(message, messages)
self.assertEqual(run_demo(binary, stdin)[0], expected)
def test_assignment(self):
self.check("assign_in_if", "-Wparentheses", "", "otro\nx vale 0\n")
def test_chained(self):
self.check("chained_compare", "do not have their mathematical meaning", "25", "En rango\n")
def test_semicolon(self):
self.check("semicolon_if", "-Wempty-body", "-3", "-3 es positivo\n")
def test_indentation(self):
self.check("misleading_indent", "-Wmisleading-indentation", "15", "Rechazada\n")
def test_dangling(self):
self.check("dangling_else", "-Wdangling-else", "5 -1", "a no es positivo\n")
self.check("dangling_else", "-Wdangling-else", "-5 1", "")
def test_not(self):
self.check("not_precedence", "-Wlogical-not-parentheses", "", "a es igual a b\n")
def test_and_or(self):
self.check("and_or_no_parens", "within", "", "1\n")
def test_or_constant_is_silent(self):
messages, status, binary = compile_error_demo("or_constant")
self.assertEqual((messages, status), ("", 0))
self.assertEqual(run_demo(binary, "7")[0], "Opción 1 o 2\n")
def test_char_vs_string(self):
messages, _, _ = compile_error_demo("char_vs_string")
self.assertIn("comparison between pointer and integer", messages)
def test_errors_that_do_not_compile(self):
messages, status, _ = compile_error_demo("block_scope")
self.assertNotEqual(status, 0)
self.assertIn("undeclared", messages)
messages, status, _ = compile_error_demo("switch_double")
self.assertNotEqual(status, 0)
self.assertIn("switch quantity not an integer", messages)
if __name__ == "__main__":
unittest.main()"""Pruebas del laboratorio. LAB_DIR dice dónde está el código: lab o solution."""
import os
import pathlib
import subprocess
import unittest
HERE = pathlib.Path(__file__).parent
LAB_DIR = os.environ.get("LAB_DIR", "solution")
PASSING_GRADE = 11
def run(program, stdin):
done = subprocess.run(
[str(HERE / "build" / LAB_DIR / program)], input=stdin, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=10,
)
return done.stdout.strip(), done.returncode
@unittest.skipUnless((HERE / LAB_DIR).is_dir(), f"no existe {LAB_DIR}/")
class LabTests(unittest.TestCase):
def test_no_warnings(self):
for name in ["grade", "leap_year", "calculator"]:
with self.subTest(name=name):
done = subprocess.run(
["gcc", "-Wall", "-Wextra", "-std=c17", "-Werror", "-fsyntax-only",
str(HERE / LAB_DIR / f"{name}.c")],
text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
)
self.assertEqual(done.returncode, 0, done.stdout)
def test_grade_passes_and_fails(self):
cases = {0: "Desaprobado", PASSING_GRADE - 1: "Desaprobado",
PASSING_GRADE: "Aprobado", 20: "Aprobado"}
for grade, text in cases.items():
with self.subTest(grade=grade):
self.assertEqual(run("grade", str(grade)), (text, 0))
def test_grade_out_of_range(self):
for grade in [-1, 21, 100]:
with self.subTest(grade=grade):
self.assertEqual(run("grade", str(grade)), ("Nota fuera de rango", 1))
def test_grade_not_a_number(self):
for text in ["abc", ""]:
with self.subTest(text=text):
self.assertEqual(run("grade", text), ("Entrada inválida", 1))
def test_leap_years(self):
cases = {1900: "No bisiesto", 2000: "Bisiesto", 2024: "Bisiesto", 2026: "No bisiesto",
2100: "No bisiesto", 2400: "Bisiesto", 2023: "No bisiesto"}
for year, text in cases.items():
with self.subTest(year=year):
self.assertEqual(run("leap_year", str(year)), (text, 0))
def test_leap_year_not_a_number(self):
self.assertEqual(run("leap_year", "abc"), ("Entrada inválida", 1))
def test_calculator_operations(self):
cases = {"8 + 2": "10.00", "8 - 2": "6.00", "8 * 2": "16.00", "8 / 2": "4.00",
"7 / 2": "3.50", "0.1 + 0.2": "0.30", "8+2": "10.00", "0 / 5": "0.00"}
for text, expected in cases.items():
with self.subTest(text=text):
self.assertEqual(run("calculator", text), (expected, 0))
def test_calculator_division_by_zero(self):
for text in ["5 / 0", "5 / 0.0", "0 / 0"]:
with self.subTest(text=text):
self.assertEqual(run("calculator", text), ("Error: división entre 0", 1))
def test_calculator_unknown_operator(self):
self.assertEqual(run("calculator", "8 x 2"), ("Operador desconocido", 1))
def test_calculator_bad_input(self):
for text in ["abc", "8 /", "8 / abc", ""]:
with self.subTest(text=text):
self.assertEqual(run("calculator", text), ("Entrada inválida", 1))
if __name__ == "__main__":
unittest.main()"""Vuelve a correr los comandos que aparecen en los apuntes y en las diapositivas
y comprueba que el código citado existe de verdad en esta carpeta."""
import pathlib
import re
import subprocess
import unittest
HERE = pathlib.Path(__file__).parent
ROOT = HERE.parent.parent
NOTES = ROOT / "notes" / "week-04.md"
SLIDES = ROOT / "slides" / "week-04.tex"
# Antes de un bloque cuya salida cambia con el procesador. Solo se exigen sus
# líneas con "runtime error".
MACHINE_DEPENDENT = "<!-- depende del procesador -->"
def normalize(text):
# Según el idioma del entorno, gcc usa comillas tipográficas o rectas.
return text.replace("‘", "'").replace("’", "'")
def run_shell(command):
done = subprocess.run(
["bash", "-c", command], cwd=HERE, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=60,
)
return normalize(done.stdout)
def split_commands(lines):
"""Parte un bloque de terminal en pares (comando, líneas de salida)."""
pairs = []
for line in lines:
if line.startswith("$ "):
pairs.append((line[2:], []))
elif pairs:
pairs[-1][1].append(normalize(line.rstrip()))
return pairs
def note_blocks(language):
"""Devuelve (líneas, depende_del_procesador) de cada bloque de los apuntes."""
blocks, current, flagged, previous = [], None, False, ""
for raw in NOTES.read_text(encoding="utf-8").splitlines():
line = re.sub(r"^> ?", "", raw)
if current is None:
if line.strip() == "```" + language:
current, flagged = [], previous.strip() == MACHINE_DEPENDENT
elif line.strip():
previous = line
elif line.strip() == "```":
blocks.append((current, flagged))
current = None
else:
current.append(line)
return blocks
def slide_blocks(environments):
text = SLIDES.read_text(encoding="utf-8")
pattern = r"\\begin\{(?:%s)\}[^\n]*\n(.*?)\\end\{(?:%s)\}" % (environments, environments)
return [match.group(1).splitlines() for match in re.finditer(pattern, text, re.S)]
def known_code_lines():
lines = set()
for source in list(HERE.glob("*.c")) + list((HERE / "errors").glob("*.c")):
lines.update(line.strip() for line in source.read_text(encoding="utf-8").splitlines())
return lines
class NotesAreTrue(unittest.TestCase):
@unittest.skipUnless(NOTES.exists(), "faltan los apuntes")
def test_terminal_blocks_in_notes(self):
checked = 0
for lines, flagged in note_blocks("text"):
if not lines or not lines[0].startswith("$ "):
continue
for command, expected in split_commands(lines):
actual = run_shell(command).rstrip("\n")
checked += 1
with self.subTest(command=command):
if flagged:
wanted = [line for line in expected if "runtime error" in line]
for line in wanted:
self.assertIn(line, actual)
else:
self.assertEqual(actual, "\n".join(expected))
self.assertGreater(checked, 10)
@unittest.skipUnless(NOTES.exists(), "faltan los apuntes")
def test_code_blocks_in_notes_exist(self):
known = known_code_lines()
for lines, _ in note_blocks("c"):
for line in lines:
if line.strip() and line.strip() != "...":
with self.subTest(line=line):
self.assertIn(line.strip(), known)
class SlidesAreTrue(unittest.TestCase):
@unittest.skipUnless(SLIDES.exists(), "faltan las diapositivas")
def test_terminal_blocks_in_slides(self):
for block in slide_blocks("terminal|salidalarga"):
for command, expected in split_commands(block):
actual = run_shell(command).splitlines()
position = 0
# Una diapositiva puede omitir líneas, pero no cambiarlas ni reordenarlas.
for line in expected:
line = line.strip()
if not line:
continue
with self.subTest(command=command, line=line):
found = [i for i in range(position, len(actual)) if actual[i].strip() == line]
self.assertTrue(found, f"no aparece en la salida real: {line}")
if found:
position = found[0] + 1
@unittest.skipUnless(SLIDES.exists(), "faltan las diapositivas")
def test_code_blocks_in_slides_exist(self):
known = known_code_lines()
for block in slide_blocks("codigo|codigochico|codigomini"):
for line in block:
if line.strip() and line.strip() != "...":
with self.subTest(line=line):
self.assertIn(line.strip(), known)
if __name__ == "__main__":
unittest.main()Laboratorio
Archivos de partida del laboratorio de la semana.
// Laboratorio, parte 3: calculadora con switch.
// Lee una operación como 8 / 2: un número, un operador (+, -, * o /) y otro número.
//
// El programa escribe exactamente una de estas líneas:
// el resultado con dos decimales, como 4.00
// Error: división entre 0 termina con return 1
// Operador desconocido termina con return 1
// Entrada inválida no se pudieron leer los tres datos; termina con return 1
//
// Pruébenlo con make test-lab.
#include <stdio.h>
int main(void) {
// Escriban aquí su programa.
return 0;
}// Laboratorio, parte 1: lee una nota entera y dice si aprobó.
// En este laboratorio se aprueba con 11 o más.
//
// El programa escribe exactamente una de estas líneas:
// Aprobado nota de 11 a 20; termina con return 0
// Desaprobado nota de 0 a 10; termina con return 0
// Nota fuera de rango nota menor que 0 o mayor que 20; termina con return 1
// Entrada inválida lo escrito no es un número; termina con return 1
//
// Pruébenlo con make test-lab.
#include <stdio.h>
int main(void) {
// Escriban aquí su programa.
return 0;
}// Laboratorio, parte 2: lee un año y dice si es bisiesto.
// Pruébenlo con 1900, 2000, 2024 y 2026.
//
// El programa escribe exactamente una de estas líneas:
// Bisiesto
// No bisiesto
// Entrada inválida lo escrito no es un número; termina con return 1
//
// Pruébenlo con make test-lab.
#include <stdio.h>
int main(void) {
// Escriban aquí su programa.
return 0;
}Compilación
Cómo se compilan y ejecutan los ejemplos.
# Semana 4: decisiones y lógica. Se usa dentro del entorno del curso.
#
# make compila los programas de la clase en build/
# make test corre las pruebas de los ejemplos y de los apuntes
# make test-lab prueba su laboratorio (lab/*.c); falla hasta que lo terminen
# make clean borra lo generado
#
# Los programas de errors/ tienen errores a propósito. No se compilan con make:
# los compilan las pruebas, y ustedes, con los comandos de los apuntes.
CC = gcc
CFLAGS = -Wall -Wextra -std=c17 -g -fsanitize=address,undefined
LDLIBS = -lm
BUILD = build
PROGRAMS = discount parity max_of_three mention relational truthy precedence \
truth_table de_morgan short_circuit read_in_range compare_double \
month_days combo vowel gates full_adder adder4
LAB_PROGRAMS = grade leap_year calculator
.PHONY: all lab test test-lab clean
all: $(PROGRAMS:%=$(BUILD)/%)
$(BUILD)/%: %.c | $(BUILD)
$(CC) $(CFLAGS) $< -o $@ $(LDLIBS)
$(BUILD)/lab/%: lab/%.c | $(BUILD)/lab
$(CC) $(CFLAGS) $< -o $@ $(LDLIBS)
$(BUILD)/solution/%: solution/%.c | $(BUILD)/solution
$(CC) $(CFLAGS) $< -o $@ $(LDLIBS)
$(BUILD) $(BUILD)/lab $(BUILD)/solution:
mkdir -p $@
lab: $(LAB_PROGRAMS:%=$(BUILD)/lab/%)
# Si existe solution/, también se comprueba la solución del laboratorio.
test: all
@if [ -d solution ]; then $(MAKE) --no-print-directory \
$(LAB_PROGRAMS:%=$(BUILD)/solution/%); fi
python3 -m unittest discover -p 'test_*.py'
test-lab: all lab
LAB_DIR=lab python3 -m unittest -v test_lab
clean:
rm -rf $(BUILD) __pycache__