// Copyright (C) 2020-2024 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this library; see the file COPYING3. If not see
// .
#ifndef ULP_H
#define ULP_H
#include
#include
#include
#include
namespace vir {
namespace test {
template
R
value_type_impl(int);
template
T
value_type_impl(float);
template
using value_type_t = decltype(value_type_impl(int()));
template
inline T
ulp_distance(const T& val_, const T& ref_)
{
if constexpr (std::is_floating_point_v>)
{
const int fp_exceptions = std::fetestexcept(FE_ALL_EXCEPT);
T val = val_;
T ref = ref_;
T diff = T();
using std::abs;
using std::fpclassify;
using std::frexp;
using std::isnan;
using std::isinf;
using std::ldexp;
using std::max;
using std::experimental::where;
using TT = value_type_t;
where(ref == 0, val) = abs(val);
where(ref == 0, diff) = 1;
where(ref == 0, ref) = std::__norm_min_v;
where(isinf(ref) && ref == val, ref)
= 0; // where(val_ == ref_) = 0 below will fix it up
where(val == 0, ref) = abs(ref);
where(val == 0, diff) += 1;
where(val == 0, val) = std::__norm_min_v;
using I = decltype(fpclassify(std::declval()));
I exp = {};
frexp(ref, &exp);
// lower bound for exp must be min_exponent to scale the resulting
// difference from a denormal correctly
exp = max(exp, I(std::__min_exponent_v));
diff += ldexp(abs(ref - val), std::__digits_v - exp);
where(val_ == ref_ || (isnan(val_) && isnan(ref_)), diff) = T();
std::feclearexcept(FE_ALL_EXCEPT ^ fp_exceptions);
return diff;
}
else
{
if (val_ > ref_)
return val_ - ref_;
else
return ref_ - val_;
}
}
template
inline T
ulp_distance_signed(const T& _val, const T& _ref)
{
using std::copysign;
return copysign(ulp_distance(_val, _ref), _val - _ref);
}
} // namespace test
} // namespace vir
#endif // ULP_H