diff --git a/cpp/src/reductions/minmax.cu b/cpp/src/reductions/minmax.cu index 5e58e0644ee..06264693d88 100644 --- a/cpp/src/reductions/minmax.cu +++ b/cpp/src/reductions/minmax.cu @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -144,6 +145,18 @@ struct assign_min_max { T* max_data; }; +template +std::unique_ptr> make_minmax_scalar(cudf::data_type type, + cuda::stream_ref stream, + rmm::device_async_resource_ref mr) +{ + if constexpr (cudf::is_fixed_point()) { + return std::make_unique>( + device_storage_type_t{}, numeric::scale_type{type.scale()}, true, stream, mr); + } + return std::make_unique>(T{}, true, stream, mr); +} + /** * @brief Computes a minmax_pair reduction directly over a dictionary column's decoded key * values, i.e. `keys[indices[i]]` for each row `i`. @@ -185,10 +198,9 @@ struct minmax_dictionary_functor { { using storage_type = device_storage_type_t; auto dev_result = reduce_dictionary(col, stream); - using ScalarType = cudf::scalar_type_t; auto const key_type = dictionary_column_view(col).keys().type(); - auto minimum = std::make_unique(T{}, true, stream, mr); - auto maximum = std::make_unique(T{}, true, stream, mr); + auto minimum = make_minmax_scalar(key_type, stream, mr); + auto maximum = make_minmax_scalar(key_type, stream, mr); cudf::detail::device_single_thread( assign_min_max{dev_result.data(), minimum->data(), maximum->data()}, stream); return {std::move(minimum), std::move(maximum)}; @@ -255,13 +267,12 @@ struct minmax_functor { // compute minimum and maximum values auto dev_result = reduce(col, stream); // create output scalars - using ScalarType = cudf::scalar_type_t; - auto minimum = new ScalarType(T{}, true, stream, mr); - auto maximum = new ScalarType(T{}, true, stream, mr); + auto minimum = make_minmax_scalar(col.type(), stream, mr); + auto maximum = make_minmax_scalar(col.type(), stream, mr); // copy dev_result to the output scalars cudf::detail::device_single_thread( assign_min_max{dev_result.data(), minimum->data(), maximum->data()}, stream); - return {std::unique_ptr(minimum), std::unique_ptr(maximum)}; + return {std::move(minimum), std::move(maximum)}; } /** diff --git a/cpp/tests/reductions/reduction_tests.cpp b/cpp/tests/reductions/reduction_tests.cpp index 200dd98da13..67a388e41ec 100644 --- a/cpp/tests/reductions/reduction_tests.cpp +++ b/cpp/tests/reductions/reduction_tests.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -2179,6 +2180,61 @@ TYPED_TEST(FixedPointTestAllReps, FixedPointReductionMaxLarge) } } +TYPED_TEST(FixedPointTestAllReps, FixedPointMinMax) +{ + using namespace numeric; + using decimalXX = TypeParam; + using RepType = cudf::device_storage_type_t; + using fp_wrapper = cudf::test::fixed_point_column_wrapper; + + for (auto const i : {0, -1, -2, -3}) { + auto const scale = scale_type{i}; + auto const column = fp_wrapper{{2, 3, 1, 4}, scale}; + + auto const expected_min = decimalXX{scaled_integer{1, scale}}; + auto const expected_max = decimalXX{scaled_integer{4, scale}}; + + auto const result = cudf::minmax(column); + auto const min_scalar = static_cast*>(result.first.get()); + auto const max_scalar = static_cast*>(result.second.get()); + + // Scale must be preserved in the output scalars. + EXPECT_EQ(min_scalar->type().scale(), i); + EXPECT_EQ(max_scalar->type().scale(), i); + + EXPECT_EQ(min_scalar->fixed_point_value(), expected_min); + EXPECT_EQ(max_scalar->fixed_point_value(), expected_max); + } +} + +TYPED_TEST(FixedPointTestAllReps, FixedPointMinMaxWithNulls) +{ + using namespace numeric; + using decimalXX = TypeParam; + using RepType = cudf::device_storage_type_t; + using fp_wrapper = cudf::test::fixed_point_column_wrapper; + + for (auto const i : {0, -1, -2, -3}) { + auto const scale = scale_type{i}; + // valid: {2, null, 1, null, 4} — min=1, max=4 + auto const column = fp_wrapper{{2, 3, 1, 5, 4}, {true, false, true, false, true}, scale}; + + auto const expected_min = decimalXX{scaled_integer{1, scale}}; + auto const expected_max = decimalXX{scaled_integer{4, scale}}; + + auto const result = cudf::minmax(column); + auto const min_scalar = static_cast*>(result.first.get()); + auto const max_scalar = static_cast*>(result.second.get()); + + // Scale must be preserved in the output scalars. + EXPECT_EQ(min_scalar->type().scale(), i); + EXPECT_EQ(max_scalar->type().scale(), i); + + EXPECT_EQ(min_scalar->fixed_point_value(), expected_min); + EXPECT_EQ(max_scalar->fixed_point_value(), expected_max); + } +} + TYPED_TEST(FixedPointTestAllReps, FixedPointReductionNUnique) { using namespace numeric; @@ -3098,6 +3154,65 @@ TYPED_TEST(DictionaryReductionTest, Quantile) 45.0); } +template +struct FixedPointDictionaryReductionTest : public cudf::test::BaseFixture {}; + +TYPED_TEST_SUITE(FixedPointDictionaryReductionTest, cudf::test::FixedPointTypes); + +TYPED_TEST(FixedPointDictionaryReductionTest, FixedPointDictionaryMinMax) +{ + using namespace numeric; + using decimalXX = TypeParam; + using RepType = cudf::device_storage_type_t; + using fp_wrapper = cudf::test::fixed_point_column_wrapper; + + for (auto const i : {0, -1, -2, -3}) { + auto const scale = scale_type{i}; + auto const col = fp_wrapper{{1, 2, 3, 4}, scale}; + auto const dict = cudf::dictionary::encode(col); + auto const expected_min = decimalXX{scaled_integer{1, scale}}; + auto const expected_max = decimalXX{scaled_integer{4, scale}}; + + auto const result = cudf::minmax(dict->view()); + auto const min_scalar = static_cast*>(result.first.get()); + auto const max_scalar = static_cast*>(result.second.get()); + + EXPECT_EQ(min_scalar->type().scale(), i); + EXPECT_EQ(max_scalar->type().scale(), i); + EXPECT_EQ(min_scalar->fixed_point_value(), expected_min); + EXPECT_EQ(max_scalar->fixed_point_value(), expected_max); + EXPECT_TRUE(min_scalar->is_valid()); + EXPECT_TRUE(max_scalar->is_valid()); + } +} + +TYPED_TEST(FixedPointDictionaryReductionTest, FixedPointDictionaryMinMaxWithNulls) +{ + using namespace numeric; + using decimalXX = TypeParam; + using RepType = cudf::device_storage_type_t; + using fp_wrapper = cudf::test::fixed_point_column_wrapper; + + for (auto const i : {0, -1, -2, -3}) { + auto const scale = scale_type{i}; + auto const col = fp_wrapper{{1, 2, 3, 4, 5}, {true, false, true, false, true}, scale}; + auto const dict = cudf::dictionary::encode(col); + auto const expected_min = decimalXX{scaled_integer{1, scale}}; + auto const expected_max = decimalXX{scaled_integer{5, scale}}; + + auto const result = cudf::minmax(dict->view()); + auto const min_scalar = static_cast*>(result.first.get()); + auto const max_scalar = static_cast*>(result.second.get()); + + EXPECT_EQ(min_scalar->type().scale(), i); + EXPECT_EQ(max_scalar->type().scale(), i); + EXPECT_EQ(min_scalar->fixed_point_value(), expected_min); + EXPECT_EQ(max_scalar->fixed_point_value(), expected_max); + EXPECT_TRUE(min_scalar->is_valid()); + EXPECT_TRUE(max_scalar->is_valid()); + } +} + struct ListReductionTest : public cudf::test::BaseFixture { void reduction_test(cudf::column_view const& input_data, cudf::column_view const& expected_value,