diff --git a/doc/changelog.dox b/doc/changelog.dox index 972dec8c3..8d00867c8 100644 --- a/doc/changelog.dox +++ b/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), +- @ref Math::Vector::min(), @ref Math::Vector::max(), + @ref Math::Vector::minmax() and batch + @ref Math::min(Containers::StridedArrayView1D), @ref Math::max(Containers::StridedArrayView1D) and @ref Math::minmax(Containers::StridedArrayView1D) 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&) or the batch @ref Math::isNan(Containers::StridedArrayView1D) to detect presence of NaN values if needed. - Changed the way @ref Math::operator<<(Corrade::Utility::Debug&, const BoolVector&) diff --git a/src/Magnum/Math/Functions.h b/src/Magnum/Math/Functions.h index 24de6763e..67640fd11 100644 --- a/src/Magnum/Math/Functions.h +++ b/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) */ -template inline typename std::enable_if::value, bool>::type isNan(T value) { - return std::isnan(UnderlyingTypeOf(value)); -} +/* defined in Vector.h */ +template typename std::enable_if::value, bool>::type isNan(T value); /** @overload */ template inline BoolVector isNan(const Vector& value) { diff --git a/src/Magnum/Math/Test/VectorTest.cpp b/src/Magnum/Math/Test/VectorTest.cpp index 76a626d36..23798b812 100644 --- a/src/Magnum/Math/Test/VectorTest.cpp +++ b/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 Constants; typedef Math::Rad 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); diff --git a/src/Magnum/Math/Vector.h b/src/Magnum/Math/Vector.h index c882424f9..1b303ddd8 100644 --- a/src/Magnum/Math/Vector.h +++ b/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 inline typename std::enable_if::value, bool>::type isNan(T value) { + return std::isnan(UnderlyingTypeOf(value)); +} + template constexpr typename std::enable_if::value, T>::type min(T a, T b) { return b < a ? b : a; } @@ -599,21 +603,24 @@ template class Vector { /** * @brief Minimal value in the vector * - * @see @ref Math::min(), @ref minmax() + * NaNs are ignored, unless the vector is all NaNs. + * @see @ref Math::min(), @ref minmax(), @ref Math::isNan() */ T min() const; /** * @brief Maximal value in the vector * - * @see @ref Math::max(), @ref minmax() + * NaNs are ignored, unless the vector is all NaNs. + * @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() + * NaNs are ignored, unless the vector is all NaNs. + * @see @ref min(), @ref max(), @ref Math::minmax(), @ref Math::isNan() */ std::pair minmax() const; @@ -1409,28 +1416,47 @@ template inline T Vector::product() const { return out; } +namespace Implementation { + /* Non-floating-point types, the first is a non-NaN for sure */ + template constexpr std::size_t firstNonNan(const T(&)[size], std::false_type) { + return 0; + } + /* Floating-point types, return the first that's not NaN */ + template 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 inline T Vector::min() const { - T out(_data[0]); + std::size_t i = Implementation::firstNonNan(_data, IsFloatingPoint{}); + 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 inline T Vector::max() const { - T out(_data[0]); + std::size_t i = Implementation::firstNonNan(_data, IsFloatingPoint{}); + 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 inline std::pair Vector::minmax() const { - T min{_data[0]}, max{_data[0]}; + std::size_t i = Implementation::firstNonNan(_data, IsFloatingPoint{}); + 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)