diff options
| author | eakozyrev <[email protected]> | 2026-07-07 12:40:35 +0300 |
|---|---|---|
| committer | eakozyrev <[email protected]> | 2026-07-07 13:17:41 +0300 |
| commit | dba709c362d120dbcaf83af9d7eebe7a3dd95f71 (patch) | |
| tree | 96fe693dcf89adbf2dc947cc5467eee4e332fc2e | |
| parent | d70e855305304f943a338b17d9893f9699310682 (diff) | |
YQL-21215: Finite values checks ListFromRange
1. Добавил проверку start/end на равенство бесконечности -\> возвращается пустой список
2. Изменил логику инкремента индекса (перевел на целочисленный тип), чтобы работало валидно при малых значениях float
commit_hash:f6b473a1d0227269591f9a683d81614696f0b040
| -rw-r--r-- | yql/essentials/docs/en/builtins/list.md | 1 | ||||
| -rw-r--r-- | yql/essentials/docs/ru/builtins/list.md | 1 | ||||
| -rw-r--r-- | yql/essentials/minikql/comp_nodes/mkql_listfromrange.cpp | 152 | ||||
| -rw-r--r-- | yql/essentials/minikql/comp_nodes/ut/mkql_listfromrange_ut.cpp | 63 |
4 files changed, 144 insertions, 73 deletions
diff --git a/yql/essentials/docs/en/builtins/list.md b/yql/essentials/docs/en/builtins/list.md index fffa60aaf9c..ef125e4f375 100644 --- a/yql/essentials/docs/en/builtins/list.md +++ b/yql/essentials/docs/en/builtins/list.md @@ -450,6 +450,7 @@ Specifics: * If the step is neither positive nor negative (0 or NaN), the result list is empty. * If any of the parameters is optional, the result list is optional. * If any of the parameters is `NULL`, the result is `NULL`. +* If any of the parameters is `double/float("inf")`, the result list is empty. #### Examples diff --git a/yql/essentials/docs/ru/builtins/list.md b/yql/essentials/docs/ru/builtins/list.md index aad2f4e3a32..fbb50573a42 100644 --- a/yql/essentials/docs/ru/builtins/list.md +++ b/yql/essentials/docs/ru/builtins/list.md @@ -718,6 +718,7 @@ ListFold1Map(List<T>?, (T)->Tuple<U,S>, (T, S)->Tuple<U,S>)->List<U>? * Если шаг не положительный и не отрицательный (0 или NaN), то список будет пустой. * Если один из параметров опциональный, то результат будет опциональный список. * Если один из параметров равен `NULL`, то результат будет `NULL`. +* Если один из параметров равен `double/float("inf")`, то список будет пустой. #### Примеры diff --git a/yql/essentials/minikql/comp_nodes/mkql_listfromrange.cpp b/yql/essentials/minikql/comp_nodes/mkql_listfromrange.cpp index 5a4c8fef6b3..91d673a4946 100644 --- a/yql/essentials/minikql/comp_nodes/mkql_listfromrange.cpp +++ b/yql/essentials/minikql/comp_nodes/mkql_listfromrange.cpp @@ -1,6 +1,7 @@ #include "mkql_listfromrange.h" #include <yql/essentials/minikql/computation/mkql_computation_node_holders.h> #include <yql/essentials/minikql/computation/mkql_computation_node_codegen.h> // Y_IGNORE +#include <yql/essentials/minikql/computation/mkql_custom_list.h> #include <yql/essentials/minikql/mkql_node_cast.h> #include <yql/essentials/minikql/mkql_safe_arithmetic_ops.h> @@ -57,11 +58,7 @@ private: class TValue: public TComputationValue<TValue> { public: - template <bool Asc, bool Float> - class TIterator; - - template <bool Asc> - class TIterator<Asc, false>: public TComputationValue<TIterator<Asc, false>> { + class TIterator: public TComputationValue<TIterator> { public: TIterator(TMemoryUsageInfo* memInfo, T start, T end, TStep step) : TComputationValue<TIterator>(memInfo) @@ -101,43 +98,6 @@ private: } }; - template <bool Asc> - class TIterator<Asc, true>: public TComputationValue<TIterator<Asc, true>> { - public: - TIterator(TMemoryUsageInfo* memInfo, T start, T end, TStep step) - : TComputationValue<TIterator>(memInfo) - , Start(start) - , Index(-T(1)) - , Limit(end - start) - , Step(step) - { - } - - private: - bool Skip() final { - const auto next = Index + T(1); - if (Asc ? next * Step < Limit : next * Step > Limit) { - Index = next; - return true; - } - - return false; - } - - bool Next(NUdf::TUnboxedValue& value) final { - if (!Skip()) { - return false; - } - - value = NUdf::TUnboxedValuePod(Start + Index * Step); - return true; - } - - const T Start; - T Index; - const T Limit; - const TStep Step; - }; TValue(TMemoryUsageInfo* memInfo, TComputationContext& ctx, T start, T end, TStep step) : TComputationValue<TValue>(memInfo) , Ctx(ctx) @@ -149,35 +109,16 @@ private: protected: NUdf::TUnboxedValue GetListIterator() const override { - if (Step > TStep(0)) { - return Ctx.HolderFactory.template Create<TIterator<true, std::is_floating_point<T>::value>>(Start, End, Step); - } else if (Step < TStep(0)) { - return Ctx.HolderFactory.template Create<TIterator<false, std::is_floating_point<T>::value>>(Start, End, Step); + if (Step != TStep(0)) { + return Ctx.HolderFactory.template Create<TIterator>(Start, End, Step); } else { return Ctx.HolderFactory.GetEmptyContainerLazy(); } } ui64 GetListLength() const final { - if constexpr (std::is_integral_v<T>) { - return GetElementsCount<T, TStep>(Start, End, Step); - } - - if (Step > T(0) && Start < End) { - ui64 len = 0ULL; - for (T i = 0; i * Step < End - Start; i += T(1)) { - ++len; - } - return len; - } else if (Step < T(0) && Start > End) { - ui64 len = 0ULL; - for (T i = 0; i * Step > End - Start; i += T(1)) { - ++len; - } - return len; - } else { - return 0ULL; - } + static_assert(std::is_integral_v<T>, "Invalid type"); + return GetElementsCount<T, TStep>(Start, End, Step); } bool HasListItems() const final { @@ -191,7 +132,73 @@ private: } bool HasFastListLength() const final { - return std::is_integral<T>(); + return true; + } + + TComputationContext& Ctx; + const T Start; + const T End; + const TStep Step; + }; + + class TFloatingValue: public TCustomListValue { + static constexpr ui64 MaxElementsCount = std::numeric_limits<ui64>::max(); + + public: + class TIterator: public TComputationValue<TIterator> { + public: + TIterator(TMemoryUsageInfo* memInfo, T start, T end, TStep step) + : TComputationValue<TIterator>(memInfo) + , Start(start) + , End(end) + , Step(step) + , ValidInput(CheckInput(start, end, step)) + , Index(0ULL) + { + } + + private: + bool Next(NUdf::TUnboxedValue& value) final { + if (!ValidInput || Index >= MaxElementsCount) { + return false; + } + const T current = Start + Index * Step; + const bool outOfRange = Step > 0 ? current >= End : current <= End; + if (outOfRange) { + return false; + } + value = NUdf::TUnboxedValuePod(current); + Index++; + return true; + } + + static bool CheckInput(T start, T end, TStep step) { + if (step == T(0) || std::isnan(step) || std::isnan(start) || std::isnan(end) || + std::isinf(start) || std::isinf(end) || std::isinf(step) || start == end) { + return false; + } + + return true; + } + + const T Start; + const T End; + const TStep Step; + const bool ValidInput; + ui64 Index; + }; + TFloatingValue(TMemoryUsageInfo* memInfo, TComputationContext& ctx, T start, T end, TStep step) + : TCustomListValue(memInfo) + , Ctx(ctx) + , Start(start) + , End(end) + , Step(step) + { + } + + protected: + NUdf::TUnboxedValue GetListIterator() const override { + return Ctx.HolderFactory.template Create<TIterator>(Start, End, Step); } TComputationContext& Ctx; @@ -202,10 +209,9 @@ private: class TTzValue: public TValue { public: - template <bool Asc> - class TTzIterator: public TValue::template TIterator<Asc, false> { + class TTzIterator: public TValue::TIterator { public: - using TBase = typename TValue::template TIterator<Asc, false>; + using TBase = typename TValue::TIterator; TTzIterator(TMemoryUsageInfo* memInfo, T start, T end, TStep step, ui16 Tz) : TBase(memInfo, start, end, step) , TimezoneId(Tz) @@ -223,10 +229,8 @@ private: const ui16 TimezoneId; }; NUdf::TUnboxedValue GetListIterator() const final { - if (TValue::Step > TStep(0)) { - return TValue::Ctx.HolderFactory.template Create<TTzIterator<true>>(TValue::Start, TValue::End, TValue::Step, TimezoneId); - } else if (TValue::Step < TStep(0)) { - return TValue::Ctx.HolderFactory.template Create<TTzIterator<false>>(TValue::Start, TValue::End, TValue::Step, TimezoneId); + if (TValue::Step != TStep(0)) { + return TValue::Ctx.HolderFactory.template Create<TTzIterator>(TValue::Start, TValue::End, TValue::Step, TimezoneId); } else { return TValue::Ctx.HolderFactory.GetEmptyContainerLazy(); } @@ -299,6 +303,8 @@ private: static NUdf::TUnboxedValuePod MakeList(TComputationContext& ctx, T start, T end, TStep step, ui16 timezoneId) { if constexpr (TzDate) { return ctx.HolderFactory.Create<TTzValue>(ctx, start, end, step, timezoneId); + } else if constexpr (std::is_floating_point_v<T>) { + return ctx.HolderFactory.Create<TFloatingValue>(ctx, start, end, step); } else { return ctx.HolderFactory.Create<TValue>(ctx, start, end, step); } diff --git a/yql/essentials/minikql/comp_nodes/ut/mkql_listfromrange_ut.cpp b/yql/essentials/minikql/comp_nodes/ut/mkql_listfromrange_ut.cpp index 7f6a60456b0..9ebe93d3aeb 100644 --- a/yql/essentials/minikql/comp_nodes/ut/mkql_listfromrange_ut.cpp +++ b/yql/essentials/minikql/comp_nodes/ut/mkql_listfromrange_ut.cpp @@ -391,6 +391,69 @@ Y_UNIT_TEST_LLVM(TestResverseUnsignedShorts) { const auto list = graph->GetValue(); AssertUnboxedValueElementEqual(list, TVector<ui16>{60000U, 59998U, 59996U, 59994U, 59992U}); } + +namespace { +template <bool UseLLVM> +void TestFloatStep(TSetup<UseLLVM>& setup, float startVal, float endVal, float stepVal, ui64 expectedLength) { + TProgramBuilder& pb = *setup.PgmBuilder; + + const auto start = NTest::ConvertValueToLiteralNode(pb, startVal); + const auto end = NTest::ConvertValueToLiteralNode(pb, endVal); + const auto step = NTest::ConvertValueToLiteralNode(pb, stepVal); + + const auto range = pb.ListFromRange(start, end, step); + + const auto graph = setup.BuildGraph(range); + const auto list = graph->GetValue(); + UNIT_ASSERT_VALUES_EQUAL(list.GetListLength(), expectedLength); + + TVector<float> vector(expectedLength); + for (size_t i = 0; i < expectedLength; ++i) { + vector[i] = startVal + i * stepVal; + } + AssertUnboxedValueElementEqual(list, vector); +} +} // namespace + +Y_UNIT_TEST_LLVM(TestFloatSmallStepNoInfiniteLoop) { + TSetup<LLVM> setup; + TestFloatStep(setup, 1.0f, 2.0f, 1e-6f, 1000000); +} + +Y_UNIT_TEST_LLVM(TestFloatSmallNegativeStepNoInfiniteLoop) { + TSetup<LLVM> setup; + TestFloatStep(setup, 2.0f, 1.0f, -1e-6f, 1000000); +} + +Y_UNIT_TEST_LLVM(TestFloatNegativeStep) { + TSetup<LLVM> setup; + TestFloatStep(setup, 1.0f, 2.0f, -1e-8f, 0); +} + +Y_UNIT_TEST_LLVM(TestFloatWithInfinityEnd) { + TSetup<LLVM> setup; + TestFloatStep(setup, 0.0f, INFINITY, 1.0f, 0); +} +Y_UNIT_TEST_LLVM(TestFloatWithNegativeInfinityStart) { + TSetup<LLVM> setup; + TestFloatStep(setup, -INFINITY, 2.0f, 1.0f, 0); +} + +Y_UNIT_TEST_LLVM(TestFloatWithInfinityStep) { + TSetup<LLVM> setup; + TestFloatStep(setup, 1.0f, 2.0f, INFINITY, 0); +} + +Y_UNIT_TEST_LLVM(TestFloatWithExtraLargeStep) { + TSetup<LLVM> setup; + TestFloatStep(setup, -0.0000000000000000000000000000000116082984f, 0.0000000000000000096245773f, 6925700880000000000000000000000.0f, 1); +} + +Y_UNIT_TEST_LLVM(TestFloatWithLargeNegativeStep) { + TSetup<LLVM> setup; + TestFloatStep(setup, -0.000000000346562223f, -277088.812f, -23368489200000.0f, 1); +} + } // Y_UNIT_TEST_SUITE(TMiniKQLListFromRangeTest) } // namespace NMiniKQL } // namespace NKikimr |
