Repository: xtensor-stack/xtensor (language: python)

Issue:
Implement nanmin and nanmax
# Checklist

- [x] The title and commit message(s) are descriptive.
- [x] Small commits made to fix your PR have been squashed to avoid history pollution.
- [x] Tests have been added for new features or bug fixes.
- [x] API of new functions and classes are documented.

# Description

<!---
Give any relevant description here. 
If your PR fixes an issue, please include "Fixes #ISSUE" (substituting the relevant issue ID).
-->

Implemented `nanmin` and `nanmax`.


Discussion / hints:
@JohanMabille @tdegeus Could you review this PR and give some hint on the gcc4.9 workaround? Thanks a lot!

```sh
body of constexpr function ‘constexpr std::common_type_t<CTR1, CTI1> xt::detail::nan_min::operator()(T, U) const [with T = unsigned int; U = unsigned int; std::common_type_t<CTR1, CTI1> = unsigned int]’ not a return-statement
```
Awesome!
Thanks for the review! I have not cleaned up my code yet...I should have added a WIP on the title.
I will do it with another PR, including the documentation of the undefined behavior discussed above and possibly a few more tests.

Fix patch:
diff --git a/include/xtensor/xmath.hpp b/include/xtensor/xmath.hpp
index 615a13624..7091af332 100644
--- a/include/xtensor/xmath.hpp
+++ b/include/xtensor/xmath.hpp
@@ -407,45 +407,6 @@ namespace detail {
                           init_value_fct(detail::fill_init<init_value_type >(INIT))), std::forward<E>(e), axes, es); \
     }
 
-#define XTENSOR_MIN_MAX_REDUCER_FUNCTION(NAME, FUNCTOR, INIT_VALUE_TYPE, INIT)                                       \
-    template <class E, class X, class EVS = DEFAULT_STRATEGY_REDUCERS,                                               \
-              XTL_REQUIRES(xtl::negation<is_reducer_options<X>>, xtl::negation<xtl::is_integral<X>>)>                \
-    inline auto NAME(E&& e, X&& axes, EVS es = EVS())                                                                \
-    {                                                                                                                \
-        using functor_type = FUNCTOR;                                                                                \
-        using init_value_fct = xt::const_value<INIT_VALUE_TYPE>;                                                     \
-        return xt::reduce(make_xreducer_functor(functor_type(),                                                      \
-                          init_value_fct(detail::fill_init<INIT_VALUE_TYPE>(INIT))),                                 \
-                          std::forward<E>(e),                                                                        \
-                          std::forward<X>(axes), es);                                                                \
-    }                                                                                                                \
-                                                                                                                     \
-    template <class E, class X, class EVS = DEFAULT_STRATEGY_REDUCERS,                                               \
-              XTL_REQUIRES(xtl::negation<is_reducer_options<X>>, xtl::is_integral<X>)>                               \
-    inline auto NAME(E&& e, X axis, EVS es = EVS())                                                                  \
-    {                                                                                                                \
-        return NAME(std::forward<E>(e), {axis}, es);                                                                 \
-    }                                                                                                                \
-                                                                                                                     \
-    template <class E, class EVS = DEFAULT_STRATEGY_REDUCERS,                                                        \
-              XTL_REQUIRES(is_reducer_options<EVS>)>                                                                 \
-    inline auto NAME(E&& e, EVS es = EVS())                                                                          \
-    {                                                                                                                \
-        using functor_type = FUNCTOR;                                                                                \
-        using init_value_fct = xt::const_value<INIT_VALUE_TYPE >;                                                    \
-        return xt::reduce(make_xreducer_functor(functor_type(),                                                      \
-                          init_value_fct(detail::fill_init<INIT_VALUE_TYPE >(INIT))), std::forward<E>(e), es);       \
-    }                                                                                                                \
-                                                                                                                     \
-    template <class E, class I, std::size_t N, class EVS = DEFAULT_STRATEGY_REDUCERS>                                \
-    inline auto NAME(E&& e, const I (&axes)[N], EVS es = EVS())                                                      \
-    {                                                                                                                \
-        using functor_type = FUNCTOR;                                                                                \
-        using init_value_fct = xt::const_value<INIT_VALUE_TYPE >;                                                    \
-        return xt::reduce(make_xreducer_functor(functor_type(),                                                      \
-                          init_value_fct(detail::fill_init<INIT_VALUE_TYPE >(INIT))), std::forward<E>(e), axes, es); \
-    }
-
     /*******************
      * basic functions *
      *******************/
