Browse Source

Math: ignore NaNs also in Vector::min(), max() and minmax().

To have the API consistent.
pull/343/head
Vladimír Vondruš 7 years ago
parent
commit
8e5ecfacac
  1. 7
      doc/changelog.dox
  2. 5
      src/Magnum/Math/Functions.h
  3. 25
      src/Magnum/Math/Test/VectorTest.cpp
  4. 44
      src/Magnum/Math/Vector.h

7
doc/changelog.dox

@ -224,10 +224,13 @@ See also:
- All batch functions in @ref Magnum/Math/FunctionsBatch.h now accept
@ref Corrade::Containers::StridedArrayView instead of a dense array view to
make them usable in more contexts
- Batch @ref Math::min(Containers::StridedArrayView1D<const T>),
- @ref Math::Vector::min(), @ref Math::Vector::max(),
@ref Math::Vector::minmax() and batch
@ref Math::min(Containers::StridedArrayView1D<const T>),
@ref Math::max(Containers::StridedArrayView1D<const T>) and
@ref Math::minmax(Containers::StridedArrayView1D<const T>) functions now
ignore NaNs in the data, if possible. Use the batch
ignore NaNs in the data, if possible. Use
@ref Math::isNan(const Vector<size, T>&) or the batch
@ref Math::isNan(Containers::StridedArrayView1D<const T>) to detect
presence of NaN values if needed.
- Changed the way @ref Math::operator<<(Corrade::Utility::Debug&, const BoolVector<size>&)

5
src/Magnum/Math/Functions.h

