#pragma once #include #include #include #include namespace DB { /** Функции округления: * roundToExp2 - вниз до ближайшей степени двойки; * roundDuration - вниз до ближайшего из: 0, 1, 10, 30, 60, 120, 180, 240, 300, 600, 1200, 1800, 3600, 7200, 18000, 36000; * roundAge - вниз до ближайшего из: 0, 18, 25, 35, 45. * round(x, N) - арифметическое округление (N - сколько знаков после запятой оставить; 0 по умолчанию). * ceil(x, N) - наименьшее число, которое не меньше x (N - сколько знаков после запятой оставить; 0 по умолчанию). * floor(x, N) - наибольшее число, которое не больше x (N - сколько знаков после запятой оставить; 0 по умолчанию). */ template struct RoundToExp2Impl { typedef A ResultType; static inline A apply(A x) { return x <= 0 ? static_cast(0) : (static_cast(1) << static_cast(log2(static_cast(x)))); } }; template<> struct RoundToExp2Impl { typedef Float32 ResultType; static inline Float32 apply(Float32 x) { return static_cast(x < 1 ? 0. : pow(2., floor(log2(x)))); } }; template<> struct RoundToExp2Impl { typedef Float64 ResultType; static inline Float64 apply(Float64 x) { return x < 1 ? 0. : pow(2., floor(log2(x))); } }; template struct RoundDurationImpl { typedef UInt16 ResultType; static inline ResultType apply(A x) { return x < 1 ? 0 : (x < 10 ? 1 : (x < 30 ? 10 : (x < 60 ? 30 : (x < 120 ? 60 : (x < 180 ? 120 : (x < 240 ? 180 : (x < 300 ? 240 : (x < 600 ? 300 : (x < 1200 ? 600 : (x < 1800 ? 1200 : (x < 3600 ? 1800 : (x < 7200 ? 3600 : (x < 18000 ? 7200 : (x < 36000 ? 18000 : 36000)))))))))))))); } }; template struct RoundAgeImpl { typedef UInt8 ResultType; static inline ResultType apply(A x) { return x < 18 ? 0 : (x < 25 ? 18 : (x < 35 ? 25 : (x < 45 ? 35 : 45))); } }; /// Реализация функций округления на низком уровне. template struct RoundingComputation { }; template struct RoundingComputation { using Data = std::array; using Scale = __m128; template static inline void prepareScale(size_t scale, Scale & mm_scale, typename std::enable_if::type * = nullptr) { Float32 fscale = static_cast(scale); mm_scale = _mm_load1_ps(&fscale); } template static inline void prepareScale(size_t scale, Scale & mm_scale, typename std::enable_if::type * = nullptr) { } template static inline void compute(const Data & in, const Scale & mm_scale, Data & out, typename std::enable_if::type * = nullptr) { __m128 mm_value = _mm_loadu_ps(reinterpret_cast(&in)); mm_value = _mm_mul_ps(mm_value, mm_scale); mm_value = _mm_round_ps(mm_value, rounding_mode); mm_value = _mm_div_ps(mm_value, mm_scale); _mm_storeu_ps(reinterpret_cast(&out), mm_value); } template static inline void compute(const Data & in, const Scale & mm_scale, Data & out, typename std::enable_if::type * = nullptr) { __m128 mm_value = _mm_loadu_ps(reinterpret_cast(&in)); mm_value = _mm_round_ps(mm_value, rounding_mode); _mm_storeu_ps(reinterpret_cast(&out), mm_value); } }; template struct RoundingComputation { using Data = std::array; using Scale = __m128d; template static inline void prepareScale(size_t scale, Scale & mm_scale, typename std::enable_if::type * = nullptr) { Float64 fscale = static_cast(scale); mm_scale = _mm_load1_pd(&fscale); } template static inline void prepareScale(size_t scale, Scale & mm_scale, typename std::enable_if::type * = nullptr) { } template static inline void compute(const Data & in, const Scale & mm_scale, Data & out, typename std::enable_if::type * = nullptr) { __m128d mm_value = _mm_loadu_pd(reinterpret_cast(&in)); mm_value = _mm_mul_pd(mm_value, mm_scale); mm_value = _mm_round_pd(mm_value, rounding_mode); mm_value = _mm_div_pd(mm_value, mm_scale); _mm_storeu_pd(reinterpret_cast(&out), mm_value); } template static inline void compute(const Data & in, const Scale & mm_scale, Data & out, typename std::enable_if::type * = nullptr) { __m128d mm_value = _mm_loadu_pd(reinterpret_cast(&in)); mm_value = _mm_round_pd(mm_value, rounding_mode); _mm_storeu_pd(reinterpret_cast(&out), mm_value); } }; /// Реализация функций округления на высоком уровне. template struct FunctionRoundingImpl { }; /// В случае целочисленных значений не выполяется округления. template struct FunctionRoundingImpl::value>::type> { static inline void apply(const PODArray & in, size_t scale, typename ColumnVector::Container_t & out) { size_t size = in.size(); for (size_t i = 0; i < size; ++i) out[i] = in[i]; } static inline T apply(T val, size_t scale) { return val; } }; template struct FunctionRoundingImpl::value>::type> { private: using Op = RoundingComputation; using Data = typename Op::Data; using Scale = typename Op::Scale; public: static inline void apply(const PODArray & in, size_t scale, typename ColumnVector::Container_t & out) { Scale mm_scale; Op::prepareScale(scale, mm_scale); const size_t size = in.size(); const size_t data_size = std::tuple_size(); size_t i; for (i = 0; i < (size - data_size + 1); i += data_size) { Data tmp; for (size_t j = 0; j < data_size; ++j) tmp[j] = in[i + j]; Data res; Op::compute(tmp, mm_scale, res); for (size_t j = 0; j < data_size; ++j) out[i + j] = res[j]; } if (i < size) { Data tmp{0}; for (size_t j = 0; (j < data_size) && ((i + j) < size); ++j) tmp[j] = in[i + j]; Data res; Op::compute(tmp, mm_scale, res); for (size_t j = 0; (j < data_size) && ((i + j) < size); ++j) out[i + j] = res[j]; } } static inline T apply(T val, size_t scale) { if (val == 0) return val; else { Scale mm_scale; Op::prepareScale(scale, mm_scale); Data tmp{0}; tmp[0] = val; Data res; Op::compute(tmp, mm_scale, res); return res[0]; } } }; template struct PrecisionForType { template static inline bool apply(const ColumnPtr & column, UInt8 & precision, typename std::enable_if::value>::type * = nullptr) { using ColumnType = ColumnConst; const ColumnType * precision_col = typeid_cast(&*column); if (precision_col == nullptr) return false; U val = precision_col->getData(); if (val < 0) val = 0; else if (val >= static_cast(std::numeric_limits::digits10)) val = static_cast(std::numeric_limits::digits10); precision = static_cast(val); return true; } /// Для целых чисел точность не имеет значения. template static inline bool apply(const ColumnPtr & column, UInt8 & precision, typename std::enable_if::value>::type * = nullptr) { using ColumnType = ColumnConst; const ColumnType * precision_col = typeid_cast(&*column); if (precision_col == nullptr) return false; precision = 0; return true; } }; /// Следующий код генерирует во время сборки таблицу степеней числа 10. namespace { /// Отдельные степени числа 10. template struct PowerOf10 { static const size_t value = 10 * PowerOf10::value; }; template<> struct PowerOf10<0> { static const size_t value = 1; }; } /// Объявление и определение контейнера содержащего таблицу степеней числа 10. template struct TableContainer { static const std::array values; }; template const std::array TableContainer::values = { TArgs... }; /// Генератор первых N степеней. template struct FillArrayImpl { using result = typename FillArrayImpl::value, TArgs...>::result; }; template struct FillArrayImpl<0, TArgs...> { using result = TableContainer::value, TArgs...>; }; template struct FillArray { using result = typename FillArrayImpl::result; }; /** Шаблон для функций, которые вычисляют приближенное значение входного параметра * типа (U)Int8/16/32/64 или Float32/64 и принимают дополнительный необязятельный * параметр указывающий сколько знаков после запятой оставить (по умолчанию - 0). * Op - функция (round/floor/ceil) */ template class FunctionRounding : public IFunction { public: static constexpr auto name = Name::name; static IFunction * create(const Context & context) { return new FunctionRounding; } private: using PowersOf10 = FillArray::digits10 + 1>::result; private: template bool checkType(const IDataType * type) const { return typeid_cast(type) != nullptr; } template bool executeForType(Block & block, const ColumnNumbers & arguments, size_t result) { using OpWithScale = FunctionRoundingImpl; using OpWithoutScale = FunctionRoundingImpl; if (ColumnVector * col = typeid_cast *>(&*block.getByPosition(arguments[0]).column)) { ColumnVector * col_res = new ColumnVector; block.getByPosition(result).column = col_res; typename ColumnVector::Container_t & vec_res = col_res->getData(); vec_res.resize(col->getData().size()); UInt8 precision = 0; if (arguments.size() == 2) precision = getPrecision(block.getByPosition(arguments[1]).column); if (precision > 0) OpWithScale::apply(col->getData(), PowersOf10::values[precision], vec_res); else OpWithoutScale::apply(col->getData(), 0, vec_res); return true; } else if (ColumnConst * col = typeid_cast *>(&*block.getByPosition(arguments[0]).column)) { UInt8 precision = 0; if (arguments.size() == 2) precision = getPrecision(block.getByPosition(arguments[1]).column); T res; if (precision > 0) res = OpWithScale::apply(col->getData(), PowersOf10::values[precision]); else res = OpWithoutScale::apply(col->getData(), 0); ColumnConst * col_res = new ColumnConst(col->size(), res); block.getByPosition(result).column = col_res; return true; } return false; } /// В зависимости от входного параметра, определить какая нужна точность /// для результата. template UInt8 getPrecision(const ColumnPtr & column) { UInt8 precision = 0; if (!( PrecisionForType::apply(column, precision) || PrecisionForType::apply(column, precision) || PrecisionForType::apply(column, precision) || PrecisionForType::apply(column, precision) || PrecisionForType::apply(column, precision) || PrecisionForType::apply(column, precision) || PrecisionForType::apply(column, precision) || PrecisionForType::apply(column, precision) || PrecisionForType::apply(column, precision))) { throw Exception("Illegal column " + column->getName() + " of second ('precision') argument of function " + getName(), ErrorCodes::ILLEGAL_COLUMN); } return precision; } public: /// Получить имя функции. String getName() const override { return name; } /// Получить типы результата по типам аргументов. Если функция неприменима для данных аргументов - кинуть исключение. DataTypePtr getReturnType(const DataTypes & arguments) const override { if ((arguments.size() < 1) || (arguments.size() > 2)) throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + toString(arguments.size()) + ", should be 1 or 2.", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); if (arguments.size() == 2) { const IDataType * type = &*arguments[1]; if (!( checkType(type) || checkType(type) || checkType(type) || checkType(type) || checkType(type) || checkType(type) || checkType(type) || checkType(type))) { throw Exception("Illegal type in second argument of function " + getName(), ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); } } const IDataType * type = &*arguments[0]; if (!type->behavesAsNumber()) throw Exception("Illegal type " + arguments[0]->getName() + " of argument of function " + getName(), ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); return arguments[0]; } /// Выполнить функцию над блоком. void execute(Block & block, const ColumnNumbers & arguments, size_t result) override { if (!( executeForType(block, arguments, result) || executeForType(block, arguments, result) || executeForType(block, arguments, result) || executeForType(block, arguments, result) || executeForType(block, arguments, result) || executeForType(block, arguments, result) || executeForType(block, arguments, result) || executeForType(block, arguments, result) || executeForType(block, arguments, result) || executeForType(block, arguments, result))) { throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() + " of argument of function " + getName(), ErrorCodes::ILLEGAL_COLUMN); } } }; struct NameRoundToExp2 { static constexpr auto name = "roundToExp2"; }; struct NameRoundDuration { static constexpr auto name = "roundDuration"; }; struct NameRoundAge { static constexpr auto name = "roundAge"; }; struct NameRound { static constexpr auto name = "round"; }; struct NameCeil { static constexpr auto name = "ceil"; }; struct NameFloor { static constexpr auto name = "floor"; }; typedef FunctionUnaryArithmetic FunctionRoundToExp2; typedef FunctionUnaryArithmetic FunctionRoundDuration; typedef FunctionUnaryArithmetic FunctionRoundAge; typedef FunctionRounding FunctionRound; typedef FunctionRounding FunctionCeil; typedef FunctionRounding FunctionFloor; }