3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-05-16 23:25:36 +00:00

Fix soundness bug in fpa2bv_converter::mk_to_real for negative exponents

The mk_to_real function in fpa2bv_converter.cpp incorrectly computed the
exponent scaling factor for floats with negative exponents. The code was
computing 2^(1/|exp|) instead of 1/(2^|exp|) = 2^(-|exp|).

Root cause: after the loop that computed exp2 = |unbiased_exp| as an integer,
the code did:
  one_div_exp2 = 1/exp2        (computed 1/|exp|, NOT 1/2^|exp|)
  exp2 = ite(exp_is_neg, one_div_exp2, exp2)
  two_exp2 = mk_power(two, exp2)  -> gives 2^(1/|exp|) for negative exp

For example, for exp=-3 (as in Float32(0xBE7BE653)):
  OLD (buggy): two_exp2 = 2^(1/3) ≈ 1.26 (wrong)
  NEW (fixed):  two_exp2 = 1/8 (correct: 2^(-3) = 1/8)

Fix: compute two_exp2 = 2^|exp| first, then use 1/two_exp2 for negative exponents:
  two_exp2 = mk_power(two, exp2)       (2^|exp|)
  one_div_two_exp2 = 1/two_exp2        (1/2^|exp| = 2^(-|exp|))
  two_exp2 = ite(exp_is_neg, one_div_two_exp2, two_exp2)

This bug affected:
1. QF_FPLRA: to_fp(RTZ, r) for symbolic real r in an interval containing
   the exact rational value of a float returned UNSAT instead of SAT
2. fp.to_real: incorrect results for floats with negative exponents,
   including denormals (which had the same issue via the lz subtraction)

Adds regression test in src/test/fpa.cpp for the to_fp (from real) case.

Agent-Logs-Url: https://github.com/Z3Prover/z3/sessions/56265d81-608a-4768-8c4d-30bd740d89fb

Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-05-13 00:11:00 +00:00 committed by GitHub
parent 4a14a67b0e
commit a26a0da1dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 28 additions and 7 deletions

View file

@ -2976,13 +2976,11 @@ void fpa2bv_converter::mk_to_real(func_decl * f, unsigned num, expr * const * ar
prev_bit = bit;
}
expr_ref one_div_exp2(m);
one_div_exp2 = m_arith_util.mk_div(one, exp2);
exp2 = m.mk_ite(exp_is_neg, one_div_exp2, exp2);
dbg_decouple("fpa2bv_to_real_exp2", exp2);
expr_ref res(m), two_exp2(m), minus_res(m), sgn_is_1(m);
expr_ref res(m), two_exp2(m), one_div_two_exp2(m), minus_res(m), sgn_is_1(m);
two_exp2 = m_arith_util.mk_power(two, exp2);
one_div_two_exp2 = m_arith_util.mk_div(one, two_exp2);
two_exp2 = m.mk_ite(exp_is_neg, one_div_two_exp2, two_exp2);
dbg_decouple("fpa2bv_to_real_exp2", two_exp2);
res = m_arith_util.mk_mul(rsig, two_exp2);
minus_res = m_arith_util.mk_uminus(res);
sgn_is_1 = m.mk_eq(sgn, bv1);
@ -2990,7 +2988,7 @@ void fpa2bv_converter::mk_to_real(func_decl * f, unsigned num, expr * const * ar
dbg_decouple("fpa2bv_to_real_sig_times_exp2", res);
TRACE(fpa2bv_to_real, tout << "rsig = " << mk_ismt2_pp(rsig, m) << std::endl;
tout << "exp2 = " << mk_ismt2_pp(exp2, m) << std::endl;);
tout << "two_exp2 = " << mk_ismt2_pp(two_exp2, m) << std::endl;);
expr_ref unspec(m);
mk_to_real_unspecified(f, num, args, unspec);