En general, la operación módulo es una operación aritmética usada para obtener el residuo en una división. Usualmente, el valor de interés en una división es el cociente, sin embargo, obtener el residuo puede ser útil para sacar conclusiones sobre el dividendo y el divisor de la operación. Lo típico al usar la operación módulo es que sus operandos sean números enteros positivos, pero no está limitada a ellos, pues se puede aplicar también sobre números negativos, y racionales de punto flotante (float) y de doble precisión (double).

El operador %

En C++ se utiliza el operator% el cual es un operador aritmético, binario, multiplicativo, sobrecargable, y de alta precedencia1. Tiene la misma precedencia que la multiplicación y la división.

Pero tantos adjetivos calificativos pueden llegar a confundir. Al final del día ¿Para qué sirve la operación módulo? El resultado de la operación puede indicar si el divisor es múltiplo del dividendo, es decir, que el dividendo es divisible por el divisor. Si dividendo % divisor == 0.

En código

Para determinar los números de una colección que son o no son múltiplos de 5:

#include <cmath> // Contiene el operator%
#include <print> // Para std::println desde -std=c++26
#include <vector>

int main() {
  std::int32_t n = 5;
  std::vector numeros{2, 4, 6, 8, 10, 15, 20, 42};

  auto esMultiplo = 
      [](std::int32_t a, std::int32_t n)
      {
        return (a % n == 0);
      };

  for (const auto &numero : numeros)
    std::println("{} es múltiplo de {}?: {:<}", 
                 numero,
                 n,
                 esMultiplo(numero, n));
}

El resultado imprime

 2 es múltiplo de 5?: false
 4 es múltiplo de 5?: false
 6 es múltiplo de 5?: false
 8 es múltiplo de 5?: false
10 es múltiplo de 5?:  true
15 es múltiplo de 5?:  true
20 es múltiplo de 5?:  true
42 es múltiplo de 5?: false

Para imprimir solamente los números que sí son múltiplos de 5:

#include <algorithm>
#include <cmath>
#include <print>
#include <vector>

//...
  std::int32_t n = 5;
  std::vector numeros{2, 4, 6, 8, 10, 15, 20, 22};

  std::print("Múltiplos de {}: ", n);

  auto imprimeMultiplosDeN = 
    [=](int a)
    {
      if(a % n == 0) 
        std::print("{} ", a);
    };

  std::ranges::for_each(numeros, imprimeMultiplosDeN);
//..

El resultado imprime

Múltiplos de 5: 10 15 20

Es bisiesto

Pero de nuevo, ¿Para qué nos sirve conocer si un número es divisible por 5? Bueno, una aplicación sencilla del uso del módulo es para conocer si el año ingresado a una función es bisiesto o no. Lo es cuando el año es divisible por 4, pero no es divisible por 100, a menos que sí sea divisible por 400:

#include <cmath>
#include <print>

bool esBisiesto(const std::int32_t& year)
{
    return ( ((year % 4 == 0) && (year % 100 != 0)) 
           || (year % 400 == 0) );
}

int main()
{
  const std::int32_t year = 2024;
  const bool result = esBisiesto(year);
  std::println("El año {} es bisiesto?: {}", year, result);
}
El año 2024 es bisiesto?: true

Otro ejemplo

El escenario es el siguiente: se quiere dibujar en la pantalla una matriz de celdas (tiles) de tamaño columnas * filas. Las celdas están distribuidas en filas y columnas, y se almacenan en un arreglo bidimensional. Al presionar sobre una de las celdas ella cambia de color.

matriz<columnas, filas> celdas; // matriz[x][y]

Suponiendo que las celdas tienen un tamaño de 64 píxeles cada una, un punto en la pantalla no corresponde necesariamente a una fila y una columna sino un lugar dentro de una celda. Al dar clic dentro de una de las celdas la posición será un punto (x,y) con las coordenadas.

Para identificar el índice columna-fila en el que se encuentra la celda dentro de la matriz se puede utilizar la operación módulo de la siguiente manera:

