| Issue |
166628
|
| Summary |
std::lerp(0, 1, inf) returns nan, but should return inf
|
| Labels |
new issue
|
| Assignees |
|
| Reporter |
mdsteele
|
`std::lerp(0, 1, inf)` should return `inf`, but it returns `nan`.
According to https://en.cppreference.com/w/cpp/numeric/lerp.html:
> When `std::isfinite(a) && std::isfinite(b)` is `true`, the following properties are guaranteed:
> * ...
> * If `std::isfinite(t) || (b - a != 0 && std::isinf(t))`, the result is not NaN.
In this case, `a` is 0 and `b` is 1, so those are finite. And `b - a != 0 && std::isinf(t)`, so the result should not be NaN. But it is. Note that `std::lerp(1, 2, inf)` correctly returns `inf`.
Looking at the [implementation](https://github.com/llvm/llvm-project/blob/f76c132230326a296c4fb8f7cb6c0fb6b943fadb/libcxx/include/cmath#L576) of `std::lerp` in libc++, we can see the problem:
```c++
template <typename _Fp>
_LIBCPP_HIDE_FROM_ABI constexpr _Fp __lerp(_Fp __a, _Fp __b, _Fp __t) noexcept {
if ((__a <= 0 && __b >= 0) || (__a >= 0 && __b <= 0))
return __t * __b + (1 - __t) * __a;
if (__t == 1)
return __b;
const _Fp __x = __a + __t * (__b - __a);
if ((__t > 1) == (__b > __a))
return __b < __x ? __x : __b;
else
return __x < __b ? __x : __b;
}
```
In this case, `a <= 0 && b >= 0`, so the first `return` statement is taken. It calculates `inf * 1 + (1 - inf) * 0`, which is `inf + (-inf) * 0`, which is `inf + nan`, which is `nan`.
As far as I can tell, this implementation comes straight from https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0811r3.html#linear-interpolation:
```c++
constexpr Float lerp(Float a, Float b, Float t) {
// Exact, monotonic, bounded, determinate, and (for a=b=0) consistent:
if(a<=0 && b>=0 || a>=0 && b<=0) return t*b + (1-t)*a;
if(t==1) return b; // exact
// Exact at t=0, monotonic except near t=1,
// bounded, determinate, and consistent:
const Float x = a + t*(b-a);
return t>1 == b>a ? max(b,x) : min(b,x); // monotonic near t=1
}
```
The first comment claims that the first case is "determinate", with "determinacy" defined as "result of NaN only for lerp(a,a,INFINITY)". But in fact, `t*b + (1-t)*a` will also return NaN for `a != b && std::isinf(t)` if exactly one of `a` or `b` is zero, so that case isn't actually determinate.
One possible fix might be to add an additional case to the top:
```c++
if (isinf(__t)) return __t * (__b - __a);
```
But perhaps there is a more efficient solution (or perhaps there's an edge case I missed that this gets wrong).
Regardless of the fix, some new test cases in [`lerp.pass.cpp`](https://github.com/llvm/llvm-project/blob/1fc5c02aa56ad4cef1391863dfc0922ef7110569/libcxx/test/std/numerics/c.math/lerp.pass.cpp#L59) seem warranted:
```c++
assert(std::lerp(T(0), T(1), inf) == T( inf);
assert(std::lerp(T(0), T(1), -inf) == T(-inf);
assert(std::lerp(T(1), T(0), inf) == T(-inf);
assert(std::lerp(T(1), T(0), -inf) == T( inf);
```
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs