Semana 3 — Bits: cómo se representan los datos

Objetivos

  • Convertir números entre decimal, binario y hexadecimal.
  • Explicar por qué ocurre el overflow y por qué 0.1 + 0.2 no da exactamente 0.3.
  • Distinguir carácter, código y byte, y explicar cómo UTF-8 guarda la ñ.
  • Guardar el historial de un trabajo con los comandos básicos de Git.

Teoría — Bits: binario, hexadecimal, enteros, decimales y texto

Lectura: Apuntes, semana 3.

Temas:

  • Sistemas posicionales: decimal, binario y hexadecimal. Bytes y múltiplos.
  • Enteros con signo y complemento a dos. Overflow.
  • Punto flotante en lo esencial. Error común: comparar double con ==. El dinero se guarda en céntimos.
  • char es un número pequeño: ASCII.
  • Unicode y UTF-8: por qué ñ ocupa 2 bytes y n ocupa 1.

Laboratorio — Bits a mano y primeros pasos con Git

  1. Convertir a mano 202 a binario y a hexadecimal. Comprobarlo con printf("%x").
  2. Sumar 1 al mayor int, con el valor leído por scanf, compilando con -fsanitize=undefined. Leer el mensaje.
  3. Imprimir 0.1 + 0.2 con %.20f.
  4. Crear un archivo con año y mirarlo con xxd. Identificar los bytes de la ñ.
  5. Crear un repositorio con git init, hacer dos commits y revisar git status, git diff y git log.

Preguntas de repaso

  1. Conviertan 10110110 a decimal y a hexadecimal.
  2. ¿Cuál es el mayor entero con signo de 8 bits? ¿Cómo se escribe -1 en complemento a dos?
  3. ¿Por qué no conviene guardar el saldo de una cuenta en un double?
  4. Un archivo tiene 5 caracteres y 7 bytes en UTF-8. ¿Cómo es posible?
  5. ¿Para qué sirven git add y git commit, y en qué se diferencian?

Código de la semana

Compilar y probar dentro del contenedor del curso con make y make test.

Ejemplos (16)Errores (2)Pruebas (2)Laboratorio (4)Compilación (1)

Ejemplos

Los programas que usan los apuntes y la clase.

bases.cDescargar bases.c
// Un mismo número, escrito en decimal y en hexadecimal.
#include <stdio.h>

int main(void) {
    int n = 202;
    int m = 0xCA;  // 0x indica que el literal está en hexadecimal

    printf("%d en decimal\n", n);
    printf("%x en hexadecimal, con minúsculas\n", n);
    printf("%X en hexadecimal, con mayúsculas\n", n);
    printf("0xCA vale %d\n", m);
    return 0;
}
char_codes.cDescargar char_codes.c
// Un char guarda un número: el código del carácter.
#include <stdio.h>

int main(void) {
    char letter = 'A';
    char digit = '7';

    printf("%c tiene el código %d\n", letter, letter);
    printf("%c tiene el código %d\n", letter + 1, letter + 1);
    printf("'a' - 'A' = %d\n", 'a' - 'A');
    printf("minúscula de %c: %c\n", letter, letter + 32);
    printf("%c tiene el código %d\n", digit, digit);
    printf("'7' - '0' = %d\n", digit - '0');
    return 0;
}
char_sign.cDescargar char_sign.c
// Lee un byte. Si vale 128 o más, char lo muestra distinto según la plataforma.
#include <limits.h>
#include <stdio.h>

int main(void) {
    char c = 0;
    int read_count = scanf("%c", &c);
    printf("scanf leyó %d dato(s)\n", read_count);

    printf("CHAR_MIN = %d, CHAR_MAX = %d\n", CHAR_MIN, CHAR_MAX);
    printf("como char:          %d\n", c);
    printf("como unsigned char: %d\n", (unsigned char) c);
    return 0;
}
compare_doubles.cDescargar compare_doubles.c
// Comparar double con == falla; se compara con una tolerancia.
#include <math.h>
#include <stdio.h>

int main(void) {
    double sum = 0.1 + 0.2;
    double target = 0.3;
    double tolerance = 0.000000001;

    printf("sum == target vale %d\n", sum == target);
    printf("fabs(sum - target) < tolerance vale %d\n", fabs(sum - target) < tolerance);
    return 0;
}
float_limits.cDescargar float_limits.c
// Un caso exacto y dos límites de double: los errores se acumulan y la precisión se acaba.
#include <stdio.h>

