diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 31dd88078..1d5b5ce18 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -58,6 +58,7 @@ add_executable(test-z3 f2n.cpp finite_set.cpp finite_set_rewriter.cpp + fpa.cpp factor_rewriter.cpp finder.cpp fixed_bit_vector.cpp diff --git a/src/test/fpa.cpp b/src/test/fpa.cpp new file mode 100644 index 000000000..589f43bdb --- /dev/null +++ b/src/test/fpa.cpp @@ -0,0 +1,73 @@ +/*++ +Copyright (c) 2025 Microsoft Corporation + +--*/ + +// Regression tests for floating-point arithmetic encoding and model generation. + +#include "api/z3.h" +#include "util/debug.h" +#include + +// Test that fp.to_real produces correct values for denormal floating-point numbers. +// Regression test for: incorrect model with (_ FloatingPoint 2 24) and fp.to_real. +// Denormal numbers require subtracting the normalization shift (lz) from the exponent; +// without this fix, denormals in fp.to_real were ~2^lz times too large. +static void test_fp_to_real_denormal() { + Z3_context ctx; + + // Test 1: the specific denormal from the bug report (fp #b0 #b00 #b00111011011111001011101) + // has fp.to_real ~= 0.232, which must NOT be > 1.0 + ctx = Z3_mk_context(nullptr); + { + const char * spec = + "(assert (> (fp.to_real (fp #b0 #b00 #b00111011011111001011101)) 1.0))\n" + "(check-sat)\n"; + const char * result = Z3_eval_smtlib2_string(ctx, spec); + ENSURE(strstr(result, "unsat") != nullptr); + } + Z3_del_context(ctx); + + // Test 2: denormal with leading significand bit = 1, fp.to_real should be 0.5 + // (fp #b0 #b00 #b10000000000000000000000) in (_ FloatingPoint 2 24) + ctx = Z3_mk_context(nullptr); + { + const char * spec = + "(assert (= (fp.to_real (fp #b0 #b00 #b10000000000000000000000)) (/ 1.0 2.0)))\n" + "(check-sat)\n"; + const char * result = Z3_eval_smtlib2_string(ctx, spec); + ENSURE(strstr(result, "sat") != nullptr); + ENSURE(strstr(result, "unsat") == nullptr); + } + Z3_del_context(ctx); + + // Test 3: denormal with significand bit pattern giving fp.to_real = 0.125 + // (fp #b0 #b00 #b00100000000000000000000) in (_ FloatingPoint 2 24) + ctx = Z3_mk_context(nullptr); + { + const char * spec = + "(assert (= (fp.to_real (fp #b0 #b00 #b00100000000000000000000)) (/ 1.0 8.0)))\n" + "(check-sat)\n"; + const char * result = Z3_eval_smtlib2_string(ctx, spec); + ENSURE(strstr(result, "sat") != nullptr); + ENSURE(strstr(result, "unsat") == nullptr); + } + Z3_del_context(ctx); + + // Test 4: a normal value (fp #b0 #b01 #b11111111111111111111111) must be > 1.0 + // This is the maximum finite normal number in (_ FloatingPoint 2 24) + ctx = Z3_mk_context(nullptr); + { + const char * spec = + "(assert (> (fp.to_real (fp #b0 #b01 #b11111111111111111111111)) 1.0))\n" + "(check-sat)\n"; + const char * result = Z3_eval_smtlib2_string(ctx, spec); + ENSURE(strstr(result, "sat") != nullptr); + ENSURE(strstr(result, "unsat") == nullptr); + } + Z3_del_context(ctx); +} + +void tst_fpa() { + test_fp_to_real_denormal(); +} diff --git a/src/test/main.cpp b/src/test/main.cpp index a8158b99e..c5d55ebe1 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -287,4 +287,5 @@ int main(int argc, char ** argv) { TST(ho_matcher); TST(finite_set); TST(finite_set_rewriter); + TST(fpa); }