diff --git a/stl/inc/cmath b/stl/inc/cmath index cf07a5b9424..5895847f4fd 100644 --- a/stl/inc/cmath +++ b/stl/inc/cmath @@ -12,6 +12,10 @@ #include #include +#if _HAS_CXX20 +#include +#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 -_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 - return _ArgA; - } - - // Opposite signs, choose the "infinity direction" according to T if it makes sense. - if (_ArgT <= 0) { + if (_STD is_constant_evaluated()) { + if (_STD _Is_nan(_ArgA)) { 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"); - } else { - return __builtin_nan("0"); - } - case 0b011: - // _ArgA is an infinity but _ArgB is not - if (_ArgT == 1) { - return _ArgB; + if (_STD _Is_nan(_ArgT)) { + return _ArgT; } - - if (_ArgT < 1) { - // towards the infinity, return it - return _ArgA; + } 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; } - // away from the infinity - return -_ArgA; - case 0b101: - // _ArgA is finite and _ArgB is an infinity - if (_ArgT == 0) { - return _ArgA; + if (_STD _Is_nan(_ArgT)) { + return _ArgT + _ArgT; } + } - if (_ArgT > 0) { - // toward the infinity - return _ArgB; + 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 { + // if _ArgT > 1: return infinity in the "direction" of _ArgB if that exists, NaN otherwise + return _ArgB - _ArgA; } - - 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); } diff --git a/stl/inc/numeric b/stl/inc/numeric index 134385db5e4..687bc8a988a 100644 --- a/stl/inc/numeric +++ b/stl/inc/numeric @@ -536,21 +536,16 @@ _CONSTEXPR20 void iota(_FwdIt _First, _FwdIt _Last, _Ty _Val) { #if _HAS_CXX17 // FUNCTION TEMPLATE _Abs_u -template -_NODISCARD constexpr auto _Abs_u(const _Arithmetic _Val) noexcept { +template +_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 && !is_same_v, 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; diff --git a/stl/inc/xutility b/stl/inc/xutility index 26299d2612e..f9ace7c254d 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -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 , 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 @@ -1135,19 +1139,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 > 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 @@ -1407,8 +1411,8 @@ _NODISCARD constexpr decltype(auto) _Get_unwrapped_n(_Iter&& _It, const _Diff _O template && 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>) @@ -1717,7 +1721,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; for (; 0 < _Off; --_Off) { @@ -1732,7 +1736,7 @@ _CONSTEXPR17 void _Advance1(_InIt& _Where, _Diff _Off, input_iterator_tag) { template _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; for (; 0 < _Off; --_Off) { @@ -1785,8 +1789,8 @@ template _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; @@ -4432,7 +4436,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::_Trivially_copyable>{})); @@ -4514,9 +4518,9 @@ _BidIt2 _Copy_backward_unchecked(_BidIt1 _First, _BidIt1 _Last, _BidIt2 _Dest, t template _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::_Trivially_copyable>{})); return _Dest; @@ -4965,7 +4969,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(_First1); const auto _First2_ch = reinterpret_cast(_First2); - const auto _Count = static_cast(reinterpret_cast(_Last1) - _First1_ch); + const auto _Count = static_cast(reinterpret_cast(_Last1) - _First1_ch); return _CSTD memcmp(_First1_ch, _First2_ch, _Count) == 0; } @@ -4974,7 +4978,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)); } @@ -5937,7 +5941,53 @@ struct _CXX17_DEPRECATE_ITERATOR_BASE_CLASS iterator { // base type for iterator using pointer = _Pointer; using reference = _Reference; }; + +// STRUCT TEMPLATE _Float_traits +template +struct _Float_traits { + static_assert(is_floating_point_v<_Ty>, "_Float_traits 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 { + using type = unsigned int; + + static constexpr type _Magnitude_mask = 0x7fff'ffffU; + static constexpr type _Exponent_mask = 0x7f80'0000U; +}; + +// FUNCTION TEMPLATE _Float_abs_bits +template , int> = 0> +_NODISCARD _CONSTEXPR_BIT_CAST auto _Float_abs_bits(const _Ty& _Xx) { + const auto _Bits = _Bit_cast::type>(_Xx); + return _Bits & _Float_traits<_Ty>::_Magnitude_mask; +} + +// FUNCTION TEMPLATE _Float_abs +template , 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 , 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 , 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) diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 860b9360d40..2b3d0d7cb77 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -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 diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index b2753dc3709..5c6ac5eb031 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -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 "" 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 @@ -779,6 +775,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. diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index 6f62c1c1b7d..1630f3d8c43 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -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 "" language.support\support.limits\support.limits.general\algorithm.version.pass.cpp language.support\support.limits\support.limits.general\functional.version.pass.cpp @@ -779,6 +775,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. diff --git a/tests/std/tests/P0811R3_midpoint_lerp/env.lst b/tests/std/tests/P0811R3_midpoint_lerp/env.lst index 642f530ffad..6d173ad8f69 100644 --- a/tests/std/tests/P0811R3_midpoint_lerp/env.lst +++ b/tests/std/tests/P0811R3_midpoint_lerp/env.lst @@ -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" diff --git a/tests/std/tests/P0811R3_midpoint_lerp/test.cpp b/tests/std/tests/P0811R3_midpoint_lerp/test.cpp index 211dd273a74..af6dedda278 100644 --- a/tests/std/tests/P0811R3_midpoint_lerp/test.cpp +++ b/tests/std/tests/P0811R3_midpoint_lerp/test.cpp @@ -1,12 +1,16 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#include #include +#include #include #include #include +#include #include #include +#include #include #include #include @@ -19,13 +23,16 @@ using namespace std; template using limits = numeric_limits; +// "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 @@ class RoundGuard { 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 -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(const bool sign, const unsigned long long payload) { - const unsigned int filteredPayload = payload & 0x7F'FFFFu; // bottom 23 bits +constexpr float mint_nan(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(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(result); } template <> -double mint_nan(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(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(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(result); } template <> -long double mint_nan(const bool sign, const unsigned long long payload) { +constexpr long double mint_nan(const bool sign, const unsigned long long payload) { return mint_nan(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::signaling_NaN() doesn't work on x86 hosted MSVC +// numeric_limits::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 struct constants; // not defined template <> struct constants { - 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 { template <> struct constants { - 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 { template <> struct constants { - 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::OnePlusUlp == nextafter(Ty(1.0), Ty(3.0))); assert(constants::PointFivePlusUlp == nextafter(Ty(0.5), Ty(3.0))); assert(constants::OneMinusUlp == nextafter(Ty(1.0), Ty(0.0))); + assert(constants::PointFiveMinusUlp == nextafter(Ty(0.5), Ty(-2.0))); assert(constants::NegOneMinusUlp == nextafter(Ty(-1.0), Ty(-2.0))); assert(constants::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::OnePlusUlp) == constants::OnePlusUlp); assert(midpoint(Ty(1.0), constants::OneMinusUlp) == Ty(1.0)); @@ -356,10 +426,13 @@ void test_midpoint_floating() { assert(midpoint(-limits::min(), -limits::max()) == -limits::max() / 2); assert(midpoint(-limits::denorm_min(), limits::max()) == limits::max() / 2); assert(midpoint(-limits::denorm_min(), -limits::max()) == -limits::max() / 2); + + assert(check_feexcept(0)); } // ditto for the other rounding modes: { + ExceptGuard except; RoundGuard round{FE_DOWNWARD}; assert(midpoint(Ty(1.0), constants::OnePlusUlp) == Ty(1.0)); assert(midpoint(Ty(1.0), constants::OneMinusUlp) == constants::OneMinusUlp); @@ -394,9 +467,12 @@ void test_midpoint_floating() { == nextafter(limits::max() / 2, limits::lowest())); assert(midpoint(-limits::denorm_min(), -limits::max()) == nextafter(-limits::max() / 2, limits::lowest())); + + assert(check_feexcept(0)); } { + ExceptGuard except; RoundGuard round{FE_TOWARDZERO}; assert(midpoint(Ty(1.0), constants::OnePlusUlp) == Ty(1.0)); assert(midpoint(Ty(1.0), constants::OneMinusUlp) == constants::OneMinusUlp); @@ -422,27 +498,49 @@ void test_midpoint_floating() { assert(midpoint(-limits::min(), -limits::max()) == -limits::max() / 2); assert(midpoint(-limits::denorm_min(), limits::max()) == nextafter(limits::max() / 2, Ty(0))); assert(midpoint(-limits::denorm_min(), -limits::max()) == -limits::max() / 2); + + assert(check_feexcept(0)); } #endif // _M_FP_STRICT - assert(midpoint(limits::denorm_min(), Ty(1.0)) == (limits::denorm_min() + Ty(1.0)) / Ty(2.0)); - assert(midpoint(limits::denorm_min(), limits::max()) - == (limits::denorm_min() + limits::max()) / Ty(2.0)); - assert(midpoint(limits::denorm_min(), limits::lowest()) - == (limits::denorm_min() + limits::lowest()) / Ty(2.0)); - assert(midpoint(limits::denorm_min(), limits::infinity()) == limits::infinity()); - assert(midpoint(limits::denorm_min(), -limits::infinity()) == -limits::infinity()); - - assert_bitwise_equal(mint_nan(0, 1), midpoint(mint_nan(0, 1), Ty(0))); - assert_bitwise_equal(mint_nan(0, 1), midpoint(Ty(0), mint_nan(0, 1))); - assert_bitwise_equal(mint_nan(0, 1), midpoint(mint_nan(0, 1), limits::max())); - assert_bitwise_equal(mint_nan(0, 1), midpoint(limits::max(), mint_nan(0, 1))); - assert_bitwise_equal(mint_nan(0, 1), midpoint(mint_nan(0, 1), mint_nan(0, 2))); - - assert(isnan(midpoint(-limits::infinity(), limits::infinity()))); - assert(isnan(midpoint(limits::quiet_NaN(), Ty(2.0)))); - assert(isnan(midpoint(Ty(2.0), limits::quiet_NaN()))); - assert(isnan(midpoint(limits::quiet_NaN(), limits::quiet_NaN()))); + { + ExceptGuard except; + + assert(midpoint(limits::denorm_min(), Ty(1.0)) == (limits::denorm_min() + Ty(1.0)) / Ty(2.0)); + assert(midpoint(limits::denorm_min(), limits::max()) + == (limits::denorm_min() + limits::max()) / Ty(2.0)); + assert(midpoint(limits::denorm_min(), limits::lowest()) + == (limits::denorm_min() + limits::lowest()) / Ty(2.0)); + assert(midpoint(limits::denorm_min(), limits::infinity()) == limits::infinity()); + assert(midpoint(limits::denorm_min(), -limits::infinity()) == -limits::infinity()); + + assert_bitwise_equal(mint_nan(0, 1), midpoint(mint_nan(0, 1), Ty(0))); + assert_bitwise_equal(mint_nan(0, 1), midpoint(Ty(0), mint_nan(0, 1))); + assert_bitwise_equal(mint_nan(0, 1), midpoint(mint_nan(0, 1), limits::max())); + assert_bitwise_equal(mint_nan(0, 1), midpoint(limits::max(), mint_nan(0, 1))); + assert_bitwise_equal(mint_nan(0, 1), midpoint(mint_nan(0, 1), mint_nan(0, 1))); + + assert(isnan(midpoint(limits::quiet_NaN(), Ty(2.0)))); + assert(isnan(midpoint(Ty(2.0), limits::quiet_NaN()))); + assert(isnan(midpoint(limits::quiet_NaN(), limits::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::infinity(), limits::infinity())); + assert(test_midpoint_fe_invalid(snan, limits::quiet_NaN())); + assert(test_midpoint_fe_invalid(limits::quiet_NaN(), snan)); + assert(test_midpoint_fe_invalid(snan, snan)); } template @@ -496,7 +594,7 @@ constexpr bool test_midpoint_pointer() { } template -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 expected_list[3] = {}; }; template struct LerpCases { // TRANSITION, VSO-934633 - static inline const LerpTestCase lerpTestCases[] = { + static inline constexpr LerpTestCase 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::denorm_min(), constants::NegOneMinusUlp, Ty(0.5), -constants::PointFivePlusUlp}, - {Ty(1.0), constants::OnePlusUlp, nextafter(Ty(0.5), Ty(-1.0)), Ty(1.0)}, - {Ty(-1.0), constants::NegOneMinusUlp, nextafter(Ty(0.5), Ty(-1.0)), Ty(-1.0)}, + {Ty(1.0), constants::OnePlusUlp, constants::PointFiveMinusUlp, Ty(1.0)}, + {Ty(-1.0), constants::NegOneMinusUlp, constants::PointFiveMinusUlp, Ty(-1.0)}, {Ty(1.0), constants::OnePlusUlp, Ty(0.5), Ty(1.0)}, {Ty(-1.0), constants::NegOneMinusUlp, Ty(0.5), Ty(-1.0)}, @@ -586,8 +685,6 @@ struct LerpCases { // TRANSITION, VSO-934633 {Ty(1.0), Ty(2.0), constants::TwoPlusUlp, constants::TwoPlusUlp + Ty(1.0)}, {Ty(1.0), Ty(2.0), Ty(0.5), constants::PointFivePlusUlp + Ty(1.0)}, - {limits::lowest(), limits::max(), Ty(2.0), limits::infinity()}, - {limits::max(), limits::lowest(), Ty(2.0), -limits::infinity()}, {limits::max(), limits::max(), Ty(2.0), limits::max()}, {limits::lowest(), limits::lowest(), Ty(2.0), limits::lowest()}, @@ -598,84 +695,192 @@ struct LerpCases { // TRANSITION, VSO-934633 {limits::denorm_min(), limits::infinity(), Ty(0.5), limits::infinity()}, {limits::denorm_min(), -limits::infinity(), Ty(0.5), -limits::infinity()}, - // the following handling of NaNs and infinities isn't in the spec, but seems like the right behavior: + // the following handling of 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 + // if the values differ and T is an infinity, the appropriate infinity according to direction + {Ty(0), Ty(1), limits::infinity(), limits::infinity()}, + {Ty(0), Ty(1), -limits::infinity(), -limits::infinity()}, + {Ty(0), -Ty(1), limits::infinity(), -limits::infinity()}, + {Ty(0), -Ty(1), -limits::infinity(), limits::infinity()}, + + // when the inputs are infinity of the same sign and 0 < T < 1, return that infinity + {limits::infinity(), limits::infinity(), limits::denorm_min(), limits::infinity()}, + {-limits::infinity(), -limits::infinity(), limits::denorm_min(), -limits::infinity()}, + {limits::infinity(), limits::infinity(), Ty(0.5), limits::infinity()}, + {-limits::infinity(), -limits::infinity(), Ty(0.5), -limits::infinity()}, + {limits::infinity(), limits::infinity(), constants::OneMinusUlp, limits::infinity()}, + {-limits::infinity(), -limits::infinity(), constants::OneMinusUlp, -limits::infinity()}, + + // when the inputs are infinity of opposite signs and T > 1, return the second parameter {-limits::infinity(), limits::infinity(), constants::OnePlusUlp, limits::infinity()}, {limits::infinity(), -limits::infinity(), constants::OnePlusUlp, -limits::infinity()}, - {-limits::infinity(), limits::infinity(), Ty(1.0), limits::infinity()}, - {limits::infinity(), -limits::infinity(), Ty(1.0), -limits::infinity()}, + {-limits::infinity(), limits::infinity(), Ty(2.0), limits::infinity()}, + {limits::infinity(), -limits::infinity(), Ty(2.0), -limits::infinity()}, + {-limits::infinity(), limits::infinity(), limits::max(), limits::infinity()}, + {limits::infinity(), -limits::infinity(), limits::max(), -limits::infinity()}, + {-limits::infinity(), limits::infinity(), limits::infinity(), limits::infinity()}, + {limits::infinity(), -limits::infinity(), limits::infinity(), -limits::infinity()}, - // when the inputs are infinity and T is 0 or less, return the first parameter + // when the inputs are infinity of opposite signs and T < 0, return the first parameter + {-limits::infinity(), limits::infinity(), -limits::infinity(), -limits::infinity()}, + {limits::infinity(), -limits::infinity(), -limits::infinity(), limits::infinity()}, + {-limits::infinity(), limits::infinity(), -limits::max(), -limits::infinity()}, + {limits::infinity(), -limits::infinity(), -limits::max(), limits::infinity()}, + {-limits::infinity(), limits::infinity(), -Ty(2.0), -limits::infinity()}, + {limits::infinity(), -limits::infinity(), -Ty(2.0), limits::infinity()}, {-limits::infinity(), limits::infinity(), -limits::denorm_min(), -limits::infinity()}, {limits::infinity(), -limits::infinity(), -limits::denorm_min(), limits::infinity()}, - {-limits::infinity(), limits::infinity(), Ty(0), -limits::infinity()}, - {limits::infinity(), -limits::infinity(), Ty(0), limits::infinity()}, - - // if any of the inputs are NaN, return the first NaN - {mint_nan(0, 42), mint_nan(1, 42), mint_nan(0, 1729), mint_nan(0, 42)}, - {Ty(1.0), mint_nan(1, 42), mint_nan(0, 1729), mint_nan(1, 42)}, - {mint_nan(1, 42), Ty(1.0), mint_nan(0, 1729), mint_nan(1, 42)}, - {Ty(1.0), Ty(1.0), mint_nan(0, 1729), mint_nan(0, 1729)}, - - {limits::infinity(), mint_nan(1, 42), mint_nan(0, 1729), mint_nan(1, 42)}, - {mint_nan(1, 42), limits::infinity(), mint_nan(0, 1729), mint_nan(1, 42)}, - {limits::infinity(), limits::infinity(), mint_nan(0, 1729), mint_nan(0, 1729)}, - // if the values differ and T is an infinity, the appropriate infinity according to direction - {Ty(0), Ty(1), limits::infinity(), limits::infinity()}, - {Ty(0), Ty(1), -limits::infinity(), -limits::infinity()}, - - {Ty(0), -Ty(1), limits::infinity(), -limits::infinity()}, - {Ty(0), -Ty(1), -limits::infinity(), limits::infinity()}, + // if a is an infinity, b is finite and T != 1, return that infinity or the other according to "direction" of t + {limits::infinity(), Ty(1.0), -limits::infinity(), limits::infinity()}, + {limits::infinity(), Ty(1.0), -limits::max(), limits::infinity()}, + {limits::infinity(), Ty(1.0), -Ty(1.0), limits::infinity()}, + {limits::infinity(), Ty(1.0), -limits::denorm_min(), limits::infinity()}, + {limits::infinity(), Ty(1.0), -Ty(0.0), limits::infinity()}, + {limits::infinity(), Ty(1.0), Ty(0.0), limits::infinity()}, + {limits::infinity(), Ty(1.0), limits::denorm_min(), limits::infinity()}, + {limits::infinity(), Ty(1.0), Ty(0.5), limits::infinity()}, + {limits::infinity(), Ty(1.0), constants::OneMinusUlp, limits::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::infinity(), Ty(1.0), Ty(1.0), Ty(1.0)}, {limits::infinity(), Ty(1.0), constants::OnePlusUlp, -limits::infinity()}, - {limits::infinity(), Ty(1.0), constants::OneMinusUlp, limits::infinity()}, - {limits::infinity(), Ty(1.0), limits::denorm_min(), limits::infinity()}, - {limits::infinity(), Ty(1.0), Ty(0.0), limits::infinity()}, + {limits::infinity(), Ty(1.0), Ty(2.0), -limits::infinity()}, + {limits::infinity(), Ty(1.0), limits::max(), -limits::infinity()}, + {limits::infinity(), Ty(1.0), limits::infinity(), -limits::infinity()}, + + {-limits::infinity(), Ty(1.0), -limits::infinity(), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), -limits::max(), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), -Ty(1.0), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), -limits::denorm_min(), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), -Ty(0.0), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), Ty(0.0), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), limits::denorm_min(), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), Ty(0.5), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), constants::OneMinusUlp, -limits::infinity()}, - {-limits::infinity(), Ty(1.0), Ty(1.0), Ty(1.0)}, {-limits::infinity(), Ty(1.0), constants::OnePlusUlp, limits::infinity()}, - {-limits::infinity(), Ty(1.0), constants::OneMinusUlp, -limits::infinity()}, - {-limits::infinity(), Ty(1.0), limits::denorm_min(), -limits::infinity()}, - {-limits::infinity(), Ty(1.0), Ty(0.0), -limits::infinity()}, + {-limits::infinity(), Ty(1.0), Ty(2.0), limits::infinity()}, + {-limits::infinity(), Ty(1.0), limits::max(), limits::infinity()}, + {-limits::infinity(), Ty(1.0), limits::infinity(), limits::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::infinity(), -limits::infinity(), -limits::infinity()}, + {Ty(1.0), limits::infinity(), -limits::max(), -limits::infinity()}, + {Ty(1.0), limits::infinity(), -Ty(1.0), -limits::infinity()}, + {Ty(1.0), limits::infinity(), -limits::denorm_min(), -limits::infinity()}, - {Ty(1.0), limits::infinity(), Ty(0.0), Ty(1.0)}, - {Ty(1.0), limits::infinity(), -Ty(0.0), Ty(1.0)}, {Ty(1.0), limits::infinity(), limits::denorm_min(), limits::infinity()}, - {Ty(1.0), limits::infinity(), -limits::denorm_min(), -limits::infinity()}, + {Ty(1.0), limits::infinity(), Ty(0.5), limits::infinity()}, + {Ty(1.0), limits::infinity(), constants::OneMinusUlp, limits::infinity()}, + {Ty(1.0), limits::infinity(), Ty(1.0), limits::infinity()}, + {Ty(1.0), limits::infinity(), constants::OnePlusUlp, limits::infinity()}, + {Ty(1.0), limits::infinity(), Ty(2.0), limits::infinity()}, + {Ty(1.0), limits::infinity(), limits::max(), limits::infinity()}, + {Ty(1.0), limits::infinity(), limits::infinity(), limits::infinity()}, + + {Ty(1.0), -limits::infinity(), -limits::infinity(), limits::infinity()}, + {Ty(1.0), -limits::infinity(), -limits::max(), limits::infinity()}, + {Ty(1.0), -limits::infinity(), -Ty(1.0), limits::infinity()}, + {Ty(1.0), -limits::infinity(), -limits::denorm_min(), limits::infinity()}, - {Ty(1.0), -limits::infinity(), Ty(0.0), Ty(1.0)}, - {Ty(1.0), -limits::infinity(), -Ty(0.0), Ty(1.0)}, {Ty(1.0), -limits::infinity(), limits::denorm_min(), -limits::infinity()}, - {Ty(1.0), -limits::infinity(), -limits::denorm_min(), limits::infinity()}, + {Ty(1.0), -limits::infinity(), Ty(0.5), -limits::infinity()}, + {Ty(1.0), -limits::infinity(), constants::OneMinusUlp, -limits::infinity()}, + {Ty(1.0), -limits::infinity(), Ty(1.0), -limits::infinity()}, + {Ty(1.0), -limits::infinity(), constants::OnePlusUlp, -limits::infinity()}, + {Ty(1.0), -limits::infinity(), Ty(2.0), -limits::infinity()}, + {Ty(1.0), -limits::infinity(), limits::max(), -limits::infinity()}, + {Ty(1.0), -limits::infinity(), limits::infinity(), -limits::infinity()}, + }; - // if all 3 values are infinities, choose the selected one according to t - {limits::infinity(), limits::infinity(), limits::infinity(), limits::infinity()}, - {limits::infinity(), limits::infinity(), -limits::infinity(), limits::infinity()}, - {limits::infinity(), -limits::infinity(), limits::infinity(), -limits::infinity()}, - {limits::infinity(), -limits::infinity(), -limits::infinity(), limits::infinity()}, - {-limits::infinity(), limits::infinity(), limits::infinity(), limits::infinity()}, - {-limits::infinity(), limits::infinity(), -limits::infinity(), -limits::infinity()}, - {-limits::infinity(), -limits::infinity(), limits::infinity(), -limits::infinity()}, - {-limits::infinity(), -limits::infinity(), -limits::infinity(), -limits::infinity()}, + static inline constexpr LerpTestCase lerpOverflowTestCases[] = { + {limits::lowest(), limits::max(), Ty(2.0), limits::infinity()}, + {limits::max(), limits::lowest(), Ty(2.0), -limits::infinity()}, }; - static inline constexpr LerpNaNTestCase lerpNaNTestCases[] = { - // when the inputs are infinity and T is between 0 and 1, return NaN + static inline constexpr LerpNaNTestCase lerpInvalidTestCases[] = { + // if the values are equal and T is an infinity, NaN + {Ty(0), Ty(0), limits::infinity()}, + {Ty(0), Ty(0), -limits::infinity()}, + + // when the inputs are infinity of the same sign and T <= 0, return NaN + {limits::infinity(), limits::infinity(), -limits::infinity()}, + {-limits::infinity(), -limits::infinity(), -limits::infinity()}, + {limits::infinity(), limits::infinity(), -limits::max()}, + {-limits::infinity(), -limits::infinity(), -limits::max()}, + {limits::infinity(), limits::infinity(), -Ty(1.0)}, + {-limits::infinity(), -limits::infinity(), -Ty(1.0)}, + {limits::infinity(), limits::infinity(), -limits::denorm_min()}, + {-limits::infinity(), -limits::infinity(), -limits::denorm_min()}, + {limits::infinity(), limits::infinity(), -Ty(0.0)}, + {-limits::infinity(), -limits::infinity(), -Ty(0.0)}, + {limits::infinity(), limits::infinity(), Ty(0.0)}, + {-limits::infinity(), -limits::infinity(), Ty(0.0)}, + + // when the inputs are infinity of the same sign and T >= 1, return NaN + {limits::infinity(), limits::infinity(), Ty(1.0)}, + {-limits::infinity(), -limits::infinity(), Ty(1.0)}, + {limits::infinity(), limits::infinity(), constants::OnePlusUlp}, + {-limits::infinity(), -limits::infinity(), constants::OnePlusUlp}, + {limits::infinity(), limits::infinity(), Ty(2.0)}, + {-limits::infinity(), -limits::infinity(), Ty(2.0)}, + {limits::infinity(), limits::infinity(), limits::max()}, + {-limits::infinity(), -limits::infinity(), limits::max()}, + {limits::infinity(), limits::infinity(), limits::infinity()}, + {-limits::infinity(), -limits::infinity(), limits::infinity()}, + + // when the inputs are infinity of opposite signs and 0 <= T <= 1, return NaN + {-limits::infinity(), limits::infinity(), -Ty(0.0)}, + {limits::infinity(), -limits::infinity(), -Ty(0.0)}, + {-limits::infinity(), limits::infinity(), Ty(0.0)}, + {limits::infinity(), -limits::infinity(), Ty(0.0)}, {-limits::infinity(), limits::infinity(), limits::denorm_min()}, {limits::infinity(), -limits::infinity(), limits::denorm_min()}, - {-limits::infinity(), limits::infinity(), constants::OneMinusUlp}, - {limits::infinity(), -limits::infinity(), constants::OneMinusUlp}, {-limits::infinity(), limits::infinity(), Ty(0.5)}, {limits::infinity(), -limits::infinity(), Ty(0.5)}, + {-limits::infinity(), limits::infinity(), constants::OneMinusUlp}, + {limits::infinity(), -limits::infinity(), constants::OneMinusUlp}, + {-limits::infinity(), limits::infinity(), Ty(1.0)}, + {limits::infinity(), -limits::infinity(), Ty(1.0)}, + + // if a is an infinity, b is finite and T = 1, return NaN + {limits::infinity(), Ty(1.0), Ty(1.0)}, + {-limits::infinity(), Ty(1.0), Ty(1.0)}, + + // if b is an infinity, a is finite and T = 0, return NaN + {Ty(1.0), limits::infinity(), Ty(0.0)}, + {Ty(1.0), limits::infinity(), -Ty(0.0)}, + {Ty(1.0), -limits::infinity(), Ty(0.0)}, + {Ty(1.0), -limits::infinity(), -Ty(0.0)}, + }; - // if the values are equal and T is an infinity, NaN - {Ty(0), Ty(0), limits::infinity()}, - {Ty(0), Ty(0), -limits::infinity()}, + static inline constexpr LerpNaNTestCase lerpNaNTestCases[] = { + {mint_nan(0, 42), mint_nan(1, 42), mint_nan(0, 1729), + {mint_nan(0, 42), mint_nan(1, 42), mint_nan(0, 1729)}}, + {Ty(1.0), mint_nan(1, 42), mint_nan(0, 1729), {mint_nan(1, 42), mint_nan(0, 1729)}}, + {mint_nan(1, 42), Ty(1.0), mint_nan(0, 1729), {mint_nan(1, 42), mint_nan(0, 1729)}}, + {Ty(1.0), Ty(1.0), mint_nan(0, 1729), {mint_nan(0, 1729)}}, + + {limits::infinity(), mint_nan(1, 42), mint_nan(0, 1729), + {mint_nan(1, 42), mint_nan(0, 1729)}}, + {mint_nan(1, 42), limits::infinity(), mint_nan(0, 1729), + {mint_nan(1, 42), mint_nan(0, 1729)}}, + {limits::infinity(), limits::infinity(), mint_nan(0, 1729), {mint_nan(0, 1729)}}, + + {mint_nan(0, 42), mint_nan(1, 42), -Ty(0.0), {mint_nan(0, 42), mint_nan(1, 42)}}, + {mint_nan(0, 42), mint_nan(1, 42), Ty(0.0), {mint_nan(0, 42), mint_nan(1, 42)}}, + {mint_nan(0, 42), mint_nan(1, 42), Ty(1.0), {mint_nan(0, 42), mint_nan(1, 42)}}, + {mint_nan(0, 42), Ty(1.0), -Ty(0.0), {mint_nan(0, 42)}}, + {mint_nan(0, 42), Ty(1.0), Ty(0.0), {mint_nan(0, 42)}}, + {mint_nan(0, 42), Ty(1.0), Ty(1.0), {mint_nan(0, 42)}}, + {mint_nan(0, 42), limits::infinity(), -Ty(0.0), {mint_nan(0, 42)}}, + {mint_nan(0, 42), limits::infinity(), Ty(0.0), {mint_nan(0, 42)}}, + {mint_nan(0, 42), limits::infinity(), Ty(1.0), {mint_nan(0, 42)}}, + {Ty(1.0), mint_nan(1, 42), -Ty(0.0), {mint_nan(1, 42)}}, + {Ty(1.0), mint_nan(1, 42), Ty(0.0), {mint_nan(1, 42)}}, + {Ty(1.0), mint_nan(1, 42), Ty(1.0), {mint_nan(1, 42)}}, + {limits::infinity(), mint_nan(1, 42), -Ty(0.0), {mint_nan(1, 42)}}, + {limits::infinity(), mint_nan(1, 42), Ty(0.0), {mint_nan(1, 42)}}, + {limits::infinity(), mint_nan(1, 42), Ty(1.0), {mint_nan(1, 42)}}, }; }; @@ -730,25 +935,93 @@ bool test_lerp() { STATIC_ASSERT(is_signed_v); 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; + + for (const auto& testCase : LerpCases::lerpTestCases) { + const auto answer = lerp(testCase.x, testCase.y, testCase.t); + assert(bit_cast(answer) == bit_cast(testCase.expected)); + } + + for (auto&& testCase : LerpCases::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(answer) == bit_cast(expected.value()); + })); + } + + return true; + }; + + STATIC_ASSERT(test_lerp_constexpr()); for (auto&& testCase : LerpCases::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::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::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::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::quiet_NaN(), limits::quiet_NaN())); + assert(test_lerp_snan(limits::quiet_NaN(), snan, limits::quiet_NaN())); + assert(test_lerp_snan(snan, snan, limits::quiet_NaN())); + assert(test_lerp_snan(limits::quiet_NaN(), limits::quiet_NaN(), snan)); + assert(test_lerp_snan(snan, limits::quiet_NaN(), snan)); + assert(test_lerp_snan(limits::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()); STATIC_ASSERT(test_midpoint_floating_constexpr()); - test_midpoint_floating_constexpr(); - test_midpoint_floating_constexpr(); - test_midpoint_floating_constexpr(); + { + ExceptGuard except; + test_midpoint_floating_constexpr(); + test_midpoint_floating_constexpr(); + test_midpoint_floating_constexpr(); + assert(check_feexcept(0)); + } test_midpoint_floating(); test_midpoint_floating(); diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.cpp index 3c374328a10..e1fc7753df4 100644 --- a/tests/std/tests/VSO_0157762_feature_test_macros/test.cpp +++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.cpp @@ -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