//...
  Point point{179, 167};//La posición en pantalla al hacer clic

  const std::size_t tile_size = 64;

  const auto& [x, y]    = point;
  const std::size_t col = (x - (x % tile_size)) / tile_size;
  const std::size_t row = (y - (y % tile_size)) / tile_size;

  std::println("Punto   : ({},{}) px ", x, y);
  std::println("Posición: ({},{}) ", col, row);
//...

Resultando en

Punto   : (179,167) px
Posición: (2,2)

Lo cual se puede visualizar así:

tiles-click

El racionamiento detrás de esto es que el módulo entre la posición y el tamaño de la celda es la diferencia entre el punto de origen de la celda y el punto de clic dentro de esa celda.

columna = (x - (x % tamaño_celda)) / tamaño_celda;

diferencia          : 179 % 4 = 51
posicion_normalizada: 179 - 51 = 128
columna             : 128 / 64 = 2

Al normalizar la posición con relación a las celdas (128, 128) basta con dividirla entre el tamaño de la celda para encontrar el índice de la columna y fila a la que corresponde (2, 2), con las siguientes consideraciones:

  • El tamaño de la ventana está en píxeles
  • El origen de la pantalla (0,0) está en la esquina superior izquierda
  • El origen de cada celda también está en su esquina superior izquierda
  • La primera celda comienza en el índice (0,0): columna cero, fila cero.
  • Las colecciones en C++ comienzan en el índice cero.

Enteros negativos

En la biblioteca estándar existe la función matemática para la división std::div la cual realiza el cómputo del cociente y el residuo. Esta función permite encontrar el módulo para números negativos.

//...
const auto result = std::div(7, -2);
std::println("cociente {}, residuo {}", result.quot, result.rem);
// cociente -3, residuo 1

Pues (-2 * -3) + 1 = 7

Signal 8: SIGFPE

La operación módulo es similar a la división en cuanto a que si se intenta dividir por cero el programa emitirá una señal, SIGFPE: Signal floating-point exception2. Igual sucede si se intenta calcular el módulo sobre cero.

  //...
  int m = 2 / 0;
  //...
warning: division by zero [-Wdiv-by-zero]
7 |     int m = 2 / 0;
|             ~~^~~

Program returned: 136
Program stderr

Program terminated with signal: SIGFPE

Números racionales

Al intentar usar el operador sobre números de punto-flotante se genera un error:

float a = 4.0F;
float b = 2.0F;
float remainder = a % b;
// error: invalid operands of types 'float' and 'float' 
// to binary 'operator%'

Para poder trabajar con números de punto flotante existen las funciones std::remainder y std::fmod. Estas dos funciones difieren en la precisión con la que se calcula el cociente para hallar el residuo. La ecuación para calcular el módulo es la siguiente:

rem = x % y
rem = x - quo * y
Donde
quo = x / y
Siendo quo = quotient = cociente

El std::remainder redondea el cociente al entero más cercano, mientras que el std::fmod lo trunca.

float a = 5.1;
float b = 3.0;

float rem = std::remainder(a, b);
float fmod = std::fmod(a, b);

std::println("{:<10} of {} % {} = {}", "remainder", a, b, rem);
std::println("{:<10} of {} % {} = {}", "fmod", a, b, fmod);

Resultando en:

remainder  of 5.1 % 3.0 = -0.9000001
fmod       of 5.1 % 3.0 = 2.1

Siendo quo = 5.1 / 3.0 = 1.6999999

Entonces rem = x - quo * y

rem = 5.1 - (1.6999999 * 3.0) // redondea 1.69 a 2.0
rem = 5.1 - (2.0 * 3.0)
rem = 5.1 - 6.0
rem = -0.90

fmod = 5.1 - (1.6999999 * 3.0) // trunca 1.69 a 1.0
fmod = 5.1 - (1.0 * 3.0)
fmod = 5.1 - 3.0
fmod = 2.1

Fuentes


Deja un comentario