// 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