int main(void) {
    double half = 0.5;
    double quarter = 0.25;
    printf("0.5 + 0.25     = %.20f\n", half + quarter);

    double tenths = 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1;
    printf("diez veces 0.1 = %.20f\n", tenths);

    double big = 9007199254740992.0;  // 2 elevado a 53
    double bigger = big + 1.0;
    printf("big            = %.1f\n", big);
    printf("big + 1        = %.1f\n", bigger);
    return 0;
}
float_sum.cDescargar float_sum.c
// 0.1 + 0.2 no da exactamente 0.3.
#include <stdio.h>

int main(void) {
    double a = 0.1;
    double b = 0.2;
    double c = 0.3;
    double sum = a + b;

    printf("con %%f:     %f\n", sum);
    printf("0.1       = %.20f\n", a);
    printf("0.2       = %.20f\n", b);
    printf("0.3       = %.20f\n", c);
    printf("0.1 + 0.2 = %.20f\n", sum);
    printf("diferencia = %.20f\n", sum - c);
    // Una comparación vale 1 si es verdadera y 0 si es falsa.
    printf("sum == c vale %d\n", sum == c);
    return 0;
}
fraction_to_binary.cDescargar fraction_to_binary.c
// Escribe los primeros 9 bits de la parte fraccionaria de un número entre 0 y 1.
#include <stdio.h>

int main(void) {
    double x = 0.0;
    int read_count = scanf("%lf", &x);
    printf("scanf leyó %d dato(s)\n", read_count);

    // Cada paso duplica x. La parte entera, 0 o 1, es el bit; el cast la obtiene.
    x = x * 2;
    int b1 = (int) x;
    x = x - b1;
    x = x * 2;
    int b2 = (int) x;
    x = x - b2;
    x = x * 2;
    int b3 = (int) x;
    x = x - b3;
    x = x * 2;
    int b4 = (int) x;
    x = x - b4;
    x = x * 2;
    int b5 = (int) x;
    x = x - b5;
    x = x * 2;
    int b6 = (int) x;
    x = x - b6;
    x = x * 2;
    int b7 = (int) x;
    x = x - b7;
    x = x * 2;
    int b8 = (int) x;
    x = x - b8;
    x = x * 2;
    int b9 = (int) x;

    printf("0.%d%d%d%d%d%d%d%d%d...\n", b1, b2, b3, b4, b5, b6, b7, b8, b9);
    return 0;
}
from_binary.cDescargar from_binary.c
// Lee 8 dígitos binarios y escribe el número en decimal y en hexadecimal.
#include <stdio.h>

int main(void) {
    int value = 0;
    int bit = 0;
    int read_count = 0;

    // %1d lee un solo dígito. scanf devuelve 1 cada vez que lo logra.
    read_count = read_count + scanf("%1d", &bit);
    value = value * 2 + bit;
    read_count = read_count + scanf("%1d", &bit);
    value = value * 2 + bit;
    read_count = read_count + scanf("%1d", &bit);
    value = value * 2 + bit;
    read_count = read_count + scanf("%1d", &bit);
    value = value * 2 + bit;
    read_count = read_count + scanf("%1d", &bit);
    value = value * 2 + bit;
    read_count = read_count + scanf("%1d", &bit);
    value = value * 2 + bit;
    read_count = read_count + scanf("%1d", &bit);
    value = value * 2 + bit;
    read_count = read_count + scanf("%1d", &bit);
    value = value * 2 + bit;

    printf("scanf leyó %d dígitos\n", read_count);
    printf("decimal: %d\n", value);
    printf("hexadecimal: %x\n", value);
    return 0;
}
money.cDescargar money.c
// El mismo cálculo de dinero en soles con double y en céntimos con int.
#include <stdio.h>

