From fd1590b21a18b005c20a4fe6b3ba17d77e4c989a Mon Sep 17 00:00:00 2001 From: statementreply Date: Thu, 16 Jul 2020 23:19:32 +0800 Subject: [PATCH 01/12] Finish P0811R3 midpoint(), lerp() * Removes workaround for missing `bit_cast()` and mark `lerp()` constexpr. * Changes how `lerp()` handles infinity inputs according to https://github.com/microsoft/STL/issues/65#issuecomment-563811523 and https://github.com/microsoft/STL/issues/65#issuecomment-564102550. --- stl/inc/cmath | 109 +++++++++++++++++------------------------------ stl/inc/numeric | 37 ++++++++-------- stl/inc/xutility | 79 ++++++++++++++++++++++++++++++++-- 3 files changed, 133 insertions(+), 92 deletions(-) diff --git a/stl/inc/cmath b/stl/inc/cmath index cf07a5b9424..bd0c6d929d9 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,64 @@ _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; } + } else { + if (_STD _Is_nan(_ArgA)) { + if (_STD _Is_nan(_ArgB) || _STD _Is_nan(_ArgT)) { + (void) (_ArgB + _ArgT); + } - if (_ArgT < 1) { - // towards the infinity, return it - return _ArgA; + return _ArgA + _ArgA; } - // 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(_ArgB) || _STD _Is_nan(_ArgT)) { + return _ArgB + _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..eaf3c7e150a 100644 --- a/stl/inc/numeric +++ b/stl/inc/numeric @@ -540,14 +540,9 @@ template _NODISCARD constexpr auto _Abs_u(const _Arithmetic _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<_Arithmetic>); - return _Val; - } else if constexpr (is_signed_v<_Arithmetic>) { + if constexpr (is_signed_v<_Arithmetic>) { using _Unsigned = make_unsigned_t<_Arithmetic>; if (_Val < 0) { // note static_cast to _Unsigned such that _Arithmetic == short returns unsigned short rather than int @@ -619,9 +614,23 @@ _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)) { + 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,16 +646,6 @@ _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. diff --git a/stl/inc/xutility b/stl/inc/xutility index 26299d2612e..87d01f9d6ee 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 @@ -5937,7 +5941,74 @@ struct _CXX17_DEPRECATE_ITERATOR_BASE_CLASS iterator { // base type for iterator using pointer = _Pointer; using reference = _Reference; }; + +// STRUCT TEMPLATE _Float_bit_traits +template +struct _Float_bit_traits; + +template <> +struct _Float_bit_traits { + using _Float_type = float; + using _Bit_type = unsigned int; + + static constexpr _Bit_type _Magnitude_mask = 0x7fff'ffffU; + static constexpr _Bit_type _Exponent_mask = 0x7f80'0000U; +}; + +template <> +struct _Float_bit_traits { + using _Float_type = double; + using _Bit_type = unsigned long long; + + static constexpr _Bit_type _Magnitude_mask = 0x7fff'ffff'ffff'ffffULL; + static constexpr _Bit_type _Exponent_mask = 0x7ff0'0000'0000'0000ULL; +}; + +template <> +struct _Float_bit_traits { + using _Float_type = long double; + using _Bit_type = unsigned long long; + + static constexpr _Bit_type _Magnitude_mask = 0x7fff'ffff'ffff'ffffULL; + static constexpr _Bit_type _Exponent_mask = 0x7ff0'0000'0000'0000ULL; +}; + +template +struct _Float_bit_traits : public _Float_bit_traits<_Ty> {}; + +template +struct _Float_bit_traits : public _Float_bit_traits<_Ty> {}; + +template +struct _Float_bit_traits : public _Float_bit_traits<_Ty> {}; + +// FUNCTION TEMPLATE _Float_abs_bits +template ::value, int> = 0> +_NODISCARD _CONSTEXPR_BIT_CAST auto _Float_abs_bits(const _Ty& _Xx) { + using _Bit_type = typename _Float_bit_traits<_Ty>::_Bit_type; + const auto _Bits = _Bit_cast<_Bit_type>(_Xx); + return static_cast<_Bit_type>(_Bits & _Float_bit_traits<_Ty>::_Magnitude_mask); +} + +// FUNCTION TEMPLATE _Float_abs +template ::value, 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 ::value, int> = 0> +_NODISCARD _CONSTEXPR_BIT_CAST bool _Is_nan(const _Ty _Xx) { // constexpr isnan() + return _Float_abs_bits(_Xx) > _Float_bit_traits<_Ty>::_Exponent_mask; +} + +// FUNCTION TEMPLATE _Is_finite +template ::value, int> = 0> +_NODISCARD _CONSTEXPR_BIT_CAST bool _Is_finite(const _Ty _Xx) { // constexpr isfinite() + return _Float_abs_bits(_Xx) < _Float_bit_traits<_Ty>::_Exponent_mask; +} _STD_END +#undef _CONSTEXPR_BIT_CAST #pragma pop_macro("new") _STL_RESTORE_CLANG_WARNINGS #pragma warning(pop) From d554f604f75e035f19108a89735f252be9d723bb Mon Sep 17 00:00:00 2001 From: statementreply Date: Thu, 16 Jul 2020 23:39:02 +0800 Subject: [PATCH 02/12] Update tests for lerp() * Adds constexpr tests. * Updates test cases for infinity inputs according to the new behavior. --- .../std/tests/P0811R3_midpoint_lerp/test.cpp | 348 +++++++++++++----- 1 file changed, 250 insertions(+), 98 deletions(-) diff --git a/tests/std/tests/P0811R3_midpoint_lerp/test.cpp b/tests/std/tests/P0811R3_midpoint_lerp/test.cpp index 211dd273a74..47a98eaeb57 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 @@ -54,46 +58,40 @@ class RoundGuard { #endif // _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); } @@ -107,12 +105,13 @@ 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 +119,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 +133,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 +151,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))); @@ -496,7 +498,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 +521,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 +567,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 +589,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 +599,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,8 +839,29 @@ 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?). + const 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); + if (bit_cast(answer) != bit_cast(testCase.expected)) { + return false; + } + } + + for (auto&& testCase : LerpCases::lerpNaNTestCases) { + const auto answer = lerp(testCase.x, testCase.y, testCase.t); + if (none_of(begin(testCase.expected_list), end(testCase.expected_list), [&](const auto& expected) { + return expected.has_value() && bit_cast(answer) == bit_cast(expected.value()); + })) { + return false; + } + } + + return true; + }; + + STATIC_ASSERT(test_lerp_constexpr()); for (auto&& testCase : LerpCases::lerpTestCases) { const auto answer = lerp(testCase.x, testCase.y, testCase.t); @@ -741,7 +871,15 @@ bool test_lerp() { } } - for (auto&& testCase : LerpCases::lerpNaNTestCases) { + for (auto&& testCase : LerpCases::lerpOverflowTestCases) { + const auto answer = lerp(testCase.x, testCase.y, testCase.t); + if (memcmp(&answer, &testCase.expected, sizeof(Ty)) != 0) { + print_lerp_result(testCase, answer); + abort(); + } + } + + for (auto&& testCase : LerpCases::lerpInvalidTestCases) { const auto answer = lerp(testCase.x, testCase.y, testCase.t); if (!isnan(answer)) { print_lerp_result(testCase, answer); @@ -749,6 +887,20 @@ bool test_lerp() { } } + for (auto&& testCase : LerpCases::lerpNaNTestCases) { + const auto answer = lerp(testCase.x, testCase.y, testCase.t); + if (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(); + } + } + + 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); From e3a405ad1821078cfcbda43dd13d9e3deb71c88d Mon Sep 17 00:00:00 2001 From: statementreply Date: Thu, 16 Jul 2020 23:59:18 +0800 Subject: [PATCH 03/12] Apply clang-format clang-format has seemingly changed its mind on some unrelated code :\ --- stl/inc/xutility | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/stl/inc/xutility b/stl/inc/xutility index 87d01f9d6ee..9a1b5c96904 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -1139,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 @@ -1411,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>) @@ -1721,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) { @@ -1736,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) { @@ -1789,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; @@ -4436,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>{})); @@ -4518,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; @@ -4969,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; } @@ -4978,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)); } From e5c0662c40ea768bd342e62a45893813705ed874 Mon Sep 17 00:00:00 2001 From: statementreply Date: Wed, 22 Jul 2020 21:02:09 +0800 Subject: [PATCH 04/12] Use template variable --- stl/inc/xutility | 61 +++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/stl/inc/xutility b/stl/inc/xutility index 9a1b5c96904..e3dcbed296b 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -5942,52 +5942,55 @@ struct _CXX17_DEPRECATE_ITERATOR_BASE_CLASS iterator { // base type for iterator using reference = _Reference; }; -// STRUCT TEMPLATE _Float_bit_traits +// STRUCT TEMPLATE _Float_bits template -struct _Float_bit_traits; +struct _Float_bits { + static_assert(_Always_false<_Ty>, "_Float_bits is invalid"); +}; template <> -struct _Float_bit_traits { - using _Float_type = float; - using _Bit_type = unsigned int; - - static constexpr _Bit_type _Magnitude_mask = 0x7fff'ffffU; - static constexpr _Bit_type _Exponent_mask = 0x7f80'0000U; +struct _Float_bits { + using type = unsigned int; }; template <> -struct _Float_bit_traits { - using _Float_type = double; - using _Bit_type = unsigned long long; - - static constexpr _Bit_type _Magnitude_mask = 0x7fff'ffff'ffff'ffffULL; - static constexpr _Bit_type _Exponent_mask = 0x7ff0'0000'0000'0000ULL; +struct _Float_bits { + using type = unsigned long long; }; template <> -struct _Float_bit_traits { - using _Float_type = long double; - using _Bit_type = unsigned long long; - - static constexpr _Bit_type _Magnitude_mask = 0x7fff'ffff'ffff'ffffULL; - static constexpr _Bit_type _Exponent_mask = 0x7ff0'0000'0000'0000ULL; +struct _Float_bits { + using type = _Float_bits::type; }; template -struct _Float_bit_traits : public _Float_bit_traits<_Ty> {}; +using _Float_bits_t = typename _Float_bits<_Ty>::type; template -struct _Float_bit_traits : public _Float_bit_traits<_Ty> {}; - +_INLINE_VAR constexpr _Float_bits_t<_Ty> _Float_magnitude_mask{}; template -struct _Float_bit_traits : public _Float_bit_traits<_Ty> {}; +_INLINE_VAR constexpr _Float_bits_t<_Ty> _Float_exponent_mask{}; + +template <> +_INLINE_VAR constexpr _Float_bits_t _Float_magnitude_mask = 0x7fff'ffffU; +template <> +_INLINE_VAR constexpr _Float_bits_t _Float_exponent_mask = 0x7f80'0000U; + +template <> +_INLINE_VAR constexpr _Float_bits_t _Float_magnitude_mask = 0x7fff'ffff'ffff'ffffULL; +template <> +_INLINE_VAR constexpr _Float_bits_t _Float_exponent_mask = 0x7ff0'0000'0000'0000ULL; + +template <> +_INLINE_VAR constexpr _Float_bits_t _Float_magnitude_mask = _Float_magnitude_mask; +template <> +_INLINE_VAR constexpr _Float_bits_t _Float_exponent_mask = _Float_exponent_mask; // FUNCTION TEMPLATE _Float_abs_bits template ::value, int> = 0> _NODISCARD _CONSTEXPR_BIT_CAST auto _Float_abs_bits(const _Ty& _Xx) { - using _Bit_type = typename _Float_bit_traits<_Ty>::_Bit_type; - const auto _Bits = _Bit_cast<_Bit_type>(_Xx); - return static_cast<_Bit_type>(_Bits & _Float_bit_traits<_Ty>::_Magnitude_mask); + const auto _Bits = _Bit_cast<_Float_bits_t<_Ty>>(_Xx); + return static_cast<_Float_bits_t<_Ty>>(_Bits & _Float_magnitude_mask<_Ty>); } // FUNCTION TEMPLATE _Float_abs @@ -5999,13 +6002,13 @@ _NODISCARD _CONSTEXPR_BIT_CAST _Ty _Float_abs(const _Ty _Xx) { // constexpr floa // FUNCTION TEMPLATE _Is_nan template ::value, int> = 0> _NODISCARD _CONSTEXPR_BIT_CAST bool _Is_nan(const _Ty _Xx) { // constexpr isnan() - return _Float_abs_bits(_Xx) > _Float_bit_traits<_Ty>::_Exponent_mask; + return _Float_abs_bits(_Xx) > _Float_exponent_mask<_Ty>; } // FUNCTION TEMPLATE _Is_finite template ::value, int> = 0> _NODISCARD _CONSTEXPR_BIT_CAST bool _Is_finite(const _Ty _Xx) { // constexpr isfinite() - return _Float_abs_bits(_Xx) < _Float_bit_traits<_Ty>::_Exponent_mask; + return _Float_abs_bits(_Xx) < _Float_exponent_mask<_Ty>; } _STD_END #undef _CONSTEXPR_BIT_CAST From e2f612b5f102d6af317ec1e48865d128edbb3cb7 Mon Sep 17 00:00:00 2001 From: statementreply Date: Wed, 22 Jul 2020 21:04:55 +0800 Subject: [PATCH 05/12] Use assert in constexpr test --- tests/std/tests/P0811R3_midpoint_lerp/test.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/std/tests/P0811R3_midpoint_lerp/test.cpp b/tests/std/tests/P0811R3_midpoint_lerp/test.cpp index 47a98eaeb57..4712231876a 100644 --- a/tests/std/tests/P0811R3_midpoint_lerp/test.cpp +++ b/tests/std/tests/P0811R3_midpoint_lerp/test.cpp @@ -839,23 +839,19 @@ bool test_lerp() { STATIC_ASSERT(is_signed_v); STATIC_ASSERT(noexcept(lerp(Ty(), Ty(), Ty()))); - const auto test_lerp_constexpr = [] { + 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); - if (bit_cast(answer) != bit_cast(testCase.expected)) { - return false; - } + assert(bit_cast(answer) == bit_cast(testCase.expected)); } for (auto&& testCase : LerpCases::lerpNaNTestCases) { const auto answer = lerp(testCase.x, testCase.y, testCase.t); - if (none_of(begin(testCase.expected_list), end(testCase.expected_list), [&](const auto& expected) { - return expected.has_value() && bit_cast(answer) == bit_cast(expected.value()); - })) { - return false; - } + 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; From 033632e971174fa5a926d3dd31395984e4281f95 Mon Sep 17 00:00:00 2001 From: statementreply Date: Thu, 23 Jul 2020 21:26:12 +0800 Subject: [PATCH 06/12] Add comments for sNaN handling code --- stl/inc/cmath | 2 ++ stl/inc/numeric | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/stl/inc/cmath b/stl/inc/cmath index bd0c6d929d9..d33041360fe 100644 --- a/stl/inc/cmath +++ b/stl/inc/cmath @@ -1289,6 +1289,8 @@ _NODISCARD constexpr _Ty _Common_lerp(const _Ty _ArgA, const _Ty _ArgB, const _T } } else { if (_STD _Is_nan(_ArgA)) { + // raise FE_INVALID if at least one of _ArgA, _ArgB and _ArgT is signaling NaN + if (_STD _Is_nan(_ArgB) || _STD _Is_nan(_ArgT)) { (void) (_ArgB + _ArgT); } diff --git a/stl/inc/numeric b/stl/inc/numeric index eaf3c7e150a..4cf75dede6e 100644 --- a/stl/inc/numeric +++ b/stl/inc/numeric @@ -624,6 +624,7 @@ _NODISCARD constexpr _Ty midpoint(const _Ty _Val1, const _Ty _Val2) noexcept { } } 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; } } @@ -651,7 +652,7 @@ _NODISCARD constexpr _Ty midpoint(const _Ty _Val1, const _Ty _Val2) noexcept { // 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; From 98b19bb557420cebaa47908dc54b5040c416ed05 Mon Sep 17 00:00:00 2001 From: statementreply Date: Thu, 23 Jul 2020 21:27:29 +0800 Subject: [PATCH 07/12] Update feature test macro --- stl/inc/yvals_core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From fbd2c52b351c4a71d6f87ad8c7a24d618721a4ea Mon Sep 17 00:00:00 2001 From: statementreply Date: Thu, 23 Jul 2020 21:41:25 +0800 Subject: [PATCH 08/12] Update tests --- tests/libcxx/expected_results.txt | 9 +++++---- tests/libcxx/skipped_tests.txt | 9 +++++---- .../tests/VSO_0157762_feature_test_macros/test.cpp | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 8 deletions(-) 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/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 From 9305264a48af95727fa15e0daa8d11ae528ee02c Mon Sep 17 00:00:00 2001 From: statementreply Date: Thu, 23 Jul 2020 23:47:53 +0800 Subject: [PATCH 09/12] Add tests for floating point exceptions --- stl/inc/cmath | 5 +- tests/std/tests/P0811R3_midpoint_lerp/env.lst | 3 + .../std/tests/P0811R3_midpoint_lerp/test.cpp | 188 +++++++++++++++--- 3 files changed, 165 insertions(+), 31 deletions(-) diff --git a/stl/inc/cmath b/stl/inc/cmath index d33041360fe..85275ffc53e 100644 --- a/stl/inc/cmath +++ b/stl/inc/cmath @@ -1290,12 +1290,13 @@ _NODISCARD constexpr _Ty _Common_lerp(const _Ty _ArgA, const _Ty _ArgB, const _T } else { if (_STD _Is_nan(_ArgA)) { // raise FE_INVALID if at least one of _ArgA, _ArgB and _ArgT is signaling NaN + _Ty _Tmp = _ArgA; if (_STD _Is_nan(_ArgB) || _STD _Is_nan(_ArgT)) { - (void) (_ArgB + _ArgT); + _Tmp = _ArgB + _ArgT; } - return _ArgA + _ArgA; + return _ArgA + _Tmp; } if (_STD _Is_nan(_ArgB) || _STD _Is_nan(_ArgT)) { 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 4712231876a..7b9f4f94c6b 100644 --- a/tests/std/tests/P0811R3_midpoint_lerp/test.cpp +++ b/tests/std/tests/P0811R3_midpoint_lerp/test.cpp @@ -23,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) @@ -55,7 +58,54 @@ 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 constexpr Ty mint_nan(const bool sign, const unsigned long long payload); @@ -100,6 +150,24 @@ 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 @@ -328,6 +396,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)); @@ -358,10 +427,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); @@ -396,9 +468,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); @@ -424,27 +499,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 @@ -860,39 +957,68 @@ bool test_lerp() { 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 (memcmp(&answer, &testCase.expected, sizeof(Ty)) != 0) { + if (!check_feexcept(FE_OVERFLOW, fe_major_except) || 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 (!isnan(answer)) { + 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 (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; - })) { + 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); @@ -951,9 +1077,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(); From c9a2ba5bd4a720011c584abcf523fa8198ba9652 Mon Sep 17 00:00:00 2001 From: statementreply Date: Fri, 24 Jul 2020 00:11:52 +0800 Subject: [PATCH 10/12] Minor fixes --- tests/std/tests/P0811R3_midpoint_lerp/test.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/std/tests/P0811R3_midpoint_lerp/test.cpp b/tests/std/tests/P0811R3_midpoint_lerp/test.cpp index 7b9f4f94c6b..af6dedda278 100644 --- a/tests/std/tests/P0811R3_midpoint_lerp/test.cpp +++ b/tests/std/tests/P0811R3_midpoint_lerp/test.cpp @@ -89,7 +89,6 @@ class ExceptGuard { 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: @@ -968,7 +967,7 @@ bool test_lerp() { for (auto&& testCase : LerpCases::lerpOverflowTestCases) { ExceptGuard except; const auto answer = lerp(testCase.x, testCase.y, testCase.t); - if (!check_feexcept(FE_OVERFLOW, fe_major_except) || memcmp(&answer, &testCase.expected, sizeof(Ty)) != 0) { + if (!check_feexcept(FE_OVERFLOW) || memcmp(&answer, &testCase.expected, sizeof(Ty)) != 0) { print_lerp_result(testCase, answer); abort(); } From 57e9826643676baa0fd0575937585e4bf92ed03a Mon Sep 17 00:00:00 2001 From: statementreply Date: Thu, 30 Jul 2020 00:51:03 +0800 Subject: [PATCH 11/12] Address review comments --- stl/inc/cmath | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/stl/inc/cmath b/stl/inc/cmath index 85275ffc53e..25d4a791111 100644 --- a/stl/inc/cmath +++ b/stl/inc/cmath @@ -1288,19 +1288,13 @@ _NODISCARD constexpr _Ty _Common_lerp(const _Ty _ArgA, const _Ty _ArgB, const _T return _ArgT; } } else { - if (_STD _Is_nan(_ArgA)) { - // raise FE_INVALID if at least one of _ArgA, _ArgB and _ArgT is signaling NaN - _Ty _Tmp = _ArgA; - - if (_STD _Is_nan(_ArgB) || _STD _Is_nan(_ArgT)) { - _Tmp = _ArgB + _ArgT; - } - - return _ArgA + _Tmp; + // 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(_ArgB) || _STD _Is_nan(_ArgT)) { - return _ArgB + _ArgT; + if (_STD _Is_nan(_ArgT)) { + return _ArgT + _ArgT; } } From af71038bec6bfb5d38e832ef4154ebd6a81b8672 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 29 Jul 2020 17:04:17 -0700 Subject: [PATCH 12/12] Fix a subtle conformance issue. It's technically nonconformant to explicitly specialize a non-inline variable template (and _INLINE_VAR is inline for C++17 and above). MSVC accepts this, but it causes problems for Clang. tests/std/tests/Dev09_172666_tr1_tuple_odr detected this in the MSVC-internal repo, but not on GitHub due to an oversight that we just discovered and are fixing (it has one.cpp and two.cpp files instead of test.cpp, so we need to add a custom run script). We can fix this by just using a struct template with static constexpr data members. A couple of other changes here: Use is_floating_point_v instead of ::value, and _Float_abs_bits doesn't need to static_cast at the end (the inputs to bitwise-AND are the same type and are unsigned int or unsigned long long, so there's no integral promotions involved). cmath: Add Oxford comma to comment. numeric: Now that we require is_integral_v, _Arithmetic should be renamed to _Integral. --- stl/inc/cmath | 2 +- stl/inc/numeric | 12 +++++----- stl/inc/xutility | 62 +++++++++++++++--------------------------------- 3 files changed, 26 insertions(+), 50 deletions(-) diff --git a/stl/inc/cmath b/stl/inc/cmath index 25d4a791111..5895847f4fd 100644 --- a/stl/inc/cmath +++ b/stl/inc/cmath @@ -1288,7 +1288,7 @@ _NODISCARD constexpr _Ty _Common_lerp(const _Ty _ArgA, const _Ty _ArgB, const _T return _ArgT; } } else { - // raise FE_INVALID if at least one of _ArgA, _ArgB and _ArgT is signaling NaN + // 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; } diff --git a/stl/inc/numeric b/stl/inc/numeric index 4cf75dede6e..687bc8a988a 100644 --- a/stl/inc/numeric +++ b/stl/inc/numeric @@ -536,16 +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) - static_assert(is_integral_v<_Arithmetic>); + static_assert(is_integral_v<_Integral>); - 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)); } diff --git a/stl/inc/xutility b/stl/inc/xutility index e3dcbed296b..f9ace7c254d 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -5942,73 +5942,49 @@ struct _CXX17_DEPRECATE_ITERATOR_BASE_CLASS iterator { // base type for iterator using reference = _Reference; }; -// STRUCT TEMPLATE _Float_bits +// STRUCT TEMPLATE _Float_traits template -struct _Float_bits { - static_assert(_Always_false<_Ty>, "_Float_bits is invalid"); -}; - -template <> -struct _Float_bits { - using type = unsigned int; -}; +struct _Float_traits { + static_assert(is_floating_point_v<_Ty>, "_Float_traits is invalid"); -template <> -struct _Float_bits { + // traits for double and long double: using type = unsigned long long; -}; -template <> -struct _Float_bits { - using type = _Float_bits::type; + static constexpr type _Magnitude_mask = 0x7fff'ffff'ffff'ffffULL; + static constexpr type _Exponent_mask = 0x7ff0'0000'0000'0000ULL; }; -template -using _Float_bits_t = typename _Float_bits<_Ty>::type; - -template -_INLINE_VAR constexpr _Float_bits_t<_Ty> _Float_magnitude_mask{}; -template -_INLINE_VAR constexpr _Float_bits_t<_Ty> _Float_exponent_mask{}; - -template <> -_INLINE_VAR constexpr _Float_bits_t _Float_magnitude_mask = 0x7fff'ffffU; -template <> -_INLINE_VAR constexpr _Float_bits_t _Float_exponent_mask = 0x7f80'0000U; - template <> -_INLINE_VAR constexpr _Float_bits_t _Float_magnitude_mask = 0x7fff'ffff'ffff'ffffULL; -template <> -_INLINE_VAR constexpr _Float_bits_t _Float_exponent_mask = 0x7ff0'0000'0000'0000ULL; +struct _Float_traits { + using type = unsigned int; -template <> -_INLINE_VAR constexpr _Float_bits_t _Float_magnitude_mask = _Float_magnitude_mask; -template <> -_INLINE_VAR constexpr _Float_bits_t _Float_exponent_mask = _Float_exponent_mask; + static constexpr type _Magnitude_mask = 0x7fff'ffffU; + static constexpr type _Exponent_mask = 0x7f80'0000U; +}; // FUNCTION TEMPLATE _Float_abs_bits -template ::value, int> = 0> +template , int> = 0> _NODISCARD _CONSTEXPR_BIT_CAST auto _Float_abs_bits(const _Ty& _Xx) { - const auto _Bits = _Bit_cast<_Float_bits_t<_Ty>>(_Xx); - return static_cast<_Float_bits_t<_Ty>>(_Bits & _Float_magnitude_mask<_Ty>); + const auto _Bits = _Bit_cast::type>(_Xx); + return _Bits & _Float_traits<_Ty>::_Magnitude_mask; } // FUNCTION TEMPLATE _Float_abs -template ::value, int> = 0> +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 ::value, int> = 0> +template , int> = 0> _NODISCARD _CONSTEXPR_BIT_CAST bool _Is_nan(const _Ty _Xx) { // constexpr isnan() - return _Float_abs_bits(_Xx) > _Float_exponent_mask<_Ty>; + return _Float_abs_bits(_Xx) > _Float_traits<_Ty>::_Exponent_mask; } // FUNCTION TEMPLATE _Is_finite -template ::value, int> = 0> +template , int> = 0> _NODISCARD _CONSTEXPR_BIT_CAST bool _Is_finite(const _Ty _Xx) { // constexpr isfinite() - return _Float_abs_bits(_Xx) < _Float_exponent_mask<_Ty>; + return _Float_abs_bits(_Xx) < _Float_traits<_Ty>::_Exponent_mask; } _STD_END #undef _CONSTEXPR_BIT_CAST