7namespace uni20::check::detail
9struct mpreal_ulp_result
12 long long distance = std::numeric_limits<long long>::max();
13 char const* reason =
nullptr;
20inline void mpfr_ulp_key(mpz_ptr key, mpfr_srcptr
value, mpfr_exp_t emin)
22 if (mpfr_zero_p(
value))
27 uni20::detail::mp_integer significand, minimum;
28 (void)mpfr_get_z_2exp(significand.value,
value);
29 mpz_abs(significand.value, significand.value);
30 mpz_set_si(key, mpfr_get_exp(
value));
31 mpz_set_si(minimum.value, emin);
32 mpz_sub(key, key, minimum.value);
33 mpz_sub_ui(key, key, 1);
34 mpz_mul_2exp(key, key,
static_cast<mp_bitcnt_t
>(mpfr_get_prec(
value) - 1));
35 mpz_add(key, key, significand.value);
36 mpz_add_ui(key, key, 1);
37 if (mpfr_signbit(
value)) mpz_neg(key, key);
40inline mpreal_ulp_result compare_mpfr(mpfr_srcptr a, mpfr_srcptr b, std::int64_t max_ulps)
42 if (max_ulps < 0)
return {.reason =
"negative ULP tolerance"};
43 if (mpfr_get_prec(a) != mpfr_get_prec(b))
return {.reason =
"working precisions differ"};
44 if (mpfr_nan_p(a) || mpfr_nan_p(b))
return {.reason =
"NaN is not comparable"};
45 if (mpfr_inf_p(a) || mpfr_inf_p(b))
47 if (mpfr_equal_p(a, b))
return {.equal =
true, .distance = 0};
48 return {.reason =
"infinities differ or only one operand is infinite"};
50 auto const emin = mpfr_get_emin(), emax = mpfr_get_emax();
51 auto outside_grid = [=](mpfr_srcptr
value) {
52 return !mpfr_zero_p(
value) && (mpfr_get_exp(
value) < emin || mpfr_get_exp(
value) > emax);
54 if (outside_grid(a) || outside_grid(b))
return {.reason =
"operand is outside the current MPFR exponent range"};
55 if (mpfr_equal_p(a, b))
return {.equal =
true, .distance = 0};
57 uni20::detail::mp_integer left, right, magnitude, limit;
58 mpfr_ulp_key(left.value, a, emin);
59 mpfr_ulp_key(right.value, b, emin);
60 mpz_sub(magnitude.value, right.value, left.value);
61 int const sign = mpz_sgn(magnitude.value);
62 mpz_abs(magnitude.value, magnitude.value);
63 mpz_import(limit.value, 1, 1,
sizeof(max_ulps), 0, 0, &max_ulps);
64 bool const equal = mpz_cmp(magnitude.value, limit.value) <= 0;
68 constexpr auto maximum = std::numeric_limits<long long>::max();
69 mpz_import(limit.value, 1, 1,
sizeof(maximum), 0, 0, &maximum);
70 long long distance = maximum;
71 if (mpz_cmp(magnitude.value, limit.value) < 0)
73 unsigned long long value = 0;
74 mpz_export(&
value,
nullptr, 1,
sizeof(
value), 0, 0, magnitude.value);
75 distance =
static_cast<long long>(
value);
77 return {.equal = equal, .distance = sign < 0 ? -distance : distance};
84 mpfr_flags_t saved = mpfr_flags_save();
85 ~mpfr_ulp_flags() { mpfr_flags_restore(saved, MPFR_FLAGS_ALL); }
88inline mpreal_ulp_result compare_mpreal(
mpreal const& a,
mpreal const& b, std::int64_t max_ulps)
91 if (max_ulps < 0)
return {.reason =
"negative ULP tolerance"};
92 if (!a.initialized() || !b.initialized())
return {.reason =
"unset operand"};
93 if (a.is_exact() && b.is_exact())
95 if (a == b)
return {.equal =
true, .distance = 0};
96 return {.reason =
"unequal exact values have no ULP grid"};
100 auto rounded = a.at(b.precision());
101 return compare_mpfr(rounded.native_handle(), b.native_handle(), max_ulps);
105 auto rounded = b.at(a.precision());
106 return compare_mpfr(a.native_handle(), rounded.native_handle(), max_ulps);
108 return compare_mpfr(a.native_handle(), b.native_handle(), max_ulps);