int main(void) {
    double price = 1.10;
    double total = price * 3;
    printf("3 x 1.10 con double: %.20f\n", total);
    printf("lo mismo con %%.2f:   %.2f\n", total);

    double balance = 1.00 - 0.10 - 0.10 - 0.10 - 0.10 - 0.10 - 0.10 - 0.10 - 0.10 - 0.10 - 0.10;
    printf("saldo con double:    %.20f\n", balance);

    int price_cents = 110;
    int total_cents = price_cents * 3;
    printf("3 x 110 céntimos:    %d\n", total_cents);
    printf("en soles:            S/ %d.%02d\n", total_cents / 100, total_cents % 100);

    int balance_cents = 100 - 10 - 10 - 10 - 10 - 10 - 10 - 10 - 10 - 10 - 10;
    printf("saldo en céntimos:   %d\n", balance_cents);
    return 0;
}
overflow.cDescargar overflow.c
// Suma 1 a un int leído con scanf. Con INT_MAX se produce overflow.
#include <stdio.h>

int main(void) {
    int n = 0;
    int read_count = scanf("%d", &n);
    printf("scanf leyó %d dato(s), n = %d\n", read_count, n);

    int next = n + 1;
    printf("n + 1 = %d\n", next);
    return 0;
}
read_bytes.cDescargar read_bytes.c
// Lee 4 bytes y escribe cada uno en hexadecimal. %c lee un byte, no un carácter.
#include <stdio.h>

int main(void) {
    char b0 = 0;
    char b1 = 0;
    char b2 = 0;
    char b3 = 0;
    int read_count = scanf("%c%c%c%c", &b0, &b1, &b2, &b3);
    printf("scanf leyó %d byte(s)\n", read_count);

    // El cast evita que un byte de 128 o más se vea negativo donde char tiene signo.
    printf("%02x %02x %02x %02x\n", (unsigned char) b0, (unsigned char) b1,
           (unsigned char) b2, (unsigned char) b3);
    return 0;
}
sizes.cDescargar sizes.c
// Cuánto ocupa cada tipo y cuáles son los límites de los enteros.
#include <limits.h>
#include <stdio.h>

int main(void) {
    printf("bits en un byte: %d\n", CHAR_BIT);
    printf("sizeof(char)   = %zu\n", sizeof(char));
    printf("sizeof(int)    = %zu\n", sizeof(int));
    printf("sizeof(double) = %zu\n", sizeof(double));
    printf("INT_MAX = %d\n", INT_MAX);
    printf("INT_MIN = %d\n", INT_MIN);
    return 0;
}
to_binary.cDescargar to_binary.c
// Escribe en binario un número de 0 a 255, con divisiones sucesivas entre 2.
#include <stdio.h>

int main(void) {
    int n = 0;
    // Mostramos cuántos datos leyó scanf. Con if, la próxima semana, ya podremos reaccionar.
    int read_count = scanf("%d", &n);
    printf("scanf leyó %d dato(s)\n", read_count);

    int b0 = n % 2;
    n = n / 2;
    int b1 = n % 2;
    n = n / 2;
    int b2 = n % 2;
    n = n / 2;
    int b3 = n % 2;
    n = n / 2;
    int b4 = n % 2;
    n = n / 2;
    int b5 = n % 2;
    n = n / 2;
    int b6 = n % 2;
    n = n / 2;
    int b7 = n % 2;

    // El primer resto es el bit de menor peso: se escribe al final.
    printf("%d%d%d%d%d%d%d%d\n", b7, b6, b5, b4, b3, b2, b1, b0);
    return 0;
}
twos_complement.cDescargar twos_complement.c
// Muestra el patrón de bits de un entero de -128 a 127, en complemento a dos.
#include <stdio.h>

int main(void) {
    int x = 0;
    int read_count = scanf("%d", &x);
    printf("scanf leyó %d dato(s)\n", read_count);

    // Sin if: si x es negativo da x + 256; si no, da x. Solo vale de -128 a 127.
    int pattern = (x + 256) % 256;

    printf("%d en 8 bits: %d%d%d%d%d%d%d%d\n", x,
           pattern / 128 % 2, pattern / 64 % 2, pattern / 32 % 2, pattern / 16 % 2,
           pattern / 8 % 2, pattern / 4 % 2, pattern / 2 % 2, pattern % 2);
    // %x espera un valor sin signo: por eso el cast. Es el int completo, de 32 bits.
    printf("%d en un int: %08x\n", x, (unsigned int) x);
    return 0;
}
unsigned_wrap.cDescargar unsigned_wrap.c
// Los tipos sin signo sí dan la vuelta: C lo define así.
#include <stdio.h>

