Skip to content

Commit 748f3e3

Browse files
fsb4000AdamBuciorabolzcbezaultMattStephanson
committed
Recalculate lerp if we got infinity. Eliminates some overflows. (microsoft#1918)
Co-authored-by: Adam Bucior <[email protected]> Co-authored-by: Alexander Bolz <[email protected]> Co-authored-by: Curtis J Bezault <[email protected]> Co-authored-by: Matt Stephanson <[email protected]> Co-authored-by: Michael Schellenberger Costa <[email protected]> Co-authored-by: statementreply <[email protected]> Co-authored-by: Stephan T. Lavavej <[email protected]>
1 parent 3093794 commit 748f3e3

File tree

2 files changed

+107
-1
lines changed

2 files changed

+107
-1
lines changed

stl/inc/cmath

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1335,6 +1335,31 @@ _NODISCARD auto hypot(const _Ty1 _Dx, const _Ty2 _Dy, const _Ty3 _Dz) {
13351335
}
13361336

13371337
#if _HAS_CXX20
1338+
template <class _Ty>
1339+
_NODISCARD constexpr _Ty _Linear_for_lerp(const _Ty _ArgA, const _Ty _ArgB, const _Ty _ArgT) {
1340+
if (_STD is_constant_evaluated()) {
1341+
auto _Smaller = _ArgT;
1342+
auto _Larger = _ArgB - _ArgA;
1343+
auto _Abs_smaller = _Float_abs(_Smaller);
1344+
auto _Abs_larger = _Float_abs(_Larger);
1345+
if (_Abs_larger < _Abs_smaller) {
1346+
_STD swap(_Smaller, _Larger);
1347+
_STD swap(_Abs_smaller, _Abs_larger);
1348+
}
1349+
1350+
if (_Abs_smaller > 1) {
1351+
// _Larger is too large to be subnormal, so scaling by 0.5 is exact, and the product _Smaller * _Larger is
1352+
// large enough that if _ArgA is subnormal, it will be too small to contribute anyway and this way can
1353+
// sometimes avoid overflow problems.
1354+
return 2 * (_Ty{0.5} * _ArgA + _Smaller * (_Ty{0.5} * _Larger));
1355+
} else {
1356+
return _ArgA + _Smaller * _Larger;
1357+
}
1358+
}
1359+
1360+
return _STD fma(_ArgT, _ArgB - _ArgA, _ArgA);
1361+
}
1362+
13381363
template <class _Ty>
13391364
_NODISCARD constexpr _Ty _Common_lerp(const _Ty _ArgA, const _Ty _ArgB, const _Ty _ArgT) noexcept {
13401365
// on a line intersecting {(0.0, _ArgA), (1.0, _ArgB)}, return the Y value for X == _ArgT
@@ -1353,7 +1378,7 @@ _NODISCARD constexpr _Ty _Common_lerp(const _Ty _ArgA, const _Ty _ArgB, const _T
13531378
}
13541379

13551380
// exact at _ArgT == 0, monotonic except near _ArgT == 1, bounded, determinate, and consistent:
1356-
const auto _Candidate = _ArgA + _ArgT * (_ArgB - _ArgA);
1381+
const auto _Candidate = _Linear_for_lerp(_ArgA, _ArgB, _ArgT);
13571382
// monotonic near _ArgT == 1:
13581383
if ((_ArgT > 1) == (_ArgB > _ArgA)) {
13591384
if (_ArgB > _Candidate) {

tests/std/tests/P0811R3_midpoint_lerp/test.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,86 @@ bool test_lerp() {
10231023
return true;
10241024
}
10251025

1026+
void test_gh_1917() {
1027+
// GH-1917 <cmath>: lerp(1e+308, 5e+307, 4.0) spuriously overflows
1028+
using bit_type = unsigned long long;
1029+
using float_bit_type = unsigned int;
1030+
STATIC_ASSERT(bit_cast<bit_type>(lerp(1e+308, 5e+307, 4.0)) == bit_cast<bit_type>(-1e+308));
1031+
{
1032+
ExceptGuard except;
1033+
1034+
assert(bit_cast<bit_type>(lerp(1e+308, 5e+307, 4.0)) == bit_cast<bit_type>(-1e+308));
1035+
assert(check_feexcept(0));
1036+
}
1037+
STATIC_ASSERT(bit_cast<float_bit_type>(lerp(2e+38f, 1e+38f, 4.0f)) == bit_cast<float_bit_type>(-2e+38f));
1038+
{
1039+
ExceptGuard except;
1040+
1041+
assert(bit_cast<float_bit_type>(lerp(2e+38f, 1e+38f, 4.0f)) == bit_cast<float_bit_type>(-2e+38f));
1042+
assert(check_feexcept(0));
1043+
}
1044+
#ifdef _M_FP_STRICT
1045+
{
1046+
ExceptGuard except;
1047+
RoundGuard round{FE_UPWARD};
1048+
1049+
assert(bit_cast<bit_type>(lerp(1e+308, 5e+307, 4.0)) == bit_cast<bit_type>(-1e+308));
1050+
assert(check_feexcept(0));
1051+
}
1052+
{
1053+
ExceptGuard except;
1054+
RoundGuard round{FE_UPWARD};
1055+
1056+
assert(bit_cast<float_bit_type>(lerp(2e+38f, 1e+38f, 4.0f)) == bit_cast<float_bit_type>(-2e+38f));
1057+
assert(check_feexcept(0));
1058+
}
1059+
{
1060+
ExceptGuard except;
1061+
RoundGuard round{FE_DOWNWARD};
1062+
1063+
assert(bit_cast<bit_type>(lerp(1e+308, 5e+307, 4.0)) == bit_cast<bit_type>(-1e+308));
1064+
assert(check_feexcept(0));
1065+
}
1066+
{
1067+
ExceptGuard except;
1068+
RoundGuard round{FE_DOWNWARD};
1069+
1070+
assert(bit_cast<float_bit_type>(lerp(2e+38f, 1e+38f, 4.0f)) == bit_cast<float_bit_type>(-2e+38f));
1071+
assert(check_feexcept(0));
1072+
}
1073+
{
1074+
ExceptGuard except;
1075+
RoundGuard round{FE_TOWARDZERO};
1076+
1077+
assert(bit_cast<bit_type>(lerp(1e+308, 5e+307, 4.0)) == bit_cast<bit_type>(-1e+308));
1078+
assert(check_feexcept(0));
1079+
}
1080+
{
1081+
ExceptGuard except;
1082+
RoundGuard round{FE_TOWARDZERO};
1083+
1084+
assert(bit_cast<float_bit_type>(lerp(2e+38f, 1e+38f, 4.0f)) == bit_cast<float_bit_type>(-2e+38f));
1085+
assert(check_feexcept(0));
1086+
}
1087+
{
1088+
ExceptGuard except;
1089+
const int r = feraiseexcept(FE_OVERFLOW);
1090+
1091+
assert(r == 0);
1092+
assert(bit_cast<bit_type>(lerp(1e+308, 5e+307, 4.0)) == bit_cast<bit_type>(-1e+308));
1093+
assert(check_feexcept(FE_OVERFLOW));
1094+
}
1095+
{
1096+
ExceptGuard except;
1097+
const int r = feraiseexcept(FE_OVERFLOW);
1098+
1099+
assert(r == 0);
1100+
assert(bit_cast<float_bit_type>(lerp(2e+38f, 1e+38f, 4.0f)) == bit_cast<float_bit_type>(-2e+38f));
1101+
assert(check_feexcept(FE_OVERFLOW));
1102+
}
1103+
#endif // _M_FP_STRICT
1104+
}
1105+
10261106
constexpr bool test_gh_2112() {
10271107
// GH-2112 <cmath>: std::lerp is missing Arithmetic overloads
10281108
assert(lerp(0, 0, 0) == 0.0);
@@ -1108,6 +1188,7 @@ int main() {
11081188
test_lerp<double>();
11091189
test_lerp<long double>();
11101190

1191+
test_gh_1917();
11111192
test_gh_2112();
11121193
STATIC_ASSERT(test_gh_2112());
11131194
}

0 commit comments

Comments
 (0)