Skip to content

Commit 4220ade

Browse files
Simplify (x * n) % n
For unbounded integer/natural types, (x * n) % n is always zero. For bitvector types, this only holds when n divides 2^width (i.e., n is a power of two that fits in the bit-width), because otherwise the multiplication may wrap and the remainder is no longer zero. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
1 parent cadd0a6 commit 4220ade

File tree

2 files changed

+70
-0
lines changed

2 files changed

+70
-0
lines changed

src/util/simplify_expr_int.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,41 @@ simplify_exprt::resultt<> simplify_exprt::simplify_mod(const mod_exprt &expr)
397397
mp_integer result = *int_value0 % *int_value1;
398398
return from_integer(result, expr.type());
399399
}
400+
401+
// (x * n) % n is zero for mathematical integers (regardless of
402+
// sign, since n always divides x * n). For bitvector types,
403+
// multiplication wraps modulo 2^width, so the identity only holds
404+
// when n divides 2^width — this is both necessary and sufficient,
405+
// and is equivalent to n being a power of two within the width.
406+
if(int_value1.has_value() && *int_value1 != 0)
407+
{
408+
const auto &type_id = expr.type().id();
409+
bool can_simplify = (type_id == ID_integer || type_id == ID_natural);
410+
411+
if(
412+
!can_simplify &&
413+
(type_id == ID_unsignedbv || type_id == ID_signedbv) &&
414+
*int_value1 > 0)
415+
{
416+
const auto width = to_bitvector_type(expr.type()).get_width();
417+
can_simplify = (power(2, width) % *int_value1 == 0);
418+
}
419+
420+
if(
421+
can_simplify && expr.op0().id() == ID_mult &&
422+
expr.op0().operands().size() == 2)
423+
{
424+
const auto &mul = to_mult_expr(expr.op0());
425+
const auto c0 = numeric_cast<mp_integer>(mul.op0());
426+
const auto c1 = numeric_cast<mp_integer>(mul.op1());
427+
if(
428+
(c0.has_value() && *c0 == *int_value1) ||
429+
(c1.has_value() && *c1 == *int_value1))
430+
{
431+
return from_integer(0, expr.type());
432+
}
433+
}
434+
}
400435
}
401436
}
402437

unit/util/simplify_expr.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,3 +652,38 @@ TEST_CASE("Simplify quantifier", "[core][util]")
652652
REQUIRE(simplify_expr(forall_exprt{a, true_exprt{}}, ns) == true_exprt{});
653653
}
654654
}
655+
656+
TEST_CASE("Simplify (x * n) % n", "[core][util]")
657+
{
658+
const symbol_tablet symbol_table;
659+
const namespacet ns{symbol_table};
660+
661+
SECTION("Integer type: always simplifies")
662+
{
663+
const integer_typet int_type;
664+
const symbol_exprt x{"x", int_type};
665+
const auto n = from_integer(7, int_type);
666+
REQUIRE(
667+
simplify_expr(mod_exprt{mult_exprt{x, n}, n}, ns) ==
668+
from_integer(0, int_type));
669+
}
670+
671+
SECTION("Unsigned bitvector with power-of-two modulus: simplifies")
672+
{
673+
const unsignedbv_typet u32{32};
674+
const symbol_exprt x{"x", u32};
675+
const auto n = from_integer(8, u32);
676+
REQUIRE(
677+
simplify_expr(mod_exprt{mult_exprt{x, n}, n}, ns) ==
678+
from_integer(0, u32));
679+
}
680+
681+
SECTION("Unsigned bitvector with non-power-of-two modulus: no simplification")
682+
{
683+
const unsignedbv_typet u32{32};
684+
const symbol_exprt x{"x", u32};
685+
const auto n = from_integer(7, u32);
686+
const mod_exprt mod{mult_exprt{x, n}, n};
687+
REQUIRE(simplify_expr(mod, ns) == mod);
688+
}
689+
}

0 commit comments

Comments
 (0)