int main(void) {
    unsigned char b = 255;
    printf("b = %d\n", b);
    b = b + 1;
    printf("b = %d\n", b);
    return 0;
}
utf8_two_bytes.cDescargar utf8_two_bytes.c
// Calcula los dos bytes UTF-8 de un código Unicode de 128 a 2047.
#include <stdio.h>

int main(void) {
    int code = 0;
    int read_count = scanf("%d", &code);
    printf("scanf leyó %d dato(s)\n", read_count);

    // Sin if no podemos rechazar otros códigos: fuera del rango, el resultado no sirve.
    int first = 192 + code / 64;   // 110xxxxx: los 5 bits altos
    int second = 128 + code % 64;  // 10xxxxxx: los 6 bits bajos

    printf("bytes: %x %x\n", first, second);
    printf("carácter: %c%c\n", first, second);
    return 0;
}

Errores

Ejemplos con errores a propósito: no compilan, fallan o dan un resultado incorrecto. Los apuntes explican cada uno.

bad/char_enye.cDescargar bad/char_enye.c
// Error a propósito: la ñ no cabe en un char. No forma parte de make.
#include <stdio.h>

int main(void) {
    char c = 'ñ';
    printf("%c\n", c);
    return 0;
}
bad/constant_overflow.cDescargar bad/constant_overflow.c
// Error a propósito: el compilador ve el overflow al compilar. No forma parte de make.
#include <limits.h>
#include <stdio.h>

int main(void) {
    int next = INT_MAX + 1;
    printf("%d\n", next);
    return 0;
}

Pruebas

Comprueban lo que afirman los apuntes; se ejecutan con make test.

test_examples.pyDescargar test_examples.py
"""Pruebas de los ejemplos de la semana 3: comparan la salida real de cada programa
con lo que dicen los apuntes. Se corren con `make test`."""
import re
import subprocess
import unittest
from pathlib import Path

HERE = Path(__file__).resolve().parent
BUILD = HERE / "build"
PROGRAMS = sorted(p for p in HERE.glob("*.c"))


def run(name, stdin=""):
    if isinstance(stdin, str):
        stdin = stdin.encode()
    return subprocess.run([str(BUILD / name)], input=stdin, capture_output=True, timeout=20)


def out(name, stdin=""):
    result = run(name, stdin)
    return result.stdout.decode()


def compile_c(source, target, *flags):
    return subprocess.run(["gcc", "-Wall", "-Wextra", "-std=c17", *flags, "-o", str(target), str(source)],
                          capture_output=True, text=True, cwd=HERE)


class Warnings(unittest.TestCase):
    def test_no_warnings(self):
        for source in PROGRAMS:
            with self.subTest(source=source.name):
                result = subprocess.run(["gcc", "-Wall", "-Wextra", "-Werror", "-std=c17", "-fsyntax-only", str(source)],
                                        capture_output=True, text=True)
                self.assertEqual(result.returncode, 0, result.stderr)


class Bases(unittest.TestCase):
    def test_to_binary(self):
        self.assertEqual(out("to_binary", "202\n"), "scanf leyó 1 dato(s)\n11001010\n")
        self.assertEqual(out("to_binary", "182\n"), "scanf leyó 1 dato(s)\n10110110\n")

    def test_to_binary_bad_input(self):
        self.assertEqual(out("to_binary", "hola\n"), "scanf leyó 0 dato(s)\n00000000\n")

    def test_from_binary(self):
        self.assertEqual(out("from_binary", "10110110\n"),
                         "scanf leyó 8 dígitos\ndecimal: 182\nhexadecimal: b6\n")

    def test_bases(self):
        self.assertEqual(out("bases"), "202 en decimal\n"
                                       "ca en hexadecimal, con minúsculas\n"
                                       "CA en hexadecimal, con mayúsculas\n"
                                       "0xCA vale 202\n")

    def test_multiples(self):
        self.assertEqual(f"{500 * 10**9 / 2**30:.2f}", "465.66")
        self.assertEqual(100 * 10**6 / 8 / 10**6, 12.5)
        self.assertEqual(2**31 - 1, 2147483647)