@@ -2426,11 +2387,11 @@ namespace detail {
      *
      * Returns an \ref xreducer for the minimum of elements over given
      * \em axes, ignoring nans.
+     * @warning Casting the result to an integer type can cause undefined behavior.
      * @param e an \ref xexpression
      * @param axes the axes along which the minimum is found (optional)
      * @param es evaluation strategy of the reducer (optional)
-     * @tparam T used for determining the value type of the result, which is
-     *           the type of `T() + E::value_type()`. The default is `E::value_type`.
+     * @tparam T the result type. The default is `E::value_type`.
      * @return an \ref xreducer
      */
     XTENSOR_REDUCER_FUNCTION(nanmin, detail::nan_min, typename std::decay_t<E>::value_type, std::nan("0"))
@@ -2441,11 +2402,11 @@ namespace detail {
      *
      * Returns an \ref xreducer for the sum of elements over given
      * \em axes, replacing nan with 0.
+     * @warning Casting the result to an integer type can cause undefined behavior.
      * @param e an \ref xexpression
      * @param axes the axes along which the sum is performed (optional)
      * @param es evaluation strategy of the reducer (optional)
-     * @tparam T used for determining the value type of the result, which is
-     *           the type of `T() + E::value_type()`. The default is `E::value_type`.
+     * @tparam T the result type. The default is `E::value_type`.
      * @return an \ref xreducer
      */
     XTENSOR_REDUCER_FUNCTION(nanmax, detail::nan_max, typename std::decay_t<E>::value_type, std::nan("0"))


Test patch:
diff --git a/test/test_xnan_functions.cpp b/test/test_xnan_functions.cpp
index 57f43d486..ce349c106 100644
--- a/test/test_xnan_functions.cpp
+++ b/test/test_xnan_functions.cpp
@@ -261,109 +261,94 @@ namespace xt
         xarray<float> afloat(shape);
         xarray<double> adouble(shape);
 
-#define CHECK_RESULT_TYPE_FOR_ALL(INPUT, RESULT_TYPE)             \
-        CHECK_RESULT_TYPE(nansum(INPUT, {1, 2}), RESULT_TYPE);    \
-        CHECK_RESULT_TYPE(nanmean(INPUT, {1, 2}), double);        \
-        CHECK_RESULT_TYPE(nanvar(INPUT, {1, 2}), double);         \
-        CHECK_RESULT_TYPE(nanstd(INPUT, {1, 2}), double);
-
-#define CHECK_NAN_MIN_MAX_RESULT_TYPE(INPUT, RESULT_TYPE)         \
-        CHECK_RESULT_TYPE(nanmin(INPUT, {1, 2}), RESULT_TYPE)     \
-        CHECK_RESULT_TYPE(nanmax(INPUT, {1, 2}), RESULT_TYPE)
-
-#define CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(INPUT, TEMPLATE_TYPE, RESULT_TYPE, STD_TYPE)  \
-        CHECK_RESULT_TYPE(nansum<TEMPLATE_TYPE>(INPUT, {1, 2}), RESULT_TYPE)              \
-        CHECK_RESULT_TYPE(nanmean<TEMPLATE_TYPE>(INPUT, {1, 2}), RESULT_TYPE)             \
-        CHECK_RESULT_TYPE(nanvar<TEMPLATE_TYPE>(INPUT, {1, 2}), RESULT_TYPE)              \
-        CHECK_RESULT_TYPE(nanstd<TEMPLATE_TYPE>(INPUT, {1, 2}), STD_TYPE)
+#define CHECK_RESULT_TYPE_FOR_ALL(INPUT, RESULT_TYPE, MINMAX_TYPE)  \
+        CHECK_RESULT_TYPE(nansum(INPUT, {1, 2}), RESULT_TYPE);      \
+        CHECK_RESULT_TYPE(nanmean(INPUT, {1, 2}), double);          \
+        CHECK_RESULT_TYPE(nanvar(INPUT, {1, 2}), double);           \
+        CHECK_RESULT_TYPE(nanstd(INPUT, {1, 2}), double);           \
+        CHECK_RESULT_TYPE(nanmin(INPUT, {1, 2}), MINMAX_TYPE);      \
+        CHECK_RESULT_TYPE(nanmax(INPUT, {1, 2}), MINMAX_TYPE);
+
+#define CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(INPUT, TEMPLATE_TYPE, RESULT_TYPE, STD_TYPE, MINMAX_TYPE)  \
+        CHECK_RESULT_TYPE(nansum<TEMPLATE_TYPE>(INPUT, {1, 2}), RESULT_TYPE)                           \
+        CHECK_RESULT_TYPE(nanmean<TEMPLATE_TYPE>(INPUT, {1, 2}), RESULT_TYPE)                          \
+        CHECK_RESULT_TYPE(nanvar<TEMPLATE_TYPE>(INPUT, {1, 2}), RESULT_TYPE)                           \
+        CHECK_RESULT_TYPE(nanstd<TEMPLATE_TYPE>(INPUT, {1, 2}), STD_TYPE)                              \
+        CHECK_RESULT_TYPE(nanmin<TEMPLATE_TYPE>(INPUT, {1, 2}), MINMAX_TYPE)                           \
+        CHECK_RESULT_TYPE(nanmax<TEMPLATE_TYPE>(INPUT, {1, 2}), MINMAX_TYPE)
 
         /*********
          * short *
          *********/
-        CHECK_RESULT_TYPE_FOR_ALL(ashort, int);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(ashort, short, int, double);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(ashort, int, int, double);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(ashort, double, double, double);
-
-        CHECK_NAN_MIN_MAX_RESULT_TYPE(ashort, short);
+        CHECK_RESULT_TYPE_FOR_ALL(ashort, int, short);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(ashort, short, int, double, short);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(ashort, int, int, double, int);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(ashort, double, double, double, double);
 
         /******************
          * unsigned short *
          ******************/
-        CHECK_RESULT_TYPE_FOR_ALL(aushort, int);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aushort, unsigned short, int, double);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aushort, int, int, double);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aushort, double, double, double);
-
-        CHECK_NAN_MIN_MAX_RESULT_TYPE(aushort, unsigned short);
+        CHECK_RESULT_TYPE_FOR_ALL(aushort, int, unsigned short);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aushort, unsigned short, int, double, unsigned short);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aushort, int, int, double, int);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aushort, double, double, double, double);
 
         /*********
          * int *
          *********/