@ -182,9 +182,8 @@ Equivalent to @cpp value != value @ce.
@see @ref isInf(), @ref Constants::nan(),
@ref isNan(Corrade::Containers::StridedArrayView1D<const T>)
*/
template<class T> inline typename std::enable_if<IsScalar<T>::value, bool>::type isNan(T value) {
return std::isnan(UnderlyingTypeOf<T>(value));
}
/* defined in Vector.h */
template<class T> typename std::enable_if<IsScalar<T>::value, bool>::type isNan(T value);
/** @overload */
template<std::size_t size, class T> inline BoolVector<size> isNan(const Vector<size, T>& value) {

25
src/Magnum/Math/Test/VectorTest.cpp

@ -97,6 +97,8 @@ struct VectorTest: Corrade::TestSuite::Tester {
void max();
void minmax();
void nanIgnoring();
void projected();
void projectedOntoNormalized();
void projectedOntoNormalizedNotNormalized();
@ -113,6 +115,7 @@ struct VectorTest: Corrade::TestSuite::Tester {
void debug();
};
typedef Math::Constants<Float> Constants;
typedef Math::Rad<Float> Rad;
typedef Vector<2, Float> Vector2;
typedef Vector<3, Float> Vector3;
@ -162,6 +165,8 @@ VectorTest::VectorTest() {
&VectorTest::max,
&VectorTest::minmax,
&VectorTest::nanIgnoring,
&VectorTest::projected,
&VectorTest::projectedOntoNormalized,
&VectorTest::projectedOntoNormalizedNotNormalized,
@ -483,6 +488,26 @@ void VectorTest::minmax() {
CORRADE_COMPARE((Vector3{-3.0f, -1.0f, 2.0f}.minmax()), expected);
}
void VectorTest::nanIgnoring() {
Vector3 oneNan{1.0f, Constants::nan(), -3.0f};
Vector3 firstNan{Constants::nan(), 1.0f, -3.0f};
Vector3 allNan{Constants::nan(), Constants::nan(), Constants::nan()};
CORRADE_COMPARE(oneNan.min(), -3.0f);
CORRADE_COMPARE(firstNan.min(), -3.0f);
CORRADE_COMPARE(allNan.min(), Constants::nan());
CORRADE_COMPARE(oneNan.max(), 1.0f);
CORRADE_COMPARE(firstNan.max(), 1.0f);
CORRADE_COMPARE(allNan.max(), Constants::nan());
CORRADE_COMPARE(oneNan.minmax(), std::make_pair(-3.0f, 1.0f));
CORRADE_COMPARE(firstNan.minmax(), std::make_pair(-3.0f, 1.0f));
/* Need to compare this way because of NaNs */
CORRADE_COMPARE(allNan.minmax().first, Constants::nan());
CORRADE_COMPARE(allNan.minmax().second, Constants::nan());
}
void VectorTest::projected() {
Vector3 line(1.0f, -1.0f, 0.5f);
Vector3 projected = Vector3(1.0f, 2.0f, 3.0f).projected(line);

44
src/Magnum/Math/Vector.h

@ -45,6 +45,10 @@ namespace Magnum { namespace Math {
#ifndef DOXYGEN_GENERATING_OUTPUT
/* Documented in Functions.h, defined here because Vector needs them */
template<class T> inline typename std::enable_if<IsScalar<T>::value, bool>::type isNan(T value) {
return std::isnan(UnderlyingTypeOf<T>(value));
}
template<class T> constexpr typename std::enable_if<IsScalar<T>::value, T>::type min(T a, T b) {
return b < a ? b : a;
}
@ -599,21 +603,24 @@ template<std::size_t size, class T> class Vector {
/**
* @brief Minimal value in the vector
*
* @see @ref Math::min(), @ref minmax()
* <em>NaN</em>s are ignored, unless the vector is all <em>NaN</em>s.
* @see @ref Math::min(), @ref minmax(), @ref Math::isNan()
*/
T min() const;
/**
* @brief Maximal value in the vector
*
* @see @ref Math::max(), @ref minmax()
* <em>NaN</em>s are ignored, unless the vector is all <em>NaN</em>s.
* @see @ref Math::max(), @ref minmax(), @ref Math::isNan()
*/
T max() const;
/**
* @brief Minimal and maximal value in the vector
*
* @see @ref min(), @ref max(), @ref Math::minmax()
* <em>NaN</em>s are ignored, unless the vector is all <em>NaN</em>s.
* @see @ref min(), @ref max(), @ref Math::minmax(), @ref Math::isNan()
*/
std::pair<T, T> minmax() const;
@ -1409,28 +1416,47 @@ template<std::size_t size, class T> inline T Vector<size, T>::product() const {
return out;
}
namespace Implementation {
/* Non-floating-point types, the first is a non-NaN for sure */
template<std::size_t size, class T> constexpr std::size_t firstNonNan(const T(&)[size], std::false_type) {
return 0;
}
/* Floating-point types, return the first that's not NaN */
template<std::size_t size, class T> inline std::size_t firstNonNan(const T(&data)[size], std::true_type) {
/* Find the first non-NaN value to compare against. If all are NaN,
return the last value so the following loop in min/max/minmax()
doesn't even execute. */
for(std::size_t i = 0; i != size; ++i)
if(!isNan(data[i])) return i;
return size - 1;
}
}
template<std::size_t size, class T> inline T Vector<size, T>::min() const {
T out(_data[0]);
std::size_t i = Implementation::firstNonNan(_data, IsFloatingPoint<T>{});
T out(_data[i]);
for(std::size_t i = 1; i != size; ++i)
for(++i; i != size; ++i)
out = Math::min(out, _data[i]);
return out;
}
template<std::size_t size, class T> inline T Vector<size, T>::max() const {
T out(_data[0]);
std::size_t i = Implementation::firstNonNan(_data, IsFloatingPoint<T>{});
T out(_data[i]);
for(std::size_t i = 1; i != size; ++i)
for(++i; i != size; ++i)
out = Math::max(out, _data[i]);
return out;
}
template<std::size_t size, class T> inline std::pair<T, T> Vector<size, T>::minmax() const {
T min{_data[0]}, max{_data[0]};
std::size_t i = Implementation::firstNonNan(_data, IsFloatingPoint<T>{});
T min{_data[i]}, max{_data[i]};
for(std::size_t i = 1; i != size; ++i) {
for(++i; i != size; ++i) {
if(_data[i] < min)
min = _data[i];
else if(_data[i] > max)

Loading…
Cancel
Save