Qué pasa cuando un número desborda

Un entero de anchura fija no tiene sitio por encima de su máximo, así que sumarle uno produce el mínimo. Un contador de ocho bits sin signo pasa de 255 a 0; uno con signo pasa de 127 a −128. No se lanza ninguna excepción y por defecto ninguna señal llega al programa, y eso es lo que convierte el desbordamiento en una clase de error y no en un mensaje: la aritmética se equivoca en silencio y todo lo que viene después se lo cree.

La codificación que lo provoca es el complemento a dos, y esto no es un defecto suyo. El desbordamiento es consecuencia directa de la propiedad que la hace valiosa, que es que sumar y restar necesitan un solo circuito.

¿Qué hace exactamente el desbordamiento?

Descarta los bits que no cabían. La suma procede con normalidad, el resultado necesita un bit más de los que tiene el tipo, y ese bit se pierde, lo que en términos modulares significa que la aritmética se hace módulo 2 elevado a la anchura.

Anchura Sin signo Con signo
8 bits 0 a 255 −128 a 127
16 bits 0 a 65.535 −32.768 a 32.767
32 bits 0 a 4.294.967.295 −2.147.483.648 a 2.147.483.647
64 bits 0 a unos 1,8 × 10^19 de unos −9,2 × 10^18 a 9,2 × 10^18

Las filas con signo son las que conviene memorizar, porque 2.147.483.647 aparece constantemente. Es el mayor valor que cabe en un entero con signo de 32 bits, y una cantidad sorprendente de software sigue guardando ahí recuentos, identificadores y marcas de tiempo.

¿Por qué el desbordamiento con signo es distinto?

En C y C++ los dos no solo difieren en el resultado sino en su estatus. El desbordamiento sin signo está definido como envolvente y un programa puede confiar en él. El desbordamiento con signo es comportamiento indefinido, lo que significa que el compilador puede suponer que nunca ocurre.

Esa suposición tiene dientes. Una comprobación escrita como if (x + 1 < x) para detectar el desbordamiento puede ser eliminada por completo por un optimizador, razonando que el desbordamiento con signo no puede ocurrir y que la condición es siempre falsa. La prueba desaparece, la compilación es correcta según el estándar, y la protección ya no está.

La forma fiable comprueba los operandos en lugar del resultado, o usa las funciones con control de desbordamiento que ofrecen los compiladores principales. Rust comprueba en compilaciones de depuración y envuelve en las de publicación, y Python esquiva la pregunta pasando a precisión arbitraria.

¿Dónde es una trampa el rango asimétrico?

En el único valor sin contrapartida positiva. Negar el número más negativo no puede dar nada representable, así que se da a sí mismo: la negación de −2.147.483.648 en 32 bits es −2.147.483.648.

Eso rompe la suposición de que un valor absoluto nunca es negativo, que es justo en lo que se apoya una comprobación. Una rutina que toma la magnitud de una diferencia y espera un resultado no negativo recibe uno negativo para una sola entrada, y acto seguido indexa un array con él.

La división tiene el mismo borde. Dividir el mínimo entre −1 desborda por la misma razón, y en varios procesadores provoca una excepción de hardware en lugar de envolver.

¿Qué fallos reales fueron esto?

Los casos bien documentados merecen conocerse porque todos eran baratos de evitar y caros de sufrir. El vuelo 501 de Ariane 5 se perdió cuando un valor de coma flotante de 64 bits se convirtió en un entero con signo de 16 bits que no podía contenerlo, en código heredado de un cohete más lento donde ese valor nunca crecía tanto.

En el Boeing 787 una directiva de aeronavegabilidad obligó a un apagado periódico porque un contador de centésimas de segundo en un entero con signo de 32 bits desbordaba a los 248 días y ponía las unidades de control de los generadores en modo seguro a la vez.

El problema del año 2038 es la misma forma con fecha conocida: una cuenta con signo de 32 bits de segundos desde 1970 se agota en enero de 2038. El arreglo es un tipo más ancho, y el trabajo consiste en encontrar cada sitio donde se guardó el estrecho.

¿Cómo se detecta antes de publicar?

Haciendo que la máquina proteste. Los desinfectantes incorporados en GCC y Clang atrapan el desbordamiento con signo en ejecución, y activar uno para una batería de pruebas suele ser un día de trabajo con un resultado permanente.

Elegir el tipo a conciencia es la otra mitad. Un recuento que plausiblemente pueda pasar de dos mil millones pertenece a un tipo de 64 bits desde el principio, y lo mismo vale para un identificador que otros sistemas van a guardar y leer.

Cuando un valor debe saturarse en lugar de envolver, como las muestras de audio o los canales de un píxel, ese comportamiento hay que escribirlo, porque ningún tipo entero lo regala.

Preguntas que hace la gente

¿Sabe el procesador que ocurrió? Sí. El hardware activa un indicador de desbordamiento, y si tu programa no lo ve es porque la mayoría de los lenguajes no lo consultan tras cada operación.

¿Se quiere envolver alguna vez? A menudo. Los hashes, las sumas de verificación y los números de secuencia cíclicos dependen de la aritmética modular, y por eso el comportamiento sin signo está definido.

¿Los tipos de 64 bits terminan el problema? Para contadores y marcas de tiempo, en la práctica sí. Para productos y acumulaciones no, porque multiplicar dos valores grandes desborda 64 bits igual de bien.

¿Y JavaScript? Los números son de coma flotante, así que la aritmética normal no envuelve; por encima de 2^53 pierde precisión, que es todavía más silencioso. Los operadores de bits son la excepción y convierten antes a 32 bits con signo.

El desbordamiento es aritmética haciendo exactamente lo que dice la codificación, sobre un valor que nadie esperaba que llegase al borde. La calculadora de complemento a dos enseña cómo se ve un valor en cada anchura y dónde están sus límites, y el conversor de bases es la vía rápida para ver qué hicieron los bits.