-        CHECK_RESULT_TYPE_FOR_ALL(aint, int);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aint, unsigned short, int, double);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aint, int, int, double);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aint, double, double, double);
-
-        CHECK_NAN_MIN_MAX_RESULT_TYPE(aint, int);
+        CHECK_RESULT_TYPE_FOR_ALL(aint, int, int);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aint, unsigned short, int, double, int);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aint, int, int, double, int);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aint, double, double, double, double);
 
         /****************
          * unsigned int *
          ****************/
-        CHECK_RESULT_TYPE_FOR_ALL(auint, unsigned int);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(auint, unsigned int, unsigned int, double);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(auint, unsigned int, unsigned int, double);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(auint, double, double, double);
-
-        CHECK_NAN_MIN_MAX_RESULT_TYPE(auint, unsigned int);
+        CHECK_RESULT_TYPE_FOR_ALL(auint, unsigned int, unsigned int);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(auint, unsigned int, unsigned int, double, unsigned int);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(auint, unsigned int, unsigned int, double, unsigned int);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(auint, double, double, double, double);
 
         /**********************
          * long long *
          **********************/
 #ifndef SKIP_ON_WERROR
         // intermediate computation done in double may imply precision loss
-        CHECK_RESULT_TYPE_FOR_ALL(along, signed long long);
+        CHECK_RESULT_TYPE_FOR_ALL(along, signed long long, signed long long);
 #endif
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(along, int, signed long long, double);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(along, long, signed long long, double);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(along, signed long long, signed long long, double);
-
-        CHECK_NAN_MIN_MAX_RESULT_TYPE(along, signed long long);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(along, int, signed long long, double, signed long long);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(along, long, signed long long, double, signed long long);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(along, signed long long, signed long long, double, signed long long);
 
         /**********************
          * unsigned long long *
          **********************/
 #ifndef SKIP_ON_WERROR
         // intermediate computation done in double may imply precision loss
-        CHECK_RESULT_TYPE_FOR_ALL(aulong, unsigned long long);
+        CHECK_RESULT_TYPE_FOR_ALL(aulong, unsigned long long, unsigned long long);
 #endif
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aulong, unsigned int, unsigned long long, double);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aulong, unsigned long, unsigned long long, double);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aulong, unsigned long long, unsigned long long, double);
-
-        CHECK_NAN_MIN_MAX_RESULT_TYPE(aulong, unsigned long long);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aulong, unsigned int, unsigned long long, double, unsigned long long);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aulong, unsigned long, unsigned long long, double, unsigned long long);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(aulong, unsigned long long, unsigned long long, double, unsigned long long);
 
         /*********
          * float *
          *********/
-        CHECK_RESULT_TYPE_FOR_ALL(afloat, float);
+        CHECK_RESULT_TYPE_FOR_ALL(afloat, float, float);
 #ifndef SKIP_ON_WERROR
         // final conversion to int may imply conversion loss
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(afloat, int, float, float);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(afloat, int, float, float, float);
 #endif
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(afloat, float, float, float);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(afloat, double, double, double);
-
-        CHECK_NAN_MIN_MAX_RESULT_TYPE(afloat, float);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(afloat, float, float, float, float);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(afloat, double, double, double, double);
 
         /**********
          * double *
          **********/
-        CHECK_RESULT_TYPE_FOR_ALL(adouble, double);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(adouble, float, double, double);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(adouble, double, double, double);
-        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(adouble, long double, long double, long double);
+        CHECK_RESULT_TYPE_FOR_ALL(adouble, double, double);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(adouble, float, double, double, double);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(adouble, double, double, double, double);
+        CHECK_TEMPLATED_RESULT_TYPE_FOR_ALL(adouble, long double, long double, long double, long double);
 
-        CHECK_NAN_MIN_MAX_RESULT_TYPE(adouble, double);
     }
 }