En lógica proposicional, a menudo es necesario transformar una fórmula en otra equivalente para simplificarla, hacerla más legible o adaptarla a un contexto específico. Estas transformaciones se rigen por un conjunto de leyes o reglas que constituyen las propiedades fundamentales del álgebra booleana, la misma matemática que gobierna las condiciones de los programas informáticos.
Dominar estas propiedades es esencial para cualquier programador: permiten simplificar condiciones complejas en sentencias if, while y for, eliminar redundancias, y escribir código más limpio y eficiente. En este artículo estudiaremos cada ley con su formulación teórica, la demostraremos mediante tablas de verdad y la aplicaremos directamente en Java con ejemplos prácticos.
📖 ¿Qué son las propiedades lógicas?
Las propiedades de la lógica proposicional (también llamadas leyes del álgebra booleana) son equivalencias matemáticas que permiten reescribir una expresión lógica en otra forma sin alterar su valor de verdad. Así como en aritmética sabemos que a × (b + c) = a×b + a×c, en lógica existen reglas análogas para los operadores AND, OR y NOT.
Estas leyes fueron formalizadas por George Boole en su obra The Laws of Thought (1854) y complementadas por Augustus De Morgan. Hoy constituyen la base teórica de los circuitos digitales, los compiladores y la optimización de código.
📊 Tabla resumen de todas las leyes
La siguiente tabla recopila todas las propiedades fundamentales como referencia rápida. Cada una se desarrolla en detalle en las secciones siguientes:
| Ley | Forma con AND | Forma con OR |
|---|---|---|
| Complemento | p ∧ ¬p = 0 | p ∨ ¬p = 1 |
| Idempotencia | p ∧ p = p | p ∨ p = p |
| Identidad | p ∧ 1 = p / p ∧ 0 = 0 | p ∨ 0 = p / p ∨ 1 = 1 |
| Doble negación | ¬(¬p) = p | |
| Asociación | (p ∧ q) ∧ r = p ∧ (q ∧ r) | (p ∨ q) ∨ r = p ∨ (q ∨ r) |
| Distribución | p ∧ (q ∨ r) = (p∧q) ∨ (p∧r) | p ∨ (q ∧ r) = (p∨q) ∧ (p∨r) |
| Absorción | p ∧ (p ∨ q) = p | p ∨ (p ∧ q) = p |
| De Morgan | ¬(p ∧ q) = ¬p ∨ ¬q | ¬(p ∨ q) = ¬p ∧ ¬q |
🔄 Ley del complemento
La ley del complemento establece que una proposición combinada con su propia negación produce resultados predecibles: la conjunción siempre es falsa y la disyunción siempre es verdadera.
| Propiedad | Fórmula | En Java | Resultado |
|---|---|---|---|
| Complemento AND | p ∧ ¬p = 0 | x && !x | Siempre false |
| Complemento OR | p ∨ ¬p = 1 | x || !x | Siempre true |
| NOT falso | ¬0 = 1 | !false | true |
| NOT verdadero | ¬1 = 0 | !true | false |
if (activo && !activo), sabrás instantáneamente que nunca se ejecutará (es una contradicción). Y if (activo || !activo) siempre se ejecutará (es una tautología).🔁 Ley de idempotencia
La idempotencia indica que aplicar un operador lógico a un valor consigo mismo no altera el resultado:
| Fórmula | En Java | Simplificación |
|---|---|---|
| p ∧ p = p | (x > 5) && (x > 5) | x > 5 |
| p ∨ p = p | (x > 5) || (x > 5) | x > 5 |
Aunque parece trivial, esta ley es útil para detectar condiciones redundantes en código. Si tras refactorizar un programa encuentras una condición repetida, puedes eliminar el duplicado con total seguridad.
🎯 Ley de identidad
La ley de identidad define cómo se comportan los operadores cuando uno de los operandos es una constante (verdadero o falso):
| Fórmula | Resultado | Explicación |
|---|---|---|
| p ∧ 1 = p | El valor de p | AND con verdadero no altera p |
| p ∧ 0 = 0 | Siempre falso | AND con falso anula todo |
| p ∨ 0 = p | El valor de p | OR con falso no altera p |
| p ∨ 1 = 1 | Siempre verdadero | OR con verdadero absorbe todo |
boolean activo = true; // Identidad AND: el true no aporta nada boolean r1 = activo && true; // equivale a: activo boolean r2 = activo && false; // equivale a: false (siempre) // Identidad OR: el false no aporta nada boolean r3 = activo || false; // equivale a: activo boolean r4 = activo || true; // equivale a: true (siempre) System.out.println(r1 + ", " + r2 + ", " + r3 + ", " + r4); // true, false, true, true
🔃 Ley de doble negación
La doble negación establece que negar dos veces una proposición devuelve su valor original: ¬(¬p) = p. En Java: !!x equivale a x.
boolean p = true;
System.out.println(!!p); // true (doble negación = original)
boolean q = false;
System.out.println(!!q); // false
// ❌ Código redundante:
if (!!esValido) { ... }
// ✅ Simplificado:
if (esValido) { ... }
if (!(!condicion)). Siempre se pueden simplificar a if (condicion).🔗 Ley de asociación
La ley de asociación permite reagrupar los operandos sin cambiar el resultado, siempre que el operador sea el mismo:
| Fórmula | En Java |
|---|---|
| (p ∧ q) ∧ r = p ∧ (q ∧ r) | (a && b) && c == a && (b && c) |
| (p ∨ q) ∨ r = p ∨ (q ∨ r) | (a || b) || c == a || (b || c) |
En la práctica, esto significa que en una cadena de AND o de OR puedes agrupar los términos como prefieras, por ejemplo para mejorar la legibilidad. Java ya evalúa de izquierda a derecha con cortocircuito, pero conceptualmente los paréntesis no afectan al resultado lógico.
📦 Ley de distribución
La distribución permite «repartir» un operador sobre otro, de forma análoga a la propiedad distributiva de la multiplicación sobre la suma:
| Fórmula | Equivalente en Java |
|---|---|
| p ∧ (q ∨ r) = (p∧q) ∨ (p∧r) | a && (b || c) == (a && b) || (a && c) |
| p ∨ (q ∧ r) = (p∨q) ∧ (p∨r) | a || (b && c) == (a || b) && (a || c) |
// Ejemplo: descuento si es cliente VIP Y (compra > 100 O tiene cupón) boolean esVIP = true; boolean compraGrande = false; boolean tieneCupon = true; // Forma compacta (distribución sin expandir) boolean descuento1 = esVIP && (compraGrande || tieneCupon); // Forma expandida (distribución aplicada) boolean descuento2 = (esVIP && compraGrande) || (esVIP && tieneCupon); System.out.println(descuento1 == descuento2); // true (equivalentes)
🧹 Ley de absorción
La absorción es quizás la ley más poderosa para simplificar código, ya que elimina términos completos que resultan redundantes:
| Fórmula | Simplificación | Motivo |
|---|---|---|
| p ∧ (p ∨ q) = p | Solo queda p | Si p es false, todo es false. Si p es true, (p ∨ q) es true, y true ∧ true = true = p. |
| p ∨ (p ∧ q) = p | Solo queda p | Si p es true, todo es true. Si p es false, (p ∧ q) es false, y false ∨ false = false = p. |
boolean esAdmin = true;
boolean tienePermiso = false;
// ❌ Condición con absorción no simplificada
if (esAdmin && (esAdmin || tienePermiso)) {
System.out.println("Acceso");
}
// ✅ Simplificada por absorción: solo necesitamos esAdmin
if (esAdmin) {
System.out.println("Acceso");
}
🧮 Leyes de De Morgan
Las leyes de De Morgan son probablemente las propiedades más utilizadas en programación. Describen cómo se distribuye la negación sobre las operaciones AND y OR:
| Ley | Fórmula lógica | En Java |
|---|---|---|
| Primera ley | ¬(p ∧ q) = ¬p ∨ ¬q | !(a && b) == (!a || !b) |
| Segunda ley | ¬(p ∨ q) = ¬p ∧ ¬q | !(a || b) == (!a && !b) |
La regla es sencilla: al negar una expresión compuesta, se niegan todos los operandos y se cambia el operador (AND pasa a OR y viceversa).
int edad = 15;
boolean tienePermiso = false;
// Condición original: NO (es mayor de edad Y tiene permiso)
boolean original = !(edad >= 18 && tienePermiso);
// Aplicando De Morgan: es menor de edad O no tiene permiso
boolean deMorgan = (edad < 18) || !tienePermiso;
System.out.println("Original: " + original); // true
System.out.println("De Morgan: " + deMorgan); // true
System.out.println("¿Iguales? " + (original == deMorgan)); // true
&& o ||, aplica De Morgan para «distribuir» la negación. El código resultante casi siempre es más fácil de leer: !(a && b) → !a || !b.☕ Aplicación práctica en Java
El siguiente programa demuestra todas las leyes de forma sistemática, verificando que cada par de expresiones produce el mismo resultado para todas las combinaciones de valores:
public class VerificadorLeyes {
static boolean verificar2vars(String nombre,
java.util.function.BiFunction<Boolean,Boolean,Boolean> f1,
java.util.function.BiFunction<Boolean,Boolean,Boolean> f2) {
boolean[] vals = {true, false};
boolean ok = true;
for (boolean p : vals) {
for (boolean q : vals) {
if (!f1.apply(p, q).equals(f2.apply(p, q))) {
ok = false;
}
}
}
System.out.printf(" %-25s → %s%n", nombre, ok ? "✅ Verificada" : "❌ Falla");
return ok;
}
public static void main(String[] args) {
System.out.println("=== Verificación de leyes lógicas ===\n");
// Complemento
verificar2vars("Complemento AND",
(p, q) -> p && !p,
(p, q) -> false);
verificar2vars("Complemento OR",
(p, q) -> p || !p,
(p, q) -> true);
// Idempotencia
verificar2vars("Idempotencia AND",
(p, q) -> p && p,
(p, q) -> p);
verificar2vars("Idempotencia OR",
(p, q) -> p || p,
(p, q) -> p);
// Identidad
verificar2vars("Identidad AND true",
(p, q) -> p && true,
(p, q) -> p);
verificar2vars("Identidad OR false",
(p, q) -> p || false,
(p, q) -> p);
// Doble negación
verificar2vars("Doble negación",
(p, q) -> !!p,
(p, q) -> p);
// De Morgan
verificar2vars("De Morgan 1ª ley",
(p, q) -> !(p && q),
(p, q) -> !p || !q);
verificar2vars("De Morgan 2ª ley",
(p, q) -> !(p || q),
(p, q) -> !p && !q);
// Distribución
verificar2vars("Distribución AND/OR",
(p, q) -> p && (p || q), // absorción: resultado = p
(p, q) -> p);
// Absorción
verificar2vars("Absorción OR/AND",
(p, q) -> p || (p && q),
(p, q) -> p);
}
}
🔬 Demostración de equivalencias
Las leyes permiten demostrar que dos fórmulas son equivalentes mediante transformaciones paso a paso. Veamos un ejemplo clásico:
Demostrar que: ¬(p → q) ≡ p ∧ ¬q
| Paso | Expresión | Ley aplicada |
|---|---|---|
| 1 | ¬(p → q) | Punto de partida |
| 2 | ¬((¬p) ∨ q) | Definición de implicación: p→q ≡ ¬p ∨ q |
| 3 | ¬(¬p) ∧ ¬q | De Morgan: ¬(A ∨ B) = ¬A ∧ ¬B |
| 4 | p ∧ ¬q | Doble negación: ¬(¬p) = p |
✅ Partiendo de ¬(p → q) y aplicando tres leyes, llegamos a p ∧ ¬q. Queda demostrada la equivalencia.
// Verificación por tabla de verdad
boolean[] v = {true, false};
System.out.println(" p\t q\t ¬(p→q)\t p∧¬q\t ¿Iguales?");
for (boolean p : v) {
for (boolean q : v) {
boolean f1 = !(!p || q); // ¬(p → q)
boolean f2 = p && !q; // p ∧ ¬q
System.out.printf(" %s\t %s\t %s\t\t %s\t %s%n",
p, q, f1, f2, f1 == f2 ? "✓" : "✗");
}
}
// Las 4 filas muestran ✓: equivalencia demostrada
❌ Errores frecuentes
🔹 Error 1: aplicar De Morgan sin cambiar el operador
// ❌ Error: negar operandos PERO NO cambiar el operador // !(a && b) NO es igual a (!a && !b) boolean a = true, b = false; System.out.println(!(a && b)); // true System.out.println(!a && !b); // false ← ¡DIFERENTE! // ✅ Correcto: negar operandos Y cambiar el operador System.out.println(!a || !b); // true ← Ahora sí coincide
🔹 Error 2: confundir distribución con asociación
La asociación reagrupa sin cambiar operadores: (a && b) && c = a && (b && c). La distribución mezcla operadores diferentes: a && (b || c) = (a && b) || (a && c). Confundirlas lleva a transformaciones incorrectas.
🔹 Error 3: no verificar con tabla de verdad
Cuando simplificamos una condición en el código, siempre conviene verificar con las 4 (o 8) combinaciones posibles. Un solo caso mal transformado puede introducir un bug sutil y difícil de detectar.
📝 Ejercicios prácticos
Ejercicio 1: Identificar la ley aplicada
Para cada par de expresiones, indica qué ley lógica se ha aplicado:
a) !(x && y) → !x || !y
b) a && (a || b) → a
c) (p || q) || r → p || (q || r)
d) !!activo → activo
Ejercicio 2: Simplificar condiciones
Simplifica las siguientes expresiones Java aplicando las leyes lógicas apropiadas:
a) !(edad >= 18 || tienePermiso)
b) esAdmin && (esAdmin || esEditor)
c) activo && true && !(!valido)
Ejercicio 3: Demostrar una equivalencia
Demuestra que ¬(p ∨ (¬p ∧ q)) es equivalente a ¬p ∧ ¬q. Hazlo de dos formas: mediante transformaciones algebraicas (paso a paso indicando la ley aplicada) y mediante un programa Java que verifique las 4 filas de la tabla de verdad.
❓ Preguntas frecuentes sobre Propiedades de programación
Las dudas más comunes respondidas de forma clara y directa.
💬 Foro de discusión
¿Tienes dudas sobre Propiedades de programación? Comparte tu pregunta con la comunidad.
Todavía no hay mensajes. ¡Sé el primero en participar!