class Integers(unittest.TestCase):
    def test_sizes(self):
        self.assertEqual(out("sizes"), "bits en un byte: 8\n"
                                       "sizeof(char)   = 1\n"
                                       "sizeof(int)    = 4\n"
                                       "sizeof(double) = 8\n"
                                       "INT_MAX = 2147483647\n"
                                       "INT_MIN = -2147483648\n")

    def test_twos_complement(self):
        table = {5: ("00000101", "00000005"), -1: ("11111111", "ffffffff"),
                 -3: ("11111101", "fffffffd"), -128: ("10000000", "ffffff80"),
                 127: ("01111111", "0000007f")}
        for x, (bits, word) in table.items():
            with self.subTest(x=x):
                self.assertEqual(out("twos_complement", f"{x}\n"),
                                 f"scanf leyó 1 dato(s)\n{x} en 8 bits: {bits}\n{x} en un int: {word}\n")

    def test_overflow_reported(self):
        result = run("overflow", "2147483647\n")
        self.assertIn("overflow.c:9:9: runtime error: signed integer overflow: "
                      "2147483647 + 1 cannot be represented in type 'int'", result.stderr.decode())

    def test_no_overflow(self):
        result = run("overflow", "41\n")
        self.assertEqual(result.stdout.decode(), "scanf leyó 1 dato(s), n = 41\nn + 1 = 42\n")
        self.assertEqual(result.stderr, b"")

    def test_overflow_bad_input(self):
        self.assertEqual(out("overflow", "hola\n"), "scanf leyó 0 dato(s), n = 0\nn + 1 = 1\n")

    def test_constant_overflow_warns(self):
        result = compile_c("bad/constant_overflow.c", BUILD / "constant_overflow")
        self.assertIn("warning: integer overflow in expression", result.stderr)

    def test_unsigned_wrap(self):
        result = run("unsigned_wrap")
        self.assertEqual(result.stdout.decode(), "b = 255\nb = 0\n")
        self.assertEqual(result.stderr, b"")


class Floats(unittest.TestCase):
    def test_float_sum(self):
        self.assertEqual(out("float_sum"), "con %f:     0.300000\n"
                                           "0.1       = 0.10000000000000000555\n"
                                           "0.2       = 0.20000000000000001110\n"
                                           "0.3       = 0.29999999999999998890\n"
                                           "0.1 + 0.2 = 0.30000000000000004441\n"
                                           "diferencia = 0.00000000000000005551\n"
                                           "sum == c vale 0\n")

    def test_float_limits(self):
        self.assertEqual(out("float_limits"), "0.5 + 0.25     = 0.75000000000000000000\n"
                                              "diez veces 0.1 = 0.99999999999999988898\n"
                                              "big            = 9007199254740992.0\n"
                                              "big + 1        = 9007199254740992.0\n")

    def test_compare_doubles(self):
        self.assertEqual(out("compare_doubles"), "sum == target vale 0\n"
                                                 "fabs(sum - target) < tolerance vale 1\n")

    def test_money(self):
        self.assertEqual(out("money"), "3 x 1.10 con double: 3.30000000000000026645\n"
                                       "lo mismo con %.2f:   3.30\n"
                                       "saldo con double:    0.00000000000000013878\n"
                                       "3 x 110 céntimos:    330\n"
                                       "en soles:            S/ 3.30\n"
                                       "saldo en céntimos:   0\n")

    def test_fraction_to_binary(self):
        self.assertEqual(out("fraction_to_binary", "0.1\n"), "scanf leyó 1 dato(s)\n0.000110011...\n")
        self.assertEqual(out("fraction_to_binary", "0.75\n"), "scanf leyó 1 dato(s)\n0.110000000...\n")

    def test_tenth_doubling(self):
        # Los bits de 0.1 de la tabla de los apuntes, con fracciones exactas.
        from fractions import Fraction
        x = Fraction(1, 10)
        bits = ""
        for _ in range(9):
            x *= 2
            bits += str(int(x))
            x -= int(x)
        self.assertEqual(bits, "000110011")


