Finish P0811R3 midpoint and lerp (#1048)

* Removes workaround for missing `bit_cast` and mark `lerp` constexpr.

* Changes how `lerp` handles infinite inputs according to
  https://github.com/microsoft/STL/issues/65#issuecomment-563811523 and
  https://github.com/microsoft/STL/issues/65#issuecomment-564102550.

* Adds constexpr tests.

Co-authored-by: Stephan T. Lavavej <stl@microsoft.com>
This commit is contained in:
statementreply 2020-07-30 11:49:20 +08:00 коммит произвёл GitHub
Родитель 0e7b5d2509
Коммит e9f56a6148
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 566 добавлений и 252 удалений

Просмотреть файл

@ -12,6 +12,10 @@
#include <cstdlib>
#include <xtr1common>
#if _HAS_CXX20
#include <xutility>
#endif // _HAS_CXX20
#pragma pack(push, _CRT_PACKING)
#pragma warning(push, _STL_WARNING_LEVEL)
#pragma warning(disable : _STL_DISABLED_WARNINGS)
@ -1238,13 +1242,12 @@ _NODISCARD auto hypot(const _Ty1 _Dx, const _Ty2 _Dy, const _Ty3 _Dz) {
#if _HAS_CXX20
// FUNCTION lerp
// TRANSITION, P0553: lerp is not yet constexpr
template <class _Ty>
_NODISCARD /* constexpr */ _Ty _Common_lerp(const _Ty _ArgA, const _Ty _ArgB, const _Ty _ArgT) noexcept {
_NODISCARD constexpr _Ty _Common_lerp(const _Ty _ArgA, const _Ty _ArgB, const _Ty _ArgT) noexcept {
// on a line intersecting {(0.0, _ArgA), (1.0, _ArgB)}, return the Y value for X == _ArgT
const int _Finite_mask = (int{isfinite(_ArgA)} << 2) | (int{isfinite(_ArgB)} << 1) | int{isfinite(_ArgT)};
if (_Finite_mask == 0b111) {
const bool _T_is_finite = _STD _Is_finite(_ArgT);
if (_T_is_finite && _STD _Is_finite(_ArgA) && _STD _Is_finite(_ArgB)) {
// 99% case, put it first; this block comes from P0811R3
if ((_ArgA <= 0 && _ArgB >= 0) || (_ArgA >= 0 && _ArgB <= 0)) {
// exact, monotonic, bounded, determinate, and (for _ArgA == _ArgB == 0) consistent:
@ -1272,96 +1275,61 @@ _NODISCARD /* constexpr */ _Ty _Common_lerp(const _Ty _ArgA, const _Ty _ArgB, co
return _Candidate;
}
if (isnan(_ArgA)) {
return _ArgA;
}
if (isnan(_ArgB)) {
return _ArgB;
}
if (isnan(_ArgT)) {
return _ArgT;
}
switch (_Finite_mask) {
case 0b000:
// All values are infinities
if (_ArgT >= 1) {
return _ArgB;
}
return _ArgA;
case 0b010:
case 0b100:
case 0b110:
// _ArgT is an infinity; return infinity in the "direction" of _ArgA and _ArgB
return _ArgT * (_ArgB - _ArgA);
case 0b001:
// Here _ArgA and _ArgB are infinities
if (_ArgA == _ArgB) {
// same sign, so T doesn't matter
if (_STD is_constant_evaluated()) {
if (_STD _Is_nan(_ArgA)) {
return _ArgA;
}
// Opposite signs, choose the "infinity direction" according to T if it makes sense.
if (_ArgT <= 0) {
return _ArgA;
}
if (_ArgT >= 1) {
if (_STD _Is_nan(_ArgB)) {
return _ArgB;
}
// Interpolating between infinities of opposite signs doesn't make sense, NaN
if constexpr (sizeof(_Ty) == sizeof(float)) {
return __builtin_nanf("0");
if (_STD _Is_nan(_ArgT)) {
return _ArgT;
}
} else {
// raise FE_INVALID if at least one of _ArgA, _ArgB, and _ArgT is signaling NaN
if (_STD _Is_nan(_ArgA) || _STD _Is_nan(_ArgB)) {
return (_ArgA + _ArgB) + _ArgT;
}
if (_STD _Is_nan(_ArgT)) {
return _ArgT + _ArgT;
}
}
if (_T_is_finite) {
// _ArgT is finite, _ArgA and/or _ArgB is infinity
if (_ArgT < 0) {
// if _ArgT < 0: return infinity in the "direction" of _ArgA if that exists, NaN otherwise
return _ArgA - _ArgB;
} else if (_ArgT <= 1) {
// if _ArgT == 0: return _ArgA (infinity) if _ArgB is finite, NaN otherwise
// if 0 < _ArgT < 1: return infinity "between" _ArgA and _ArgB if that exists, NaN otherwise
// if _ArgT == 1: return _ArgB (infinity) if _ArgA is finite, NaN otherwise
return _ArgT * _ArgB + (1 - _ArgT) * _ArgA;
} else {
return __builtin_nan("0");
// if _ArgT > 1: return infinity in the "direction" of _ArgB if that exists, NaN otherwise
return _ArgB - _ArgA;
}
case 0b011:
// _ArgA is an infinity but _ArgB is not
if (_ArgT == 1) {
return _ArgB;
}
if (_ArgT < 1) {
// towards the infinity, return it
return _ArgA;
}
// away from the infinity
return -_ArgA;
case 0b101:
// _ArgA is finite and _ArgB is an infinity
if (_ArgT == 0) {
return _ArgA;
}
if (_ArgT > 0) {
// toward the infinity
return _ArgB;
}
return -_ArgB;
case 0b111: // impossible; handled in fast path
default:
_CSTD abort();
} else {
// _ArgT is an infinity; return infinity in the "direction" of _ArgA and _ArgB if that exists, NaN otherwise
return _ArgT * (_ArgB - _ArgA);
}
}
// As of 2019-06-17 it is unclear whether the "sufficient additional overloads" clause is intended to target lerp;
// LWG-3223 is pending.
_NODISCARD /* constexpr */ inline float lerp(const float _ArgA, const float _ArgB, const float _ArgT) noexcept {
_NODISCARD constexpr inline float lerp(const float _ArgA, const float _ArgB, const float _ArgT) noexcept {
return _Common_lerp(_ArgA, _ArgB, _ArgT);
}
_NODISCARD /* constexpr */ inline double lerp(const double _ArgA, const double _ArgB, const double _ArgT) noexcept {
_NODISCARD constexpr inline double lerp(const double _ArgA, const double _ArgB, const double _ArgT) noexcept {
return _Common_lerp(_ArgA, _ArgB, _ArgT);
}
_NODISCARD /* constexpr */ inline long double lerp(
_NODISCARD constexpr inline long double lerp(
const long double _ArgA, const long double _ArgB, const long double _ArgT) noexcept {
return _Common_lerp(_ArgA, _ArgB, _ArgT);
}

Просмотреть файл

@ -536,21 +536,16 @@ _CONSTEXPR20 void iota(_FwdIt _First, _FwdIt _Last, _Ty _Val) {
#if _HAS_CXX17
// FUNCTION TEMPLATE _Abs_u
template <class _Arithmetic>
_NODISCARD constexpr auto _Abs_u(const _Arithmetic _Val) noexcept {
template <class _Integral>
_NODISCARD constexpr auto _Abs_u(const _Integral _Val) noexcept {
// computes absolute value of _Val (converting to an unsigned integer type if necessary to avoid overflow
// representing the negation of the minimum value)
if constexpr (is_floating_point_v<_Arithmetic>) {
// TRANSITION, P0553: this mishandles NaNs
if (_Val < 0) {
return -_Val;
}
static_assert(is_integral_v<_Integral>);
return _Val;
} else if constexpr (is_signed_v<_Arithmetic>) {
using _Unsigned = make_unsigned_t<_Arithmetic>;
if constexpr (is_signed_v<_Integral>) {
using _Unsigned = make_unsigned_t<_Integral>;
if (_Val < 0) {
// note static_cast to _Unsigned such that _Arithmetic == short returns unsigned short rather than int
// note static_cast to _Unsigned such that _Integral == short returns unsigned short rather than int
return static_cast<_Unsigned>(_Unsigned{0} - static_cast<_Unsigned>(_Val));
}
@ -619,9 +614,24 @@ _NODISCARD constexpr common_type_t<_Mt, _Nt> lcm(const _Mt _Mx, const _Nt _Nx) n
template <class _Ty, enable_if_t<is_arithmetic_v<_Ty> && !is_same_v<remove_cv_t<_Ty>, bool>, int> = 0>
_NODISCARD constexpr _Ty midpoint(const _Ty _Val1, const _Ty _Val2) noexcept {
if constexpr (is_floating_point_v<_Ty>) {
if (_STD is_constant_evaluated()) {
if (_STD _Is_nan(_Val1)) {
return _Val1;
}
if (_STD _Is_nan(_Val2)) {
return _Val2;
}
} else {
if (_STD _Is_nan(_Val1) || _STD _Is_nan(_Val2)) {
// raise FE_INVALID if at least one of _Val1 and _Val2 is signaling NaN
return _Val1 + _Val2;
}
}
constexpr _Ty _High_limit = (numeric_limits<_Ty>::max)() / 2;
const auto _Val1_a = _Abs_u(_Val1);
const auto _Val2_a = _Abs_u(_Val2);
const auto _Val1_a = _Float_abs(_Val1);
const auto _Val2_a = _Float_abs(_Val2);
if (_Val1_a <= _High_limit && _Val2_a <= _High_limit) {
// _Val1 and _Val2 are small enough that _Val1 + _Val2 won't overflow
@ -637,22 +647,12 @@ _NODISCARD constexpr _Ty midpoint(const _Ty _Val1, const _Ty _Val2) noexcept {
return (_Val1 + _Val2) / 2;
}
// TRANSITION, P0553: the next two branches handle NaNs but don't produce correct behavior under /fp:fast or
// -fassociative-math
if (_Val1 != _Val1) {
return _Val1;
}
if (_Val2 != _Val2) {
return _Val2;
}
// Here at least one of {_Val1, _Val2} has large magnitude.
// Therefore, if one of the values is too small to divide by 2 exactly, the small magnitude is much less than
// one ULP of the result, so we can add it directly without the potentially inexact division by 2.
// In the default rounding mode this less than one ULP difference will always be rounded away, so under
// /fp:precise or /fp:fast we could avoid these tests if we had some means of detecting it in the caller.
// /fp:fast we could avoid these tests if we had some means of detecting it in the caller.
constexpr _Ty _Low_limit = (numeric_limits<_Ty>::min)() * 2;
if (_Val1_a < _Low_limit) {
return _Val1 + _Val2 / 2;

Просмотреть файл

@ -27,6 +27,12 @@ _STL_DISABLE_CLANG_WARNINGS
#define _USE_STD_VECTOR_ALGORITHMS 0
#endif
#ifdef __CUDACC__
#define _CONSTEXPR_BIT_CAST inline
#else // ^^^ workaround ^^^ / vvv no workaround vvv
#define _CONSTEXPR_BIT_CAST constexpr
#endif // ^^^ no workaround ^^^
#if _USE_STD_VECTOR_ALGORITHMS
_EXTERN_C
// The "noalias" attribute tells the compiler optimizer that pointers going into these hand-vectorized algorithms
@ -52,17 +58,15 @@ template <class _To, class _From,
enable_if_t<conjunction_v<bool_constant<sizeof(_To) == sizeof(_From)>, is_trivially_copyable<_To>,
is_trivially_copyable<_From>>,
int> = 0>
_NODISCARD _CONSTEXPR_BIT_CAST _To _Bit_cast(const _From& _Val) noexcept {
#ifdef __CUDACC__
_NODISCARD _To _Bit_cast(const _From& _Val) noexcept {
_To _To_obj; // assumes default-init
_CSTD memcpy(_STD addressof(_To_obj), _STD addressof(_Val), sizeof(_To));
return _To_obj;
}
#else // ^^^ workaround ^^^ / vvv no workaround vvv
_NODISCARD constexpr _To _Bit_cast(const _From& _Val) noexcept {
return __builtin_bit_cast(_To, _Val);
}
#endif // ^^^ no workaround ^^^
}
// STRUCT TEMPLATE _Get_first_parameter
template <class _Ty>
@ -1149,19 +1153,19 @@ struct _Iterator_traits_base<_Iter,
typename _Iter::pointer, typename _Iter::reference>> {
// defined if _Iter::* types exist
using iterator_category = typename _Iter::iterator_category;
using value_type = typename _Iter::value_type;
using difference_type = typename _Iter::difference_type;
using pointer = typename _Iter::pointer;
using reference = typename _Iter::reference;
using value_type = typename _Iter::value_type;
using difference_type = typename _Iter::difference_type;
using pointer = typename _Iter::pointer;
using reference = typename _Iter::reference;
};
template <class _Ty, bool = is_object_v<_Ty>>
struct _Iterator_traits_pointer_base { // iterator properties for pointers to object
using iterator_category = random_access_iterator_tag;
using value_type = remove_cv_t<_Ty>;
using difference_type = ptrdiff_t;
using pointer = _Ty*;
using reference = _Ty&;
using value_type = remove_cv_t<_Ty>;
using difference_type = ptrdiff_t;
using pointer = _Ty*;
using reference = _Ty&;
};
template <class _Ty>
@ -1421,8 +1425,8 @@ _NODISCARD constexpr decltype(auto) _Get_unwrapped_n(_Iter&& _It, const _Diff _O
template <class _Iter, class _Diff, enable_if_t<_Unwrappable_for_offset_v<_Iter> && is_integral_v<_Diff>, int> = 0>
_NODISCARD constexpr decltype(auto) _Get_unwrapped_n(_Iter&& _It, const _Diff _Off) {
// ask an iterator to assert that the iterator moved _Off positions is valid, and unwrap
using _IDiff = _Iter_diff_t<_Remove_cvref_t<_Iter>>;
using _CDiff = common_type_t<_Diff, _IDiff>;
using _IDiff = _Iter_diff_t<_Remove_cvref_t<_Iter>>;
using _CDiff = common_type_t<_Diff, _IDiff>;
const auto _COff = static_cast<_CDiff>(_Off);
_STL_ASSERT(_COff <= static_cast<_CDiff>(_Max_possible_v<_IDiff>)
@ -1731,7 +1735,7 @@ _CONSTEXPR17 void _Advance1(_InIt& _Where, _Diff _Off, input_iterator_tag) {
// increment iterator by offset, input iterators
_STL_ASSERT(_Off >= 0, "negative advance of non-bidirectional iterator");
decltype(auto) _UWhere = _Get_unwrapped_n(_STD move(_Where), _Off);
decltype(auto) _UWhere = _Get_unwrapped_n(_STD move(_Where), _Off);
constexpr bool _Need_rewrap = !is_reference_v<decltype(_Get_unwrapped_n(_STD move(_Where), _Off))>;
for (; 0 < _Off; --_Off) {
@ -1746,7 +1750,7 @@ _CONSTEXPR17 void _Advance1(_InIt& _Where, _Diff _Off, input_iterator_tag) {
template <class _BidIt, class _Diff>
_CONSTEXPR17 void _Advance1(_BidIt& _Where, _Diff _Off, bidirectional_iterator_tag) {
// increment iterator by offset, bidirectional iterators
decltype(auto) _UWhere = _Get_unwrapped_n(_STD move(_Where), _Off);
decltype(auto) _UWhere = _Get_unwrapped_n(_STD move(_Where), _Off);
constexpr bool _Need_rewrap = !is_reference_v<decltype(_Get_unwrapped_n(_STD move(_Where), _Off))>;
for (; 0 < _Off; --_Off) {
@ -1799,8 +1803,8 @@ template <class _InIt>
_CONSTEXPR17 _Iter_diff_t<_InIt> _Distance1(_InIt _First, _InIt _Last, input_iterator_tag) {
// return distance between iterators; input
_Adl_verify_range(_First, _Last);
auto _UFirst = _Get_unwrapped(_First);
const auto _ULast = _Get_unwrapped(_Last);
auto _UFirst = _Get_unwrapped(_First);
const auto _ULast = _Get_unwrapped(_Last);
_Iter_diff_t<_InIt> _Off = 0;
for (; _UFirst != _ULast; ++_UFirst) {
++_Off;
@ -4431,7 +4435,7 @@ _OutIt copy_n(_InIt _First, _Diff _Count_raw, _OutIt _Dest) { // copy [_First, _
const _Algorithm_int_t<_Diff> _Count = _Count_raw;
if (0 < _Count) {
auto _UFirst = _Get_unwrapped_n(_First, _Count);
auto _UDest = _Get_unwrapped_n(_Dest, _Count);
auto _UDest = _Get_unwrapped_n(_Dest, _Count);
_Seek_wrapped(
_Dest, _Copy_n_unchecked4(_UFirst, _Count, _UDest,
bool_constant<_Ptr_copy_cat<decltype(_UFirst), decltype(_UDest)>::_Trivially_copyable>{}));
@ -4518,9 +4522,9 @@ _BidIt2 _Copy_backward_unchecked(_BidIt1 _First, _BidIt1 _Last, _BidIt2 _Dest, t
template <class _BidIt1, class _BidIt2>
_BidIt2 copy_backward(_BidIt1 _First, _BidIt1 _Last, _BidIt2 _Dest) { // copy [_First, _Last) backwards to [..., _Dest)
_Adl_verify_range(_First, _Last);
auto _UFirst = _Get_unwrapped(_First);
auto _UFirst = _Get_unwrapped(_First);
const auto _ULast = _Get_unwrapped(_Last);
auto _UDest = _Get_unwrapped_n(_Dest, -_Idl_distance<_BidIt1>(_UFirst, _ULast));
auto _UDest = _Get_unwrapped_n(_Dest, -_Idl_distance<_BidIt1>(_UFirst, _ULast));
_Seek_wrapped(_Dest, _Copy_backward_unchecked(_UFirst, _ULast, _UDest,
bool_constant<_Ptr_copy_cat<decltype(_UFirst), decltype(_UDest)>::_Trivially_copyable>{}));
return _Dest;
@ -4969,7 +4973,7 @@ bool _Equal_unchecked(const _InIt1 _First1, const _InIt1 _Last1, const _InIt2 _F
// compare [_First1, _Last1) to [_First2, ...), memcmp optimization
const auto _First1_ch = reinterpret_cast<const char*>(_First1);
const auto _First2_ch = reinterpret_cast<const char*>(_First2);
const auto _Count = static_cast<size_t>(reinterpret_cast<const char*>(_Last1) - _First1_ch);
const auto _Count = static_cast<size_t>(reinterpret_cast<const char*>(_Last1) - _First1_ch);
return _CSTD memcmp(_First1_ch, _First2_ch, _Count) == 0;
}
@ -4978,7 +4982,7 @@ _NODISCARD bool equal(const _InIt1 _First1, const _InIt1 _Last1, const _InIt2 _F
// compare [_First1, _Last1) to [_First2, ...)
_Adl_verify_range(_First1, _Last1);
const auto _UFirst1 = _Get_unwrapped(_First1);
const auto _ULast1 = _Get_unwrapped(_Last1);
const auto _ULast1 = _Get_unwrapped(_Last1);
const auto _UFirst2 = _Get_unwrapped_n(_First2, _Idl_distance<_InIt1>(_UFirst1, _ULast1));
return _Equal_unchecked(_UFirst1, _ULast1, _UFirst2, _Pass_fn(_Pred));
}
@ -5867,7 +5871,53 @@ struct _CXX17_DEPRECATE_ITERATOR_BASE_CLASS iterator { // base type for iterator
using pointer = _Pointer;
using reference = _Reference;
};
// STRUCT TEMPLATE _Float_traits
template <class _Ty>
struct _Float_traits {
static_assert(is_floating_point_v<_Ty>, "_Float_traits<NonFloatingPoint> is invalid");
// traits for double and long double:
using type = unsigned long long;
static constexpr type _Magnitude_mask = 0x7fff'ffff'ffff'ffffULL;
static constexpr type _Exponent_mask = 0x7ff0'0000'0000'0000ULL;
};
template <>
struct _Float_traits<float> {
using type = unsigned int;
static constexpr type _Magnitude_mask = 0x7fff'ffffU;
static constexpr type _Exponent_mask = 0x7f80'0000U;
};
// FUNCTION TEMPLATE _Float_abs_bits
template <class _Ty, enable_if_t<is_floating_point_v<_Ty>, int> = 0>
_NODISCARD _CONSTEXPR_BIT_CAST auto _Float_abs_bits(const _Ty& _Xx) {
const auto _Bits = _Bit_cast<typename _Float_traits<_Ty>::type>(_Xx);
return _Bits & _Float_traits<_Ty>::_Magnitude_mask;
}
// FUNCTION TEMPLATE _Float_abs
template <class _Ty, enable_if_t<is_floating_point_v<_Ty>, int> = 0>
_NODISCARD _CONSTEXPR_BIT_CAST _Ty _Float_abs(const _Ty _Xx) { // constexpr floating point abs()
return _Bit_cast<_Ty>(_Float_abs_bits(_Xx));
}
// FUNCTION TEMPLATE _Is_nan
template <class _Ty, enable_if_t<is_floating_point_v<_Ty>, int> = 0>
_NODISCARD _CONSTEXPR_BIT_CAST bool _Is_nan(const _Ty _Xx) { // constexpr isnan()
return _Float_abs_bits(_Xx) > _Float_traits<_Ty>::_Exponent_mask;
}
// FUNCTION TEMPLATE _Is_finite
template <class _Ty, enable_if_t<is_floating_point_v<_Ty>, int> = 0>
_NODISCARD _CONSTEXPR_BIT_CAST bool _Is_finite(const _Ty _Xx) { // constexpr isfinite()
return _Float_abs_bits(_Xx) < _Float_traits<_Ty>::_Exponent_mask;
}
_STD_END
#undef _CONSTEXPR_BIT_CAST
#pragma pop_macro("new")
_STL_RESTORE_CLANG_WARNINGS
#pragma warning(pop)

Просмотреть файл

@ -164,7 +164,6 @@
// (partially implemented)
// P0769R2 shift_left(), shift_right()
// P0811R3 midpoint(), lerp()
// (partially implemented, lerp() not yet constexpr)
// P0879R0 constexpr For Swapping Functions
// P0887R1 type_identity
// P0896R4 Ranges
@ -1169,6 +1168,7 @@
#define __cpp_lib_generic_unordered_lookup 201811L
#define __cpp_lib_int_pow2 202002L
#define __cpp_lib_integer_comparison_functions 202002L
#define __cpp_lib_interpolate 201902L
#define __cpp_lib_is_constant_evaluated 201811L
#define __cpp_lib_is_nothrow_convertible 201806L
#define __cpp_lib_list_remove_return_type 201806L

Просмотреть файл

@ -465,10 +465,6 @@ std/utilities/variant/variant.variant/variant.ctor/T.pass.cpp FAIL
# C++20 P0768R1 "Library Support for the Spaceship (Comparison) Operator"
std/language.support/support.limits/support.limits.general/compare.version.pass.cpp FAIL
# C++20 P0811R2 "midpoint(), lerp()"
std/language.support/support.limits/support.limits.general/numeric.version.pass.cpp FAIL
std/numerics/c.math/c.math.lerp/c.math.lerp.pass.cpp FAIL
# C++20 P0896R4 "<ranges>"
std/language.support/support.limits/support.limits.general/algorithm.version.pass.cpp FAIL
std/language.support/support.limits/support.limits.general/functional.version.pass.cpp FAIL
@ -775,6 +771,11 @@ std/iterators/predef.iterators/insert.iterators/insert.iterator/types.pass.cpp F
std/numerics/complex.number/cmplx.over/conj.pass.cpp:0 FAIL
std/numerics/complex.number/cmplx.over/proj.pass.cpp:0 FAIL
# Assertion failed: (std::lerp(T(2.3), T(2.3), inf) == T(2.3))
# Asserts `(std::lerp(T(2.3), T(2.3), inf) == T(2.3))` and `std::isnan(std::lerp(T( 0), T( 0), inf))`
# They shouldn't behave differently. Both of them should probably return NaN.
std/numerics/c.math/c.math.lerp/c.math.lerp.pass.cpp FAIL
# *** LIKELY STL BUGS ***
# Not yet analyzed, likely STL bugs. Assertions and other runtime failures.

Просмотреть файл

@ -465,10 +465,6 @@ utilities\variant\variant.variant\variant.ctor\T.pass.cpp
# C++20 P0768R1 "Library Support for the Spaceship (Comparison) Operator"
language.support\support.limits\support.limits.general\compare.version.pass.cpp
# C++20 P0811R2 "midpoint(), lerp()"
language.support\support.limits\support.limits.general\numeric.version.pass.cpp
numerics\c.math\c.math.lerp\c.math.lerp.pass.cpp
# C++20 P0896R4 "<ranges>"
language.support\support.limits\support.limits.general\algorithm.version.pass.cpp
language.support\support.limits\support.limits.general\functional.version.pass.cpp
@ -775,6 +771,11 @@ iterators\predef.iterators\insert.iterators\insert.iterator\types.pass.cpp
numerics\complex.number\cmplx.over\conj.pass.cpp
numerics\complex.number\cmplx.over\proj.pass.cpp
# Assertion failed: (std::lerp(T(2.3), T(2.3), inf) == T(2.3))
# Asserts `(std::lerp(T(2.3), T(2.3), inf) == T(2.3))` and `std::isnan(std::lerp(T( 0), T( 0), inf))`
# They shouldn't behave differently. Both of them should probably return NaN.
numerics\c.math\c.math.lerp\c.math.lerp.pass.cpp
# *** LIKELY STL BUGS ***
# Not yet analyzed, likely STL bugs. Assertions and other runtime failures.

Просмотреть файл

@ -2,3 +2,6 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
RUNALL_INCLUDE ..\usual_latest_matrix.lst
RUNALL_CROSSLIST
PM_CL="/Od"
PM_CL="/O2"

Просмотреть файл

@ -1,12 +1,16 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <algorithm>
#include <assert.h>
#include <bit>
#include <charconv>
#include <cmath>
#include <fenv.h>
#include <iterator>
#include <limits>
#include <numeric>
#include <optional>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -19,13 +23,16 @@ using namespace std;
template <typename Ty>
using limits = numeric_limits<Ty>;
// "major" floating point exceptions, excluding underflow and inexact
constexpr int fe_major_except = FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW;
#ifdef _M_FP_STRICT
// According to:
// https://docs.microsoft.com/en-us/cpp/build/reference/fp-specify-floating-point-behavior
// Under the default /fp:precise mode:
// The compiler generates code intended to run in the default floating-point environment and assumes that the
// floating-point environment is not accessed or modified at runtime.
// ... so we only do testing of rounding modes when strict is enabled.
// ... so we only do testing of rounding modes and floating-point exceptions when strict is enabled.
// TRANSITION, VSO-923474 -- should be #pragma STDC FENV_ACCESS ON
#pragma fenv_access(on)
@ -51,49 +58,89 @@ public:
private:
int oldRound;
};
#endif // _M_FP_STRICT
void checked_feholdexcept(fenv_t* const env) {
[[maybe_unused]] const int holdExcept = feholdexcept(env);
assert(holdExcept == 0);
}
void checked_fesetenv(const fenv_t* const env) {
[[maybe_unused]] const int setEnv = fesetenv(env);
assert(setEnv == 0);
}
class ExceptGuard {
public:
ExceptGuard() {
checked_feholdexcept(&env);
}
ExceptGuard(const ExceptGuard&) = delete;
ExceptGuard& operator=(const ExceptGuard&) = delete;
~ExceptGuard() {
checked_fesetenv(&env);
}
private:
fenv_t env;
};
bool check_feexcept(const int expected_excepts, const int except_mask = fe_major_except) {
return fetestexcept(except_mask) == (expected_excepts & except_mask);
}
#else // ^^^ defined(_M_FP_STRICT) / !defined(_M_FP_STRICT) vvv
class ExceptGuard {
public:
ExceptGuard() {}
ExceptGuard(const ExceptGuard&) = delete;
ExceptGuard& operator=(const ExceptGuard&) = delete;
~ExceptGuard() {}
};
bool check_feexcept(
[[maybe_unused]] const int expected_excepts, [[maybe_unused]] const int except_mask = fe_major_except) {
return true;
}
#endif // ^^^ !defined(_M_FP_STRICT) ^^^
template <class Ty>
Ty mint_nan(const bool sign, const unsigned long long payload);
constexpr Ty mint_nan(const bool sign, const unsigned long long payload);
template <>
float mint_nan<float>(const bool sign, const unsigned long long payload) {
const unsigned int filteredPayload = payload & 0x7F'FFFFu; // bottom 23 bits
constexpr float mint_nan<float>(const bool sign, const unsigned long long payload) {
const unsigned int filteredPayload = payload & 0x3F'FFFFu; // bottom 22 bits
assert(filteredPayload == payload); // if this assert fails, payload didn't fit
assert(filteredPayload != 0); // if this assert fails, the NaN would be an infinity
// clang-format off
const unsigned int result =
(static_cast<unsigned int>(sign) << 31)
| 0x7F80'0000u // turn on all exponent bits
| 0x7FC0'0000u // turn on all exponent bits and the qNaN bit
| filteredPayload;
// clang-format on
float resultConverted; // TRANSITION, bit_cast
memcpy(&resultConverted, &result, sizeof(result));
return resultConverted;
return bit_cast<float>(result);
}
template <>
double mint_nan<double>(const bool sign, const unsigned long long payload) {
const unsigned long long filteredPayload = payload & 0xF'FFFF'FFFF'FFFFllu; // bottom 52 bits
constexpr double mint_nan<double>(const bool sign, const unsigned long long payload) {
const unsigned long long filteredPayload = payload & 0x7'FFFF'FFFF'FFFFllu; // bottom 51 bits
assert(filteredPayload == payload); // if this assert fails, payload didn't fit
assert(filteredPayload != 0); // if this assert fails, the NaN would be an infinity
// clang-format off
const unsigned long long result =
(static_cast<unsigned long long>(sign) << 63)
| 0x7FF0'0000'0000'0000u // turn on all exponent bits
| 0x7FF8'0000'0000'0000u // turn on all exponent bits and the qNaN bit
| filteredPayload;
// clang-format on
double resultConverted; // TRANSITION, bit_cast
memcpy(&resultConverted, &result, sizeof(result));
return resultConverted;
return bit_cast<double>(result);
}
template <>
long double mint_nan<long double>(const bool sign, const unsigned long long payload) {
constexpr long double mint_nan<long double>(const bool sign, const unsigned long long payload) {
return mint_nan<double>(sign, payload);
}
@ -102,17 +149,36 @@ void assert_bitwise_equal(const Ty& a, const Ty& b) {
assert(memcmp(&a, &b, sizeof(Ty)) == 0);
}
// TRANSITION
// numeric_limits<T>::signaling_NaN() doesn't work on x86 hosted MSVC
// numeric_limits<float>::signaling_NaN() doesn't work on x64 hosted MSVC
void make_snan(float& x) {
constexpr unsigned int bits = 0x7f80'0001U;
memcpy(&x, &bits, sizeof(x));
}
void make_snan(double& x) {
constexpr unsigned long long bits = 0x7ff0'0000'0000'0001ULL;
memcpy(&x, &bits, sizeof(x));
}
void make_snan(long double& x) {
constexpr unsigned long long bits = 0x7ff0'0000'0000'0001ULL;
memcpy(&x, &bits, sizeof(x));
}
template <typename Ty>
struct constants; // not defined
template <>
struct constants<float> {
static constexpr float TwoPlusUlp = 0x1.000002p+1f;
static constexpr float OnePlusUlp = 0x1.000002p+0f;
static constexpr float PointFivePlusUlp = 0x1.000002p-1f;
static constexpr float OneMinusUlp = 0x1.fffffep-1f;
static constexpr float NegOneMinusUlp = -OnePlusUlp;
static constexpr float NegOnePlusUlp = -OneMinusUlp;
static constexpr float TwoPlusUlp = 0x1.000002p+1f;
static constexpr float OnePlusUlp = 0x1.000002p+0f;
static constexpr float PointFivePlusUlp = 0x1.000002p-1f;
static constexpr float OneMinusUlp = 0x1.fffffep-1f;
static constexpr float PointFiveMinusUlp = 0x1.fffffep-2f;
static constexpr float NegOneMinusUlp = -OnePlusUlp;
static constexpr float NegOnePlusUlp = -OneMinusUlp;
static constexpr float EighthPlusUlp = 0x1.000002p-3f;
static constexpr float EighthMinusUlp = 0x1.fffffep-4f;
@ -120,12 +186,13 @@ struct constants<float> {
template <>
struct constants<double> {
static constexpr double TwoPlusUlp = 0x1.0000000000001p+1;
static constexpr double OnePlusUlp = 0x1.0000000000001p+0;
static constexpr double PointFivePlusUlp = 0x1.0000000000001p-1;
static constexpr double OneMinusUlp = 0x1.fffffffffffffp-1;
static constexpr double NegOneMinusUlp = -OnePlusUlp;
static constexpr double NegOnePlusUlp = -OneMinusUlp;
static constexpr double TwoPlusUlp = 0x1.0000000000001p+1;
static constexpr double OnePlusUlp = 0x1.0000000000001p+0;
static constexpr double PointFivePlusUlp = 0x1.0000000000001p-1;
static constexpr double OneMinusUlp = 0x1.fffffffffffffp-1;
static constexpr double PointFiveMinusUlp = 0x1.fffffffffffffp-2;
static constexpr double NegOneMinusUlp = -OnePlusUlp;
static constexpr double NegOnePlusUlp = -OneMinusUlp;
static constexpr double EighthPlusUlp = 0x1.0000000000001p-3;
static constexpr double EighthMinusUlp = 0x1.fffffffffffffp-4;
@ -133,12 +200,13 @@ struct constants<double> {
template <>
struct constants<long double> {
static constexpr long double TwoPlusUlp = 0x1.0000000000001p+1;
static constexpr long double OnePlusUlp = 0x1.0000000000001p+0;
static constexpr long double PointFivePlusUlp = 0x1.0000000000001p-1;
static constexpr long double OneMinusUlp = 0x1.fffffffffffffp-1;
static constexpr long double NegOneMinusUlp = -OnePlusUlp;
static constexpr long double NegOnePlusUlp = -OneMinusUlp;
static constexpr long double TwoPlusUlp = 0x1.0000000000001p+1;
static constexpr long double OnePlusUlp = 0x1.0000000000001p+0;
static constexpr long double PointFivePlusUlp = 0x1.0000000000001p-1;
static constexpr long double OneMinusUlp = 0x1.fffffffffffffp-1;
static constexpr long double PointFiveMinusUlp = 0x1.fffffffffffffp-2;
static constexpr long double NegOneMinusUlp = -OnePlusUlp;
static constexpr long double NegOnePlusUlp = -OneMinusUlp;
static constexpr long double EighthPlusUlp = 0x1.0000000000001p-3;
static constexpr long double EighthMinusUlp = 0x1.fffffffffffffp-4;
@ -150,6 +218,7 @@ void test_constants() {
assert(constants<Ty>::OnePlusUlp == nextafter(Ty(1.0), Ty(3.0)));
assert(constants<Ty>::PointFivePlusUlp == nextafter(Ty(0.5), Ty(3.0)));
assert(constants<Ty>::OneMinusUlp == nextafter(Ty(1.0), Ty(0.0)));
assert(constants<Ty>::PointFiveMinusUlp == nextafter(Ty(0.5), Ty(-2.0)));
assert(constants<Ty>::NegOneMinusUlp == nextafter(Ty(-1.0), Ty(-2.0)));
assert(constants<Ty>::NegOnePlusUlp == nextafter(Ty(-1.0), Ty(0.0)));
@ -326,6 +395,7 @@ void test_midpoint_floating() {
#ifdef _M_FP_STRICT
{
// test results exactly between 1 ULP:
ExceptGuard except;
RoundGuard round{FE_UPWARD};
assert(midpoint(Ty(1.0), constants<Ty>::OnePlusUlp) == constants<Ty>::OnePlusUlp);
assert(midpoint(Ty(1.0), constants<Ty>::OneMinusUlp) == Ty(1.0));
@ -356,10 +426,13 @@ void test_midpoint_floating() {
assert(midpoint(-limits<Ty>::min(), -limits<Ty>::max()) == -limits<Ty>::max() / 2);
assert(midpoint(-limits<Ty>::denorm_min(), limits<Ty>::max()) == limits<Ty>::max() / 2);
assert(midpoint(-limits<Ty>::denorm_min(), -limits<Ty>::max()) == -limits<Ty>::max() / 2);
assert(check_feexcept(0));
}
// ditto for the other rounding modes:
{
ExceptGuard except;
RoundGuard round{FE_DOWNWARD};
assert(midpoint(Ty(1.0), constants<Ty>::OnePlusUlp) == Ty(1.0));
assert(midpoint(Ty(1.0), constants<Ty>::OneMinusUlp) == constants<Ty>::OneMinusUlp);
@ -394,9 +467,12 @@ void test_midpoint_floating() {
== nextafter(limits<Ty>::max() / 2, limits<Ty>::lowest()));
assert(midpoint(-limits<Ty>::denorm_min(), -limits<Ty>::max())
== nextafter(-limits<Ty>::max() / 2, limits<Ty>::lowest()));
assert(check_feexcept(0));
}
{
ExceptGuard except;
RoundGuard round{FE_TOWARDZERO};
assert(midpoint(Ty(1.0), constants<Ty>::OnePlusUlp) == Ty(1.0));
assert(midpoint(Ty(1.0), constants<Ty>::OneMinusUlp) == constants<Ty>::OneMinusUlp);
@ -422,27 +498,49 @@ void test_midpoint_floating() {
assert(midpoint(-limits<Ty>::min(), -limits<Ty>::max()) == -limits<Ty>::max() / 2);
assert(midpoint(-limits<Ty>::denorm_min(), limits<Ty>::max()) == nextafter(limits<Ty>::max() / 2, Ty(0)));
assert(midpoint(-limits<Ty>::denorm_min(), -limits<Ty>::max()) == -limits<Ty>::max() / 2);
assert(check_feexcept(0));
}
#endif // _M_FP_STRICT
assert(midpoint(limits<Ty>::denorm_min(), Ty(1.0)) == (limits<Ty>::denorm_min() + Ty(1.0)) / Ty(2.0));
assert(midpoint(limits<Ty>::denorm_min(), limits<Ty>::max())
== (limits<Ty>::denorm_min() + limits<Ty>::max()) / Ty(2.0));
assert(midpoint(limits<Ty>::denorm_min(), limits<Ty>::lowest())
== (limits<Ty>::denorm_min() + limits<Ty>::lowest()) / Ty(2.0));
assert(midpoint(limits<Ty>::denorm_min(), limits<Ty>::infinity()) == limits<Ty>::infinity());
assert(midpoint(limits<Ty>::denorm_min(), -limits<Ty>::infinity()) == -limits<Ty>::infinity());
{
ExceptGuard except;
assert_bitwise_equal(mint_nan<Ty>(0, 1), midpoint(mint_nan<Ty>(0, 1), Ty(0)));
assert_bitwise_equal(mint_nan<Ty>(0, 1), midpoint(Ty(0), mint_nan<Ty>(0, 1)));
assert_bitwise_equal(mint_nan<Ty>(0, 1), midpoint(mint_nan<Ty>(0, 1), limits<Ty>::max()));
assert_bitwise_equal(mint_nan<Ty>(0, 1), midpoint(limits<Ty>::max(), mint_nan<Ty>(0, 1)));
assert_bitwise_equal(mint_nan<Ty>(0, 1), midpoint(mint_nan<Ty>(0, 1), mint_nan<Ty>(0, 2)));
assert(midpoint(limits<Ty>::denorm_min(), Ty(1.0)) == (limits<Ty>::denorm_min() + Ty(1.0)) / Ty(2.0));
assert(midpoint(limits<Ty>::denorm_min(), limits<Ty>::max())
== (limits<Ty>::denorm_min() + limits<Ty>::max()) / Ty(2.0));
assert(midpoint(limits<Ty>::denorm_min(), limits<Ty>::lowest())
== (limits<Ty>::denorm_min() + limits<Ty>::lowest()) / Ty(2.0));
assert(midpoint(limits<Ty>::denorm_min(), limits<Ty>::infinity()) == limits<Ty>::infinity());
assert(midpoint(limits<Ty>::denorm_min(), -limits<Ty>::infinity()) == -limits<Ty>::infinity());
assert(isnan(midpoint(-limits<Ty>::infinity(), limits<Ty>::infinity())));
assert(isnan(midpoint(limits<Ty>::quiet_NaN(), Ty(2.0))));
assert(isnan(midpoint(Ty(2.0), limits<Ty>::quiet_NaN())));
assert(isnan(midpoint(limits<Ty>::quiet_NaN(), limits<Ty>::quiet_NaN())));
assert_bitwise_equal(mint_nan<Ty>(0, 1), midpoint(mint_nan<Ty>(0, 1), Ty(0)));
assert_bitwise_equal(mint_nan<Ty>(0, 1), midpoint(Ty(0), mint_nan<Ty>(0, 1)));
assert_bitwise_equal(mint_nan<Ty>(0, 1), midpoint(mint_nan<Ty>(0, 1), limits<Ty>::max()));
assert_bitwise_equal(mint_nan<Ty>(0, 1), midpoint(limits<Ty>::max(), mint_nan<Ty>(0, 1)));
assert_bitwise_equal(mint_nan<Ty>(0, 1), midpoint(mint_nan<Ty>(0, 1), mint_nan<Ty>(0, 1)));
assert(isnan(midpoint(limits<Ty>::quiet_NaN(), Ty(2.0))));
assert(isnan(midpoint(Ty(2.0), limits<Ty>::quiet_NaN())));
assert(isnan(midpoint(limits<Ty>::quiet_NaN(), limits<Ty>::quiet_NaN())));
assert(check_feexcept(0));
}
// cases where midpoint() should raise FE_INVALID and return NaN
constexpr auto test_midpoint_fe_invalid = [](const Ty& a, const Ty& b) {
ExceptGuard except;
const auto answer = midpoint(a, b);
return check_feexcept(FE_INVALID) && isnan(answer);
};
Ty snan;
make_snan(snan);
assert(test_midpoint_fe_invalid(-limits<Ty>::infinity(), limits<Ty>::infinity()));
assert(test_midpoint_fe_invalid(snan, limits<Ty>::quiet_NaN()));
assert(test_midpoint_fe_invalid(limits<Ty>::quiet_NaN(), snan));
assert(test_midpoint_fe_invalid(snan, snan));
}
template <typename Ty>
@ -496,7 +594,7 @@ constexpr bool test_midpoint_pointer() {
}
template <typename Ty>
int cmp(const Ty x, const Ty y) {
constexpr int cmp(const Ty x, const Ty y) {
if (x > y) {
return 1;
} else if (x < y) {
@ -519,11 +617,12 @@ struct LerpNaNTestCase {
Ty x;
Ty y;
Ty t;
optional<Ty> expected_list[3] = {};
};
template <typename Ty>
struct LerpCases { // TRANSITION, VSO-934633
static inline const LerpTestCase<Ty> lerpTestCases[] = {
static inline constexpr LerpTestCase<Ty> lerpTestCases[] = {
{Ty(-1.0), Ty(1.0), Ty(2.0), Ty(3.0)},
{Ty(0.0), Ty(1.0), Ty(2.0), Ty(2.0)},
{Ty(-1.0), Ty(0.0), Ty(2.0), Ty(1.0)},
@ -564,8 +663,8 @@ struct LerpCases { // TRANSITION, VSO-934633
// double: lerp(-0x0.0000000000001p-1022, -0x1.0000000000001p+0, 0.5) = -0x1.0000000000001p-1
{-limits<Ty>::denorm_min(), constants<Ty>::NegOneMinusUlp, Ty(0.5), -constants<Ty>::PointFivePlusUlp},
{Ty(1.0), constants<Ty>::OnePlusUlp, nextafter(Ty(0.5), Ty(-1.0)), Ty(1.0)},
{Ty(-1.0), constants<Ty>::NegOneMinusUlp, nextafter(Ty(0.5), Ty(-1.0)), Ty(-1.0)},
{Ty(1.0), constants<Ty>::OnePlusUlp, constants<Ty>::PointFiveMinusUlp, Ty(1.0)},
{Ty(-1.0), constants<Ty>::NegOneMinusUlp, constants<Ty>::PointFiveMinusUlp, Ty(-1.0)},
{Ty(1.0), constants<Ty>::OnePlusUlp, Ty(0.5), Ty(1.0)},
{Ty(-1.0), constants<Ty>::NegOneMinusUlp, Ty(0.5), Ty(-1.0)},
@ -586,8 +685,6 @@ struct LerpCases { // TRANSITION, VSO-934633
{Ty(1.0), Ty(2.0), constants<Ty>::TwoPlusUlp, constants<Ty>::TwoPlusUlp + Ty(1.0)},
{Ty(1.0), Ty(2.0), Ty(0.5), constants<Ty>::PointFivePlusUlp + Ty(1.0)},
{limits<Ty>::lowest(), limits<Ty>::max(), Ty(2.0), limits<Ty>::infinity()},
{limits<Ty>::max(), limits<Ty>::lowest(), Ty(2.0), -limits<Ty>::infinity()},
{limits<Ty>::max(), limits<Ty>::max(), Ty(2.0), limits<Ty>::max()},
{limits<Ty>::lowest(), limits<Ty>::lowest(), Ty(2.0), limits<Ty>::lowest()},
@ -598,84 +695,192 @@ struct LerpCases { // TRANSITION, VSO-934633
{limits<Ty>::denorm_min(), limits<Ty>::infinity(), Ty(0.5), limits<Ty>::infinity()},
{limits<Ty>::denorm_min(), -limits<Ty>::infinity(), Ty(0.5), -limits<Ty>::infinity()},
// the following handling of NaNs and infinities isn't in the spec, but seems like the right behavior:
// when the inputs are infinity and T is 1 or more, return the second parameter
{-limits<Ty>::infinity(), limits<Ty>::infinity(), constants<Ty>::OnePlusUlp, limits<Ty>::infinity()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), constants<Ty>::OnePlusUlp, -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), Ty(1.0), limits<Ty>::infinity()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), Ty(1.0), -limits<Ty>::infinity()},
// when the inputs are infinity and T is 0 or less, return the first parameter
{-limits<Ty>::infinity(), limits<Ty>::infinity(), -limits<Ty>::denorm_min(), -limits<Ty>::infinity()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), -limits<Ty>::denorm_min(), limits<Ty>::infinity()},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), Ty(0), -limits<Ty>::infinity()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), Ty(0), limits<Ty>::infinity()},
// if any of the inputs are NaN, return the first NaN
{mint_nan<Ty>(0, 42), mint_nan<Ty>(1, 42), mint_nan<Ty>(0, 1729), mint_nan<Ty>(0, 42)},
{Ty(1.0), mint_nan<Ty>(1, 42), mint_nan<Ty>(0, 1729), mint_nan<Ty>(1, 42)},
{mint_nan<Ty>(1, 42), Ty(1.0), mint_nan<Ty>(0, 1729), mint_nan<Ty>(1, 42)},
{Ty(1.0), Ty(1.0), mint_nan<Ty>(0, 1729), mint_nan<Ty>(0, 1729)},
{limits<Ty>::infinity(), mint_nan<Ty>(1, 42), mint_nan<Ty>(0, 1729), mint_nan<Ty>(1, 42)},
{mint_nan<Ty>(1, 42), limits<Ty>::infinity(), mint_nan<Ty>(0, 1729), mint_nan<Ty>(1, 42)},
{limits<Ty>::infinity(), limits<Ty>::infinity(), mint_nan<Ty>(0, 1729), mint_nan<Ty>(0, 1729)},
// the following handling of infinities isn't in the spec, but seems like the right behavior:
// if the values differ and T is an infinity, the appropriate infinity according to direction
{Ty(0), Ty(1), limits<Ty>::infinity(), limits<Ty>::infinity()},
{Ty(0), Ty(1), -limits<Ty>::infinity(), -limits<Ty>::infinity()},
{Ty(0), -Ty(1), limits<Ty>::infinity(), -limits<Ty>::infinity()},
{Ty(0), -Ty(1), -limits<Ty>::infinity(), limits<Ty>::infinity()},
// if one of a or b is an infinity, choose the other value when t says exact, otherwise
// return that infinity or the other according to "direction" of t
{limits<Ty>::infinity(), Ty(1.0), Ty(1.0), Ty(1.0)},
{limits<Ty>::infinity(), Ty(1.0), constants<Ty>::OnePlusUlp, -limits<Ty>::infinity()},
{limits<Ty>::infinity(), Ty(1.0), constants<Ty>::OneMinusUlp, limits<Ty>::infinity()},
{limits<Ty>::infinity(), Ty(1.0), limits<Ty>::denorm_min(), limits<Ty>::infinity()},
// when the inputs are infinity of the same sign and 0 < T < 1, return that infinity
{limits<Ty>::infinity(), limits<Ty>::infinity(), limits<Ty>::denorm_min(), limits<Ty>::infinity()},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), limits<Ty>::denorm_min(), -limits<Ty>::infinity()},
{limits<Ty>::infinity(), limits<Ty>::infinity(), Ty(0.5), limits<Ty>::infinity()},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), Ty(0.5), -limits<Ty>::infinity()},
{limits<Ty>::infinity(), limits<Ty>::infinity(), constants<Ty>::OneMinusUlp, limits<Ty>::infinity()},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), constants<Ty>::OneMinusUlp, -limits<Ty>::infinity()},
// when the inputs are infinity of opposite signs and T > 1, return the second parameter
{-limits<Ty>::infinity(), limits<Ty>::infinity(), constants<Ty>::OnePlusUlp, limits<Ty>::infinity()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), constants<Ty>::OnePlusUlp, -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), Ty(2.0), limits<Ty>::infinity()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), Ty(2.0), -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), limits<Ty>::max(), limits<Ty>::infinity()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), limits<Ty>::max(), -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), limits<Ty>::infinity(), limits<Ty>::infinity()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), limits<Ty>::infinity(), -limits<Ty>::infinity()},
// when the inputs are infinity of opposite signs and T < 0, return the first parameter
{-limits<Ty>::infinity(), limits<Ty>::infinity(), -limits<Ty>::infinity(), -limits<Ty>::infinity()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), -limits<Ty>::infinity(), limits<Ty>::infinity()},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), -limits<Ty>::max(), -limits<Ty>::infinity()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), -limits<Ty>::max(), limits<Ty>::infinity()},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), -Ty(2.0), -limits<Ty>::infinity()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), -Ty(2.0), limits<Ty>::infinity()},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), -limits<Ty>::denorm_min(), -limits<Ty>::infinity()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), -limits<Ty>::denorm_min(), limits<Ty>::infinity()},
// if a is an infinity, b is finite and T != 1, return that infinity or the other according to "direction" of t
{limits<Ty>::infinity(), Ty(1.0), -limits<Ty>::infinity(), limits<Ty>::infinity()},
{limits<Ty>::infinity(), Ty(1.0), -limits<Ty>::max(), limits<Ty>::infinity()},
{limits<Ty>::infinity(), Ty(1.0), -Ty(1.0), limits<Ty>::infinity()},
{limits<Ty>::infinity(), Ty(1.0), -limits<Ty>::denorm_min(), limits<Ty>::infinity()},
{limits<Ty>::infinity(), Ty(1.0), -Ty(0.0), limits<Ty>::infinity()},
{limits<Ty>::infinity(), Ty(1.0), Ty(0.0), limits<Ty>::infinity()},
{limits<Ty>::infinity(), Ty(1.0), limits<Ty>::denorm_min(), limits<Ty>::infinity()},
{limits<Ty>::infinity(), Ty(1.0), Ty(0.5), limits<Ty>::infinity()},
{limits<Ty>::infinity(), Ty(1.0), constants<Ty>::OneMinusUlp, limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), Ty(1.0), Ty(1.0)},
{-limits<Ty>::infinity(), Ty(1.0), constants<Ty>::OnePlusUlp, limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), constants<Ty>::OneMinusUlp, -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), limits<Ty>::denorm_min(), -limits<Ty>::infinity()},
{limits<Ty>::infinity(), Ty(1.0), constants<Ty>::OnePlusUlp, -limits<Ty>::infinity()},
{limits<Ty>::infinity(), Ty(1.0), Ty(2.0), -limits<Ty>::infinity()},
{limits<Ty>::infinity(), Ty(1.0), limits<Ty>::max(), -limits<Ty>::infinity()},
{limits<Ty>::infinity(), Ty(1.0), limits<Ty>::infinity(), -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), -limits<Ty>::infinity(), -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), -limits<Ty>::max(), -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), -Ty(1.0), -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), -limits<Ty>::denorm_min(), -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), -Ty(0.0), -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), Ty(0.0), -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), limits<Ty>::denorm_min(), -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), Ty(0.5), -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), constants<Ty>::OneMinusUlp, -limits<Ty>::infinity()},
{Ty(1.0), limits<Ty>::infinity(), Ty(0.0), Ty(1.0)},
{Ty(1.0), limits<Ty>::infinity(), -Ty(0.0), Ty(1.0)},
{Ty(1.0), limits<Ty>::infinity(), limits<Ty>::denorm_min(), limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), constants<Ty>::OnePlusUlp, limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), Ty(2.0), limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), limits<Ty>::max(), limits<Ty>::infinity()},
{-limits<Ty>::infinity(), Ty(1.0), limits<Ty>::infinity(), limits<Ty>::infinity()},
// if b is an infinity, a is finite and T != 0, return that infinity or the other according to "direction" of t
{Ty(1.0), limits<Ty>::infinity(), -limits<Ty>::infinity(), -limits<Ty>::infinity()},
{Ty(1.0), limits<Ty>::infinity(), -limits<Ty>::max(), -limits<Ty>::infinity()},
{Ty(1.0), limits<Ty>::infinity(), -Ty(1.0), -limits<Ty>::infinity()},
{Ty(1.0), limits<Ty>::infinity(), -limits<Ty>::denorm_min(), -limits<Ty>::infinity()},
{Ty(1.0), -limits<Ty>::infinity(), Ty(0.0), Ty(1.0)},
{Ty(1.0), -limits<Ty>::infinity(), -Ty(0.0), Ty(1.0)},
{Ty(1.0), -limits<Ty>::infinity(), limits<Ty>::denorm_min(), -limits<Ty>::infinity()},
{Ty(1.0), limits<Ty>::infinity(), limits<Ty>::denorm_min(), limits<Ty>::infinity()},
{Ty(1.0), limits<Ty>::infinity(), Ty(0.5), limits<Ty>::infinity()},
{Ty(1.0), limits<Ty>::infinity(), constants<Ty>::OneMinusUlp, limits<Ty>::infinity()},
{Ty(1.0), limits<Ty>::infinity(), Ty(1.0), limits<Ty>::infinity()},
{Ty(1.0), limits<Ty>::infinity(), constants<Ty>::OnePlusUlp, limits<Ty>::infinity()},
{Ty(1.0), limits<Ty>::infinity(), Ty(2.0), limits<Ty>::infinity()},
{Ty(1.0), limits<Ty>::infinity(), limits<Ty>::max(), limits<Ty>::infinity()},
{Ty(1.0), limits<Ty>::infinity(), limits<Ty>::infinity(), limits<Ty>::infinity()},
{Ty(1.0), -limits<Ty>::infinity(), -limits<Ty>::infinity(), limits<Ty>::infinity()},
{Ty(1.0), -limits<Ty>::infinity(), -limits<Ty>::max(), limits<Ty>::infinity()},
{Ty(1.0), -limits<Ty>::infinity(), -Ty(1.0), limits<Ty>::infinity()},
{Ty(1.0), -limits<Ty>::infinity(), -limits<Ty>::denorm_min(), limits<Ty>::infinity()},
// if all 3 values are infinities, choose the selected one according to t
{limits<Ty>::infinity(), limits<Ty>::infinity(), limits<Ty>::infinity(), limits<Ty>::infinity()},
{limits<Ty>::infinity(), limits<Ty>::infinity(), -limits<Ty>::infinity(), limits<Ty>::infinity()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), limits<Ty>::infinity(), -limits<Ty>::infinity()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), -limits<Ty>::infinity(), limits<Ty>::infinity()},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), limits<Ty>::infinity(), limits<Ty>::infinity()},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), -limits<Ty>::infinity(), -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), limits<Ty>::infinity(), -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), -limits<Ty>::infinity(), -limits<Ty>::infinity()},
{Ty(1.0), -limits<Ty>::infinity(), limits<Ty>::denorm_min(), -limits<Ty>::infinity()},
{Ty(1.0), -limits<Ty>::infinity(), Ty(0.5), -limits<Ty>::infinity()},
{Ty(1.0), -limits<Ty>::infinity(), constants<Ty>::OneMinusUlp, -limits<Ty>::infinity()},
{Ty(1.0), -limits<Ty>::infinity(), Ty(1.0), -limits<Ty>::infinity()},
{Ty(1.0), -limits<Ty>::infinity(), constants<Ty>::OnePlusUlp, -limits<Ty>::infinity()},
{Ty(1.0), -limits<Ty>::infinity(), Ty(2.0), -limits<Ty>::infinity()},
{Ty(1.0), -limits<Ty>::infinity(), limits<Ty>::max(), -limits<Ty>::infinity()},
{Ty(1.0), -limits<Ty>::infinity(), limits<Ty>::infinity(), -limits<Ty>::infinity()},
};
static inline constexpr LerpNaNTestCase<Ty> lerpNaNTestCases[] = {
// when the inputs are infinity and T is between 0 and 1, return NaN
{-limits<Ty>::infinity(), limits<Ty>::infinity(), limits<Ty>::denorm_min()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), limits<Ty>::denorm_min()},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), constants<Ty>::OneMinusUlp},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), constants<Ty>::OneMinusUlp},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), Ty(0.5)},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), Ty(0.5)},
static inline constexpr LerpTestCase<Ty> lerpOverflowTestCases[] = {
{limits<Ty>::lowest(), limits<Ty>::max(), Ty(2.0), limits<Ty>::infinity()},
{limits<Ty>::max(), limits<Ty>::lowest(), Ty(2.0), -limits<Ty>::infinity()},
};
static inline constexpr LerpNaNTestCase<Ty> lerpInvalidTestCases[] = {
// if the values are equal and T is an infinity, NaN
{Ty(0), Ty(0), limits<Ty>::infinity()},
{Ty(0), Ty(0), -limits<Ty>::infinity()},
// when the inputs are infinity of the same sign and T <= 0, return NaN
{limits<Ty>::infinity(), limits<Ty>::infinity(), -limits<Ty>::infinity()},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), -limits<Ty>::infinity()},
{limits<Ty>::infinity(), limits<Ty>::infinity(), -limits<Ty>::max()},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), -limits<Ty>::max()},
{limits<Ty>::infinity(), limits<Ty>::infinity(), -Ty(1.0)},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), -Ty(1.0)},
{limits<Ty>::infinity(), limits<Ty>::infinity(), -limits<Ty>::denorm_min()},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), -limits<Ty>::denorm_min()},
{limits<Ty>::infinity(), limits<Ty>::infinity(), -Ty(0.0)},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), -Ty(0.0)},
{limits<Ty>::infinity(), limits<Ty>::infinity(), Ty(0.0)},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), Ty(0.0)},
// when the inputs are infinity of the same sign and T >= 1, return NaN
{limits<Ty>::infinity(), limits<Ty>::infinity(), Ty(1.0)},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), Ty(1.0)},
{limits<Ty>::infinity(), limits<Ty>::infinity(), constants<Ty>::OnePlusUlp},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), constants<Ty>::OnePlusUlp},
{limits<Ty>::infinity(), limits<Ty>::infinity(), Ty(2.0)},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), Ty(2.0)},
{limits<Ty>::infinity(), limits<Ty>::infinity(), limits<Ty>::max()},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), limits<Ty>::max()},
{limits<Ty>::infinity(), limits<Ty>::infinity(), limits<Ty>::infinity()},
{-limits<Ty>::infinity(), -limits<Ty>::infinity(), limits<Ty>::infinity()},
// when the inputs are infinity of opposite signs and 0 <= T <= 1, return NaN
{-limits<Ty>::infinity(), limits<Ty>::infinity(), -Ty(0.0)},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), -Ty(0.0)},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), Ty(0.0)},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), Ty(0.0)},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), limits<Ty>::denorm_min()},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), limits<Ty>::denorm_min()},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), Ty(0.5)},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), Ty(0.5)},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), constants<Ty>::OneMinusUlp},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), constants<Ty>::OneMinusUlp},
{-limits<Ty>::infinity(), limits<Ty>::infinity(), Ty(1.0)},
{limits<Ty>::infinity(), -limits<Ty>::infinity(), Ty(1.0)},
// if a is an infinity, b is finite and T = 1, return NaN
{limits<Ty>::infinity(), Ty(1.0), Ty(1.0)},
{-limits<Ty>::infinity(), Ty(1.0), Ty(1.0)},
// if b is an infinity, a is finite and T = 0, return NaN
{Ty(1.0), limits<Ty>::infinity(), Ty(0.0)},
{Ty(1.0), limits<Ty>::infinity(), -Ty(0.0)},
{Ty(1.0), -limits<Ty>::infinity(), Ty(0.0)},
{Ty(1.0), -limits<Ty>::infinity(), -Ty(0.0)},
};
static inline constexpr LerpNaNTestCase<Ty> lerpNaNTestCases[] = {
{mint_nan<Ty>(0, 42), mint_nan<Ty>(1, 42), mint_nan<Ty>(0, 1729),
{mint_nan<Ty>(0, 42), mint_nan<Ty>(1, 42), mint_nan<Ty>(0, 1729)}},
{Ty(1.0), mint_nan<Ty>(1, 42), mint_nan<Ty>(0, 1729), {mint_nan<Ty>(1, 42), mint_nan<Ty>(0, 1729)}},
{mint_nan<Ty>(1, 42), Ty(1.0), mint_nan<Ty>(0, 1729), {mint_nan<Ty>(1, 42), mint_nan<Ty>(0, 1729)}},
{Ty(1.0), Ty(1.0), mint_nan<Ty>(0, 1729), {mint_nan<Ty>(0, 1729)}},
{limits<Ty>::infinity(), mint_nan<Ty>(1, 42), mint_nan<Ty>(0, 1729),
{mint_nan<Ty>(1, 42), mint_nan<Ty>(0, 1729)}},
{mint_nan<Ty>(1, 42), limits<Ty>::infinity(), mint_nan<Ty>(0, 1729),
{mint_nan<Ty>(1, 42), mint_nan<Ty>(0, 1729)}},
{limits<Ty>::infinity(), limits<Ty>::infinity(), mint_nan<Ty>(0, 1729), {mint_nan<Ty>(0, 1729)}},
{mint_nan<Ty>(0, 42), mint_nan<Ty>(1, 42), -Ty(0.0), {mint_nan<Ty>(0, 42), mint_nan<Ty>(1, 42)}},
{mint_nan<Ty>(0, 42), mint_nan<Ty>(1, 42), Ty(0.0), {mint_nan<Ty>(0, 42), mint_nan<Ty>(1, 42)}},
{mint_nan<Ty>(0, 42), mint_nan<Ty>(1, 42), Ty(1.0), {mint_nan<Ty>(0, 42), mint_nan<Ty>(1, 42)}},
{mint_nan<Ty>(0, 42), Ty(1.0), -Ty(0.0), {mint_nan<Ty>(0, 42)}},
{mint_nan<Ty>(0, 42), Ty(1.0), Ty(0.0), {mint_nan<Ty>(0, 42)}},
{mint_nan<Ty>(0, 42), Ty(1.0), Ty(1.0), {mint_nan<Ty>(0, 42)}},
{mint_nan<Ty>(0, 42), limits<Ty>::infinity(), -Ty(0.0), {mint_nan<Ty>(0, 42)}},
{mint_nan<Ty>(0, 42), limits<Ty>::infinity(), Ty(0.0), {mint_nan<Ty>(0, 42)}},
{mint_nan<Ty>(0, 42), limits<Ty>::infinity(), Ty(1.0), {mint_nan<Ty>(0, 42)}},
{Ty(1.0), mint_nan<Ty>(1, 42), -Ty(0.0), {mint_nan<Ty>(1, 42)}},
{Ty(1.0), mint_nan<Ty>(1, 42), Ty(0.0), {mint_nan<Ty>(1, 42)}},
{Ty(1.0), mint_nan<Ty>(1, 42), Ty(1.0), {mint_nan<Ty>(1, 42)}},
{limits<Ty>::infinity(), mint_nan<Ty>(1, 42), -Ty(0.0), {mint_nan<Ty>(1, 42)}},
{limits<Ty>::infinity(), mint_nan<Ty>(1, 42), Ty(0.0), {mint_nan<Ty>(1, 42)}},
{limits<Ty>::infinity(), mint_nan<Ty>(1, 42), Ty(1.0), {mint_nan<Ty>(1, 42)}},
};
};
@ -730,25 +935,93 @@ bool test_lerp() {
STATIC_ASSERT(is_signed_v<Ty>);
STATIC_ASSERT(noexcept(lerp(Ty(), Ty(), Ty())));
// No constexpr tests for now because implementing constexpr lerp appears to depend on p0553, which was not accepted
// (yet?).
constexpr auto test_lerp_constexpr = [] {
using bit_type = conditional_t<sizeof(Ty) == 4, unsigned int, unsigned long long>;
for (const auto& testCase : LerpCases<Ty>::lerpTestCases) {
const auto answer = lerp(testCase.x, testCase.y, testCase.t);
assert(bit_cast<bit_type>(answer) == bit_cast<bit_type>(testCase.expected));
}
for (auto&& testCase : LerpCases<Ty>::lerpNaNTestCases) {
const auto answer = lerp(testCase.x, testCase.y, testCase.t);
assert(any_of(begin(testCase.expected_list), end(testCase.expected_list), [&](const auto& expected) {
return expected.has_value() && bit_cast<bit_type>(answer) == bit_cast<bit_type>(expected.value());
}));
}
return true;
};
STATIC_ASSERT(test_lerp_constexpr());
for (auto&& testCase : LerpCases<Ty>::lerpTestCases) {
ExceptGuard except;
const auto answer = lerp(testCase.x, testCase.y, testCase.t);
if (memcmp(&answer, &testCase.expected, sizeof(Ty)) != 0) {
if (!check_feexcept(0) || memcmp(&answer, &testCase.expected, sizeof(Ty)) != 0) {
print_lerp_result(testCase, answer);
abort();
}
}
for (auto&& testCase : LerpCases<Ty>::lerpOverflowTestCases) {
ExceptGuard except;
const auto answer = lerp(testCase.x, testCase.y, testCase.t);
if (!check_feexcept(FE_OVERFLOW) || memcmp(&answer, &testCase.expected, sizeof(Ty)) != 0) {
print_lerp_result(testCase, answer);
abort();
}
}
for (auto&& testCase : LerpCases<Ty>::lerpInvalidTestCases) {
ExceptGuard except;
const auto answer = lerp(testCase.x, testCase.y, testCase.t);
if (!check_feexcept(FE_INVALID) || !isnan(answer)) {
print_lerp_result(testCase, answer);
abort();
}
}
for (auto&& testCase : LerpCases<Ty>::lerpNaNTestCases) {
ExceptGuard except;
const auto answer = lerp(testCase.x, testCase.y, testCase.t);
if (!isnan(answer)) {
if (!check_feexcept(0)
|| none_of(begin(testCase.expected_list), end(testCase.expected_list), [&answer](const auto& expected) {
return expected.has_value() && memcmp(&answer, &expected.value(), sizeof(Ty)) == 0;
})) {
print_lerp_result(testCase, answer);
abort();
}
}
constexpr auto test_lerp_snan = [](const Ty& a, const Ty& b, const Ty& t) {
ExceptGuard except;
const auto answer = lerp(a, b, t);
return check_feexcept(FE_INVALID) && isnan(answer);
};
Ty snan;
make_snan(snan);
assert(test_lerp_snan(snan, limits<Ty>::quiet_NaN(), limits<Ty>::quiet_NaN()));
assert(test_lerp_snan(limits<Ty>::quiet_NaN(), snan, limits<Ty>::quiet_NaN()));
assert(test_lerp_snan(snan, snan, limits<Ty>::quiet_NaN()));
assert(test_lerp_snan(limits<Ty>::quiet_NaN(), limits<Ty>::quiet_NaN(), snan));
assert(test_lerp_snan(snan, limits<Ty>::quiet_NaN(), snan));
assert(test_lerp_snan(limits<Ty>::quiet_NaN(), snan, snan));
assert(test_lerp_snan(snan, snan, snan));
assert(test_lerp_snan(Ty{0}, Ty{0}, snan));
assert(test_lerp_snan(Ty{1}, Ty{1}, snan));
assert(test_lerp_snan(Ty{0}, snan, Ty{0}));
assert(test_lerp_snan(Ty{0}, snan, Ty{1}));
assert(test_lerp_snan(snan, Ty{0}, Ty{0}));
assert(test_lerp_snan(snan, Ty{0}, Ty{1}));
STATIC_ASSERT(cmp(lerp(Ty(1.0), Ty(2.0), Ty(4.0)), lerp(Ty(1.0), Ty(2.0), Ty(3.0))) * cmp(Ty(4.0), Ty(3.0))
* cmp(Ty(2.0), Ty(1.0))
>= 0);
assert(cmp(lerp(Ty(1.0), Ty(2.0), Ty(4.0)), lerp(Ty(1.0), Ty(2.0), Ty(3.0))) * cmp(Ty(4.0), Ty(3.0))
* cmp(Ty(2.0), Ty(1.0))
>= 0);
@ -803,9 +1076,13 @@ int main() {
STATIC_ASSERT(test_midpoint_floating_constexpr<double>());
STATIC_ASSERT(test_midpoint_floating_constexpr<long double>());
test_midpoint_floating_constexpr<float>();
test_midpoint_floating_constexpr<double>();
test_midpoint_floating_constexpr<long double>();
{
ExceptGuard except;
test_midpoint_floating_constexpr<float>();
test_midpoint_floating_constexpr<double>();
test_midpoint_floating_constexpr<long double>();
assert(check_feexcept(0));
}
test_midpoint_floating<float>();
test_midpoint_floating<double>();

Просмотреть файл

@ -685,6 +685,20 @@ STATIC_ASSERT(__cpp_lib_integer_sequence == 201304L);
STATIC_ASSERT(__cpp_lib_integral_constant_callable == 201304L);
#endif
#if _HAS_CXX20
#ifndef __cpp_lib_interpolate
#error __cpp_lib_interpolate is not defined
#elif __cpp_lib_interpolate != 201902L
#error __cpp_lib_interpolate is not 201902L
#else
STATIC_ASSERT(__cpp_lib_interpolate == 201902L);
#endif
#else
#ifdef __cpp_lib_interpolate
#error __cpp_lib_interpolate is defined
#endif
#endif
#ifndef __cpp_lib_invoke
#error __cpp_lib_invoke is not defined
#elif __cpp_lib_invoke != 201411L