class Text(unittest.TestCase):
    def test_char_codes(self):
        self.assertEqual(out("char_codes"), "A tiene el código 65\n"
                                            "B tiene el código 66\n"
                                            "'a' - 'A' = 32\n"
                                            "minúscula de A: a\n"
                                            "7 tiene el código 55\n"
                                            "'7' - '0' = 7\n")

    def test_char_sign_both_platforms(self):
        # El signo de char depende de la plataforma: se comprueban las dos variantes.
        cases = {"-fsigned-char": ("CHAR_MIN = -128, CHAR_MAX = 127", "como char:          -61"),
                 "-funsigned-char": ("CHAR_MIN = 0, CHAR_MAX = 255", "como char:          195")}
        for flag, (limits, value) in cases.items():
            with self.subTest(flag=flag):
                target = BUILD / f"char_sign{flag}"
                self.assertEqual(compile_c("char_sign.c", target, flag).returncode, 0)
                result = subprocess.run([str(target)], input="ñ".encode(), capture_output=True).stdout.decode()
                self.assertIn(limits, result)
                self.assertIn(value, result)
                self.assertIn("como unsigned char: 195", result)

    def test_read_bytes(self):
        self.assertEqual(out("read_bytes", "año"), "scanf leyó 4 byte(s)\n61 c3 b1 6f\n")

    def test_utf8_two_bytes_matches_python(self):
        for code in (128, 193, 201, 205, 209, 211, 218, 225, 233, 237, 241, 243, 250, 2047):
            with self.subTest(code=code):
                first, second = chr(code).encode("utf-8")
                self.assertEqual(out("utf8_two_bytes", f"{code}\n"),
                                 f"scanf leyó 1 dato(s)\nbytes: {first:x} {second:x}\n"
                                 f"carácter: {chr(code)}\n")

    def test_utf8_table(self):
        table = {"A": "41", "n": "6e", "ñ": "c3b1", "€": "e282ac", "😀": "f09f9880"}
        for char, encoded in table.items():
            with self.subTest(char=char):
                self.assertEqual(char.encode("utf-8").hex(), encoded)
        self.assertEqual((ord("ñ"), ord("€"), ord("😀")), (0xF1, 0x20AC, 0x1F600))

    def test_xxd(self):
        result = subprocess.run(["xxd"], input="año\n".encode(), capture_output=True, text=False)
        self.assertEqual(result.stdout.decode(), "00000000: 61c3 b16f 0a                             a..o.\n")

    def test_five_characters_seven_bytes(self):
        self.assertEqual((len("ñandú"), len("ñandú".encode())), (5, 7))

    def test_mojibake(self):
        self.assertEqual("ñ".encode().decode("latin-1"), "ñ")

    def test_enye_constant_warns(self):
        target = BUILD / "char_enye"
        result = compile_c("bad/char_enye.c", target)
        self.assertRegex(result.stderr, re.compile(r"bad/char_enye\.c:5:14: warning: multi-character character constant"))
        self.assertIn("changes value from ‘50097’", result.stderr)


if __name__ == "__main__":
    unittest.main()
test_lab.pyDescargar test_lab.py
"""Pruebas del laboratorio 3. Corren sobre lab/ con `make test-lab`, y sobre solution/
dentro de `make test`, si esa carpeta existe. La actividad 5 (Git) no se prueba aquí:
se revisa en el laboratorio."""
import os
import re
import subprocess
import unittest
from pathlib import Path

HERE = Path(__file__).resolve().parent
LAB_DIR = os.environ.get("LAB_DIR", "solution")
SOURCE = HERE / LAB_DIR
BIN = HERE / "build" / LAB_DIR


def run(name, stdin=""):
    return subprocess.run([str(BIN / name)], input=stdin.encode(), capture_output=True, timeout=20)


def answers():
    values = {}
    for line in (SOURCE / "answers.txt").read_text(encoding="utf-8").splitlines():
        if "=" in line and not line.startswith("#"):
            key, value = line.split("=", 1)
            # Se aceptan mayúsculas, espacios y el prefijo 0x: c3 b1, C3B1, 0xca.
            values[key.strip()] = value.lower().replace(" ", "").replace("0x", "")
    return values


class Activity1(unittest.TestCase):
    def test_by_hand(self):
        self.assertEqual(answers().get("binario_202"), "11001010")
        self.assertEqual(answers().get("hexadecimal_202"), "ca")

    def test_program_uses_percent_x(self):
        self.assertRegex((SOURCE / "hex_202.c").read_text(encoding="utf-8"), r"%[0-9]*[xX]")
        self.assertEqual(run("hex_202").stdout.decode().strip().lower(), "ca")


class Activity2(unittest.TestCase):
    def test_normal_value(self):
        lines = run("int_overflow", "41\n").stdout.decode().strip().splitlines()
        self.assertTrue(lines, "el programa no escribió nada")
        self.assertEqual(lines[-1], "42")

    def test_overflow_message_and_line(self):
        stderr = run("int_overflow", "2147483647\n").stderr.decode()
        found = re.search(r"int_overflow\.c:(\d+):\d+: runtime error: signed integer overflow", stderr)
        self.assertIsNotNone(found, "no apareció el mensaje de overflow")
        self.assertEqual(answers().get("linea_overflow"), found.group(1))


class Activity3(unittest.TestCase):
    def test_twenty_decimals(self):
        self.assertIn("0.30000000000000004441", run("float_print").stdout.decode())


class Activity4(unittest.TestCase):
    def test_year_file(self):
        path = SOURCE / "year.txt"
        self.assertTrue(path.exists(), f"falta crear {LAB_DIR}/year.txt")
        data = path.read_bytes()
        self.assertIn(data, (b"a\xc3\xb1o\n", b"a\xc3\xb1o"), "year.txt debe contener año, en UTF-8")
        self.assertEqual(answers().get("bytes_year"), str(len(data)))

    def test_enye_bytes(self):
        self.assertEqual(answers().get("bytes_enye"), "c3b1")


if __name__ == "__main__":
    unittest.main()

Laboratorio

Archivos de partida del laboratorio de la semana.

lab/answers.txtDescargar lab/answers.txt
# Laboratorio 3: completen cada línea después del signo =.
# Primero háganlo a mano y luego compruébenlo con lo que escriben sus programas.

# Actividad 1: 202 en binario (8 bits) y en hexadecimal (2 dígitos).
binario_202 =
hexadecimal_202 =

# Actividad 2: número de línea de int_overflow.c que indica el mensaje runtime error.
linea_overflow =

# Actividad 4: tamaño en bytes de year.txt y los dos bytes de la ñ en hexadecimal.
bytes_year =
bytes_enye =
lab/float_print.cDescargar lab/float_print.c
// Actividad 3: escriban 0.1 + 0.2 con 20 decimales.
#include <stdio.h>

int main(void) {
    // Completen aquí. Guarden la suma en un double y usen %.20f.
    return 0;
}
lab/hex_202.cDescargar lab/hex_202.c
// Actividad 1: escriban 202 en hexadecimal con printf y %x.
#include <stdio.h>

int main(void) {
    // Completen aquí. La salida debe ser una sola línea, con el número en hexadecimal.
    return 0;
}
lab/int_overflow.cDescargar lab/int_overflow.c
// Actividad 2: lean un int con scanf, súmenle 1 y escriban el resultado.
#include <stdio.h>

int main(void) {
    // Completen aquí. Con la entrada 41, la última línea de la salida debe ser 42.
    return 0;
}

Compilación

Cómo se compilan y ejecutan los ejemplos.

MakefileDescargar Makefile
# Semana 3: bits y representación de datos. Se usa dentro del entorno del curso.
#
#   make           compila los programas de la clase en build/
#   make lab       compila su laboratorio (lab/*.c) en build/lab/
#   make test      corre las pruebas de los ejemplos y, si existe solution/, las del laboratorio
#   make test-lab  prueba su laboratorio (lab/); falla hasta que lo terminen
#   make clean     borra lo generado

CC = gcc
CFLAGS = -Wall -Wextra -std=c17 -g -fsanitize=address,undefined
LDLIBS = -lm
BUILD = build
PROGRAMS = to_binary from_binary bases sizes twos_complement overflow unsigned_wrap \
           float_sum fraction_to_binary float_limits compare_doubles money char_codes char_sign \
           read_bytes utf8_two_bytes
LAB_PROGRAMS = hex_202 int_overflow float_print

.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
	python3 -m unittest -v test_examples
	@if [ -d solution ]; then \
	  $(MAKE) --no-print-directory $(LAB_PROGRAMS:%=$(BUILD)/solution/%) && \
	  LAB_DIR=solution python3 -m unittest -v test_lab; fi

test-lab: lab
	LAB_DIR=lab python3 -m unittest -v test_lab

clean:
	rm -rf $(BUILD) __pycache__