2012-12-24 08:16:25 +00:00
|
|
|
#pragma once
|
|
|
|
|
2017-04-01 09:19:00 +00:00
|
|
|
#include <Functions/FunctionsArithmetic.h>
|
2017-07-21 06:35:58 +00:00
|
|
|
#include <Functions/FunctionHelpers.h>
|
2017-04-01 09:19:00 +00:00
|
|
|
#include <IO/WriteHelpers.h>
|
2017-03-12 10:13:45 +00:00
|
|
|
|
2018-09-10 14:11:30 +00:00
|
|
|
#include <common/intExp.h>
|
2015-05-14 12:08:27 +00:00
|
|
|
#include <cmath>
|
|
|
|
#include <type_traits>
|
|
|
|
#include <array>
|
2017-09-16 22:12:24 +00:00
|
|
|
#include <ext/bit_cast.h>
|
2012-12-24 08:16:25 +00:00
|
|
|
|
2017-01-27 19:55:33 +00:00
|
|
|
#if __SSE4_1__
|
2017-04-01 07:20:54 +00:00
|
|
|
#include <smmintrin.h>
|
2016-01-16 00:45:19 +00:00
|
|
|
#endif
|
|
|
|
|
2017-09-16 22:12:24 +00:00
|
|
|
|
2012-12-24 08:16:25 +00:00
|
|
|
namespace DB
|
|
|
|
{
|
2014-11-12 17:23:26 +00:00
|
|
|
|
2017-06-13 02:06:53 +00:00
|
|
|
namespace ErrorCodes
|
|
|
|
{
|
|
|
|
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-05-27 15:45:25 +00:00
|
|
|
/** Rounding Functions:
|
2017-09-17 17:58:30 +00:00
|
|
|
* round(x, N) - rounding to nearest (N = 0 by default). Use banker's rounding for floating point numbers.
|
|
|
|
* floor(x, N) is the largest number <= x (N = 0 by default).
|
|
|
|
* ceil(x, N) is the smallest number >= x (N = 0 by default).
|
|
|
|
* trunc(x, N) - is the largest by absolute value number that is not greater than x by absolute value (N = 0 by default).
|
2017-04-01 07:20:54 +00:00
|
|
|
*
|
2017-09-17 17:58:30 +00:00
|
|
|
* The value of the parameter N (scale):
|
2017-05-27 15:45:25 +00:00
|
|
|
* - N > 0: round to the number with N decimal places after the decimal point
|
|
|
|
* - N < 0: round to an integer with N zero characters
|
|
|
|
* - N = 0: round to an integer
|
2017-09-17 17:58:30 +00:00
|
|
|
*
|
|
|
|
* Type of the result is the type of argument.
|
|
|
|
* For integer arguments, when passing negative scale, overflow can occur.
|
|
|
|
* In that case, the behavior is implementation specific.
|
|
|
|
*
|
|
|
|
* roundToExp2 - down to the nearest power of two (see below);
|
|
|
|
*
|
|
|
|
* Deprecated functions:
|
|
|
|
* roundDuration - down to the nearest of: 0, 1, 10, 30, 60, 120, 180, 240, 300, 600, 1200, 1800, 3600, 7200, 18000, 36000;
|
|
|
|
* roundAge - down to the nearest of: 0, 18, 25, 35, 45, 55.
|
2017-04-01 07:20:54 +00:00
|
|
|
*/
|
2017-03-09 03:34:09 +00:00
|
|
|
|
2017-09-16 22:12:24 +00:00
|
|
|
template <typename T>
|
2017-12-25 04:01:46 +00:00
|
|
|
inline std::enable_if_t<std::is_integral_v<T> && (sizeof(T) <= sizeof(UInt32)), T>
|
2017-09-16 22:12:24 +00:00
|
|
|
roundDownToPowerOfTwo(T x)
|
2017-03-09 03:34:09 +00:00
|
|
|
{
|
2017-09-16 22:12:24 +00:00
|
|
|
return x <= 0 ? 0 : (T(1) << (31 - __builtin_clz(x)));
|
|
|
|
}
|
2014-11-12 17:23:26 +00:00
|
|
|
|
2017-09-16 22:12:24 +00:00
|
|
|
template <typename T>
|
2017-12-25 04:01:46 +00:00
|
|
|
inline std::enable_if_t<std::is_integral_v<T> && (sizeof(T) == sizeof(UInt64)), T>
|
2017-09-16 22:12:24 +00:00
|
|
|
roundDownToPowerOfTwo(T x)
|
|
|
|
{
|
|
|
|
return x <= 0 ? 0 : (T(1) << (63 - __builtin_clzll(x)));
|
|
|
|
}
|
2014-11-12 17:23:26 +00:00
|
|
|
|
2017-09-16 22:12:24 +00:00
|
|
|
template <typename T>
|
2017-12-25 04:01:46 +00:00
|
|
|
inline std::enable_if_t<std::is_same_v<T, Float32>, T>
|
2017-09-16 22:12:24 +00:00
|
|
|
roundDownToPowerOfTwo(T x)
|
2017-03-09 03:34:09 +00:00
|
|
|
{
|
2017-09-16 22:12:24 +00:00
|
|
|
return ext::bit_cast<T>(ext::bit_cast<UInt32>(x) & ~((1ULL << 23) - 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename T>
|
2017-12-25 04:01:46 +00:00
|
|
|
inline std::enable_if_t<std::is_same_v<T, Float64>, T>
|
2017-09-16 22:12:24 +00:00
|
|
|
roundDownToPowerOfTwo(T x)
|
|
|
|
{
|
|
|
|
return ext::bit_cast<T>(ext::bit_cast<UInt64>(x) & ~((1ULL << 52) - 1));
|
|
|
|
}
|
2014-11-12 17:23:26 +00:00
|
|
|
|
2017-09-17 17:58:30 +00:00
|
|
|
/** For integer data types:
|
|
|
|
* - if number is greater than zero, round it down to nearest power of two (example: roundToExp2(100) = 64, roundToExp2(64) = 64);
|
|
|
|
* - otherwise, return 0.
|
|
|
|
*
|
|
|
|
* For floating point data types: zero out mantissa, but leave exponent.
|
|
|
|
* - if number is greater than zero, round it down to nearest power of two (example: roundToExp2(3) = 2);
|
|
|
|
* - negative powers are also used (example: roundToExp2(0.7) = 0.5);
|
|
|
|
* - if number is zero, return zero;
|
|
|
|
* - if number is less than zero, the result is symmetrical: roundToExp2(x) = -roundToExp2(-x). (example: roundToExp2(-0.3) = -0.25);
|
|
|
|
*/
|
2014-11-12 17:23:26 +00:00
|
|
|
|
2017-09-16 22:12:24 +00:00
|
|
|
template <typename T>
|
|
|
|
struct RoundToExp2Impl
|
2017-03-09 03:34:09 +00:00
|
|
|
{
|
2017-09-16 22:12:24 +00:00
|
|
|
using ResultType = T;
|
2014-11-12 17:23:26 +00:00
|
|
|
|
2017-09-16 22:12:24 +00:00
|
|
|
static inline T apply(T x)
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2017-09-16 22:12:24 +00:00
|
|
|
return roundDownToPowerOfTwo<T>(x);
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
2018-04-29 22:43:02 +00:00
|
|
|
|
|
|
|
#if USE_EMBEDDED_COMPILER
|
|
|
|
static constexpr bool compilable = false;
|
|
|
|
#endif
|
2017-03-09 03:34:09 +00:00
|
|
|
};
|
2014-11-12 17:23:26 +00:00
|
|
|
|
2017-09-16 22:12:24 +00:00
|
|
|
|
2017-09-15 12:16:12 +00:00
|
|
|
template <typename A>
|
2017-03-09 03:34:09 +00:00
|
|
|
struct RoundDurationImpl
|
|
|
|
{
|
2017-04-01 07:20:54 +00:00
|
|
|
using ResultType = UInt16;
|
|
|
|
|
|
|
|
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))))))))))))));
|
|
|
|
}
|
2018-04-29 22:43:02 +00:00
|
|
|
|
|
|
|
#if USE_EMBEDDED_COMPILER
|
|
|
|
static constexpr bool compilable = false;
|
|
|
|
#endif
|
2017-03-09 03:34:09 +00:00
|
|
|
};
|
2014-11-12 17:23:26 +00:00
|
|
|
|
2017-09-15 12:16:12 +00:00
|
|
|
template <typename A>
|
2017-03-09 03:34:09 +00:00
|
|
|
struct RoundAgeImpl
|
|
|
|
{
|
2017-04-01 07:20:54 +00:00
|
|
|
using ResultType = UInt8;
|
|
|
|
|
|
|
|
static inline ResultType apply(A x)
|
|
|
|
{
|
|
|
|
return x < 1 ? 0
|
|
|
|
: (x < 18 ? 17
|
|
|
|
: (x < 25 ? 18
|
|
|
|
: (x < 35 ? 25
|
|
|
|
: (x < 45 ? 35
|
2017-05-15 18:26:46 +00:00
|
|
|
: (x < 55 ? 45
|
|
|
|
: 55)))));
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
2018-04-29 22:43:02 +00:00
|
|
|
|
|
|
|
#if USE_EMBEDDED_COMPILER
|
|
|
|
static constexpr bool compilable = false;
|
|
|
|
#endif
|
2017-03-09 03:34:09 +00:00
|
|
|
};
|
2015-05-27 22:59:46 +00:00
|
|
|
|
2017-03-09 03:34:09 +00:00
|
|
|
|
2017-05-27 15:45:25 +00:00
|
|
|
/** This parameter controls the behavior of the rounding functions.
|
2017-09-16 16:38:27 +00:00
|
|
|
*/
|
|
|
|
enum class ScaleMode
|
2017-03-09 03:34:09 +00:00
|
|
|
{
|
2017-09-16 16:38:27 +00:00
|
|
|
Positive, // round to a number with N decimal places after the decimal point
|
|
|
|
Negative, // round to an integer with N zero characters
|
|
|
|
Zero, // round to an integer
|
2017-03-09 03:34:09 +00:00
|
|
|
};
|
2015-05-19 13:23:13 +00:00
|
|
|
|
2017-09-16 16:38:27 +00:00
|
|
|
enum class RoundingMode
|
2017-03-09 03:34:09 +00:00
|
|
|
{
|
2017-09-16 16:38:27 +00:00
|
|
|
#if __SSE4_1__
|
|
|
|
Round = _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC,
|
|
|
|
Floor = _MM_FROUND_TO_NEG_INF | _MM_FROUND_NO_EXC,
|
|
|
|
Ceil = _MM_FROUND_TO_POS_INF | _MM_FROUND_NO_EXC,
|
|
|
|
Trunc = _MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC,
|
|
|
|
#else
|
|
|
|
Round = 8, /// Values are correspond to above just in case.
|
|
|
|
Floor = 9,
|
|
|
|
Ceil = 10,
|
|
|
|
Trunc = 11,
|
|
|
|
#endif
|
2017-03-09 03:34:09 +00:00
|
|
|
};
|
2015-05-27 22:59:46 +00:00
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
|
2017-09-16 19:31:20 +00:00
|
|
|
/** Rounding functions for integer values.
|
2017-09-16 16:38:27 +00:00
|
|
|
*/
|
|
|
|
template <typename T, RoundingMode rounding_mode, ScaleMode scale_mode>
|
|
|
|
struct IntegerRoundingComputation
|
2017-03-09 03:34:09 +00:00
|
|
|
{
|
2017-09-16 18:36:16 +00:00
|
|
|
static const size_t data_count = 1;
|
|
|
|
|
|
|
|
static size_t prepare(size_t scale)
|
|
|
|
{
|
|
|
|
return scale;
|
|
|
|
}
|
|
|
|
|
2017-09-16 19:31:20 +00:00
|
|
|
static ALWAYS_INLINE T computeImpl(T x, T scale)
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2017-09-16 16:38:27 +00:00
|
|
|
switch (rounding_mode)
|
|
|
|
{
|
|
|
|
case RoundingMode::Trunc:
|
2017-09-16 19:31:20 +00:00
|
|
|
{
|
2017-09-16 16:38:27 +00:00
|
|
|
return x / scale * scale;
|
2017-09-16 19:31:20 +00:00
|
|
|
}
|
2017-09-16 16:38:27 +00:00
|
|
|
case RoundingMode::Floor:
|
2017-09-16 19:31:20 +00:00
|
|
|
{
|
2017-09-16 16:38:27 +00:00
|
|
|
if (x < 0)
|
|
|
|
x -= scale - 1;
|
|
|
|
return x / scale * scale;
|
2017-09-16 19:31:20 +00:00
|
|
|
}
|
2017-09-16 16:38:27 +00:00
|
|
|
case RoundingMode::Ceil:
|
2017-09-16 19:31:20 +00:00
|
|
|
{
|
2017-09-16 16:38:27 +00:00
|
|
|
if (x >= 0)
|
|
|
|
x += scale - 1;
|
|
|
|
return x / scale * scale;
|
2017-09-16 19:31:20 +00:00
|
|
|
}
|
2017-09-16 16:38:27 +00:00
|
|
|
case RoundingMode::Round:
|
2017-09-16 19:31:20 +00:00
|
|
|
{
|
|
|
|
bool negative = x < 0;
|
|
|
|
if (negative)
|
|
|
|
x = -x;
|
|
|
|
x = (x + scale / 2) / scale * scale;
|
|
|
|
if (negative)
|
|
|
|
x = -x;
|
|
|
|
return x;
|
|
|
|
}
|
2018-06-07 14:42:38 +00:00
|
|
|
default:
|
|
|
|
__builtin_unreachable();
|
2017-09-16 16:38:27 +00:00
|
|
|
}
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
|
|
|
|
2017-09-16 19:31:20 +00:00
|
|
|
static ALWAYS_INLINE T compute(T x, T scale)
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2017-09-16 16:38:27 +00:00
|
|
|
switch (scale_mode)
|
|
|
|
{
|
|
|
|
case ScaleMode::Zero:
|
|
|
|
return x;
|
|
|
|
case ScaleMode::Positive:
|
|
|
|
return x;
|
|
|
|
case ScaleMode::Negative:
|
2017-09-16 19:31:20 +00:00
|
|
|
return computeImpl(x, scale);
|
2018-06-07 14:42:38 +00:00
|
|
|
default:
|
|
|
|
__builtin_unreachable();
|
2017-09-16 16:38:27 +00:00
|
|
|
}
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
2017-09-16 18:36:16 +00:00
|
|
|
|
2017-09-16 19:31:20 +00:00
|
|
|
static ALWAYS_INLINE void compute(const T * __restrict in, size_t scale, T * __restrict out)
|
2017-09-16 18:36:16 +00:00
|
|
|
{
|
|
|
|
*out = compute(*in, scale);
|
|
|
|
}
|
|
|
|
|
2017-03-09 03:34:09 +00:00
|
|
|
};
|
2015-05-27 22:59:46 +00:00
|
|
|
|
2015-05-25 13:35:04 +00:00
|
|
|
|
2017-01-27 19:55:33 +00:00
|
|
|
#if __SSE4_1__
|
2017-09-16 18:36:16 +00:00
|
|
|
|
2017-03-09 03:34:09 +00:00
|
|
|
template <typename T>
|
|
|
|
class BaseFloatRoundingComputation;
|
|
|
|
|
|
|
|
template <>
|
|
|
|
class BaseFloatRoundingComputation<Float32>
|
|
|
|
{
|
|
|
|
public:
|
2017-09-16 18:36:16 +00:00
|
|
|
using ScalarType = Float32;
|
|
|
|
using VectorType = __m128;
|
2017-04-01 07:20:54 +00:00
|
|
|
static const size_t data_count = 4;
|
2015-05-25 13:35:04 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
static VectorType load(const ScalarType * in) { return _mm_loadu_ps(in); }
|
|
|
|
static VectorType load1(const ScalarType in) { return _mm_load1_ps(&in); }
|
|
|
|
static void store(ScalarType * out, VectorType val) { _mm_storeu_ps(out, val);}
|
|
|
|
static VectorType multiply(VectorType val, VectorType scale) { return _mm_mul_ps(val, scale); }
|
|
|
|
static VectorType divide(VectorType val, VectorType scale) { return _mm_div_ps(val, scale); }
|
|
|
|
template <RoundingMode mode> static VectorType apply(VectorType val) { return _mm_round_ps(val, int(mode)); }
|
2017-04-01 07:20:54 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
static VectorType prepare(size_t scale)
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2017-09-16 18:36:16 +00:00
|
|
|
return load1(scale);
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
2017-03-09 03:34:09 +00:00
|
|
|
};
|
2015-06-03 17:01:30 +00:00
|
|
|
|
2017-03-09 03:34:09 +00:00
|
|
|
template <>
|
|
|
|
class BaseFloatRoundingComputation<Float64>
|
|
|
|
{
|
|
|
|
public:
|
2017-09-16 18:36:16 +00:00
|
|
|
using ScalarType = Float64;
|
|
|
|
using VectorType = __m128d;
|
2017-04-01 07:20:54 +00:00
|
|
|
static const size_t data_count = 2;
|
2015-05-19 13:23:13 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
static VectorType load(const ScalarType * in) { return _mm_loadu_pd(in); }
|
|
|
|
static VectorType load1(const ScalarType in) { return _mm_load1_pd(&in); }
|
|
|
|
static void store(ScalarType * out, VectorType val) { _mm_storeu_pd(out, val);}
|
|
|
|
static VectorType multiply(VectorType val, VectorType scale) { return _mm_mul_pd(val, scale); }
|
|
|
|
static VectorType divide(VectorType val, VectorType scale) { return _mm_div_pd(val, scale); }
|
|
|
|
template <RoundingMode mode> static VectorType apply(VectorType val) { return _mm_round_pd(val, int(mode)); }
|
2015-05-25 13:35:04 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
static VectorType prepare(size_t scale)
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2017-09-16 18:36:16 +00:00
|
|
|
return load1(scale);
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
2017-03-09 03:34:09 +00:00
|
|
|
};
|
2015-05-25 13:35:04 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
#else
|
2017-04-01 07:20:54 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
/// Implementation for ARM. Not vectorized.
|
2015-05-25 13:35:04 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
inline float roundWithMode(float x, RoundingMode mode)
|
2017-03-09 03:34:09 +00:00
|
|
|
{
|
2017-09-16 18:36:16 +00:00
|
|
|
switch (mode)
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2017-09-16 18:36:16 +00:00
|
|
|
case RoundingMode::Round: return roundf(x);
|
|
|
|
case RoundingMode::Floor: return floorf(x);
|
|
|
|
case RoundingMode::Ceil: return ceilf(x);
|
|
|
|
case RoundingMode::Trunc: return truncf(x);
|
2017-09-24 13:59:18 +00:00
|
|
|
default:
|
|
|
|
throw Exception("Logical error: unexpected 'mode' parameter passed to function roundWithMode", ErrorCodes::LOGICAL_ERROR);
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
2017-09-16 18:36:16 +00:00
|
|
|
}
|
2017-04-01 07:20:54 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
inline double roundWithMode(double x, RoundingMode mode)
|
2017-03-09 03:34:09 +00:00
|
|
|
{
|
2017-09-16 18:36:16 +00:00
|
|
|
switch (mode)
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2017-09-16 18:36:16 +00:00
|
|
|
case RoundingMode::Round: return round(x);
|
|
|
|
case RoundingMode::Floor: return floor(x);
|
|
|
|
case RoundingMode::Ceil: return ceil(x);
|
|
|
|
case RoundingMode::Trunc: return trunc(x);
|
2017-09-24 13:59:18 +00:00
|
|
|
default:
|
|
|
|
throw Exception("Logical error: unexpected 'mode' parameter passed to function roundWithMode", ErrorCodes::LOGICAL_ERROR);
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
2017-09-16 18:36:16 +00:00
|
|
|
}
|
2017-03-09 03:34:09 +00:00
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
class BaseFloatRoundingComputation
|
|
|
|
{
|
|
|
|
public:
|
2017-09-16 18:36:16 +00:00
|
|
|
using ScalarType = T;
|
|
|
|
using VectorType = T;
|
2017-04-01 07:20:54 +00:00
|
|
|
static const size_t data_count = 1;
|
2017-03-09 03:34:09 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
static VectorType load(const ScalarType * in) { return *in; }
|
|
|
|
static VectorType load1(const ScalarType in) { return in; }
|
|
|
|
static VectorType store(ScalarType * out, ScalarType val) { return *out = val;}
|
|
|
|
static VectorType multiply(VectorType val, VectorType scale) { return val * scale; }
|
|
|
|
static VectorType divide(VectorType val, VectorType scale) { return val / scale; }
|
|
|
|
template <RoundingMode mode> static VectorType apply(VectorType val) { return roundWithMode(val, mode); }
|
2016-01-16 00:45:19 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
static VectorType prepare(size_t scale)
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2017-09-16 18:36:16 +00:00
|
|
|
return load1(scale);
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
2017-03-09 03:34:09 +00:00
|
|
|
};
|
2016-01-16 00:45:19 +00:00
|
|
|
|
2017-03-09 03:34:09 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
/** Implementation of low-level round-off functions for floating-point values.
|
2017-09-16 16:38:27 +00:00
|
|
|
*/
|
|
|
|
template <typename T, RoundingMode rounding_mode, ScaleMode scale_mode>
|
2017-09-16 18:36:16 +00:00
|
|
|
class FloatRoundingComputation : public BaseFloatRoundingComputation<T>
|
2017-03-09 03:34:09 +00:00
|
|
|
{
|
2017-09-16 18:36:16 +00:00
|
|
|
using Base = BaseFloatRoundingComputation<T>;
|
2016-01-16 00:45:19 +00:00
|
|
|
|
2017-03-09 03:34:09 +00:00
|
|
|
public:
|
2017-09-16 18:36:16 +00:00
|
|
|
static inline void compute(const T * __restrict in, const typename Base::VectorType & scale, T * __restrict out)
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2017-09-16 18:36:16 +00:00
|
|
|
auto val = Base::load(in);
|
2017-04-01 07:20:54 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
if (scale_mode == ScaleMode::Positive)
|
|
|
|
val = Base::multiply(val, scale);
|
|
|
|
else if (scale_mode == ScaleMode::Negative)
|
|
|
|
val = Base::divide(val, scale);
|
2017-04-01 07:20:54 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
val = Base::template apply<rounding_mode>(val);
|
|
|
|
|
|
|
|
if (scale_mode == ScaleMode::Positive)
|
|
|
|
val = Base::divide(val, scale);
|
|
|
|
else if (scale_mode == ScaleMode::Negative)
|
|
|
|
val = Base::multiply(val, scale);
|
|
|
|
|
|
|
|
Base::store(out, val);
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
2017-03-09 03:34:09 +00:00
|
|
|
};
|
2015-05-19 13:23:13 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
|
|
|
|
/** Implementing high-level rounding functions.
|
2017-09-16 16:38:27 +00:00
|
|
|
*/
|
|
|
|
template <typename T, RoundingMode rounding_mode, ScaleMode scale_mode>
|
2017-09-16 20:17:19 +00:00
|
|
|
struct FloatRoundingImpl
|
2017-03-09 03:34:09 +00:00
|
|
|
{
|
|
|
|
private:
|
2017-09-16 20:17:19 +00:00
|
|
|
using Op = FloatRoundingComputation<T, rounding_mode, scale_mode>;
|
2017-04-01 07:20:54 +00:00
|
|
|
using Data = std::array<T, Op::data_count>;
|
2015-05-19 13:23:13 +00:00
|
|
|
|
2017-03-09 03:34:09 +00:00
|
|
|
public:
|
2017-12-15 21:32:25 +00:00
|
|
|
static NO_INLINE void apply(const PaddedPODArray<T> & in, size_t scale, typename ColumnVector<T>::Container & out)
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2017-09-16 18:36:16 +00:00
|
|
|
auto mm_scale = Op::prepare(scale);
|
2017-04-01 07:20:54 +00:00
|
|
|
|
|
|
|
const size_t data_count = std::tuple_size<Data>();
|
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
const T* end_in = in.data() + in.size();
|
|
|
|
const T* limit = in.data() + in.size() / data_count * data_count;
|
2017-04-01 07:20:54 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
const T* __restrict p_in = in.data();
|
|
|
|
T* __restrict p_out = out.data();
|
2017-04-01 07:20:54 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
while (p_in < limit)
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
|
|
|
Op::compute(p_in, mm_scale, p_out);
|
2017-09-16 18:36:16 +00:00
|
|
|
p_in += data_count;
|
2017-04-01 07:20:54 +00:00
|
|
|
p_out += data_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p_in < end_in)
|
|
|
|
{
|
2017-09-16 18:36:16 +00:00
|
|
|
Data tmp_src{{}};
|
|
|
|
Data tmp_dst;
|
2017-04-01 07:20:54 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
size_t tail_size_bytes = (end_in - p_in) * sizeof(*p_in);
|
2017-04-01 07:20:54 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
memcpy(&tmp_src, p_in, tail_size_bytes);
|
|
|
|
Op::compute(reinterpret_cast<T *>(&tmp_src), mm_scale, reinterpret_cast<T *>(&tmp_dst));
|
|
|
|
memcpy(p_out, &tmp_dst, tail_size_bytes);
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-09 03:34:09 +00:00
|
|
|
};
|
2015-05-14 12:08:27 +00:00
|
|
|
|
2017-09-16 20:17:19 +00:00
|
|
|
template <typename T, RoundingMode rounding_mode, ScaleMode scale_mode>
|
|
|
|
struct IntegerRoundingImpl
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
using Op = IntegerRoundingComputation<T, rounding_mode, scale_mode>;
|
|
|
|
|
|
|
|
public:
|
|
|
|
template <size_t scale>
|
2017-12-15 21:32:25 +00:00
|
|
|
static NO_INLINE void applyImpl(const PaddedPODArray<T> & in, typename ColumnVector<T>::Container & out)
|
2017-09-16 20:17:19 +00:00
|
|
|
{
|
|
|
|
const T* end_in = in.data() + in.size();
|
|
|
|
|
|
|
|
const T* __restrict p_in = in.data();
|
|
|
|
T* __restrict p_out = out.data();
|
|
|
|
|
|
|
|
while (p_in < end_in)
|
|
|
|
{
|
|
|
|
Op::compute(p_in, scale, p_out);
|
|
|
|
++p_in;
|
|
|
|
++p_out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-15 21:32:25 +00:00
|
|
|
static NO_INLINE void apply(const PaddedPODArray<T> & in, size_t scale, typename ColumnVector<T>::Container & out)
|
2017-09-16 20:17:19 +00:00
|
|
|
{
|
|
|
|
/// Manual function cloning for compiler to generate integer division by constant.
|
|
|
|
switch (scale)
|
|
|
|
{
|
|
|
|
case 1ULL: return applyImpl<1ULL>(in, out);
|
|
|
|
case 10ULL: return applyImpl<10ULL>(in, out);
|
|
|
|
case 100ULL: return applyImpl<100ULL>(in, out);
|
|
|
|
case 1000ULL: return applyImpl<1000ULL>(in, out);
|
|
|
|
case 10000ULL: return applyImpl<10000ULL>(in, out);
|
|
|
|
case 100000ULL: return applyImpl<100000ULL>(in, out);
|
|
|
|
case 1000000ULL: return applyImpl<1000000ULL>(in, out);
|
|
|
|
case 10000000ULL: return applyImpl<10000000ULL>(in, out);
|
|
|
|
case 100000000ULL: return applyImpl<100000000ULL>(in, out);
|
|
|
|
case 1000000000ULL: return applyImpl<1000000000ULL>(in, out);
|
|
|
|
case 10000000000ULL: return applyImpl<10000000000ULL>(in, out);
|
|
|
|
case 100000000000ULL: return applyImpl<100000000000ULL>(in, out);
|
|
|
|
case 1000000000000ULL: return applyImpl<1000000000000ULL>(in, out);
|
|
|
|
case 10000000000000ULL: return applyImpl<10000000000000ULL>(in, out);
|
|
|
|
case 100000000000000ULL: return applyImpl<100000000000000ULL>(in, out);
|
|
|
|
case 1000000000000000ULL: return applyImpl<1000000000000000ULL>(in, out);
|
|
|
|
case 10000000000000000ULL: return applyImpl<10000000000000000ULL>(in, out);
|
|
|
|
case 100000000000000000ULL: return applyImpl<100000000000000000ULL>(in, out);
|
|
|
|
case 1000000000000000000ULL: return applyImpl<1000000000000000000ULL>(in, out);
|
|
|
|
case 10000000000000000000ULL: return applyImpl<10000000000000000000ULL>(in, out);
|
|
|
|
default:
|
|
|
|
throw Exception("Logical error: unexpected 'scale' parameter passed to function IntegerRoundingComputation::compute",
|
|
|
|
ErrorCodes::LOGICAL_ERROR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-03-09 03:34:09 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
template <typename T, RoundingMode rounding_mode>
|
2018-09-10 13:52:18 +00:00
|
|
|
class DecimalRounding
|
2017-03-09 03:34:09 +00:00
|
|
|
{
|
2018-09-10 14:11:30 +00:00
|
|
|
using NativeType = typename T::NativeType;
|
2018-09-10 13:52:18 +00:00
|
|
|
using Op = IntegerRoundingComputation<NativeType, rounding_mode, ScaleMode::Negative>;
|
|
|
|
using Container = typename ColumnDecimal<T>::Container;
|
2017-04-01 07:20:54 +00:00
|
|
|
|
2018-09-10 13:52:18 +00:00
|
|
|
public:
|
|
|
|
static NO_INLINE void apply(const Container & in, Container & out, Int64 scale_arg)
|
|
|
|
{
|
|
|
|
scale_arg = in.getScale() - scale_arg;
|
|
|
|
if (scale_arg > 0)
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2018-09-10 14:11:30 +00:00
|
|
|
size_t scale = intExp10(scale_arg);
|
2017-04-01 07:20:54 +00:00
|
|
|
|
2018-09-10 13:52:18 +00:00
|
|
|
const NativeType * __restrict p_in = reinterpret_cast<const NativeType *>(in.data());
|
|
|
|
const NativeType * end_in = reinterpret_cast<const NativeType *>(in.data()) + in.size();
|
|
|
|
NativeType * __restrict p_out = reinterpret_cast<NativeType *>(out.data());
|
2017-09-17 17:58:30 +00:00
|
|
|
|
2018-09-10 13:52:18 +00:00
|
|
|
while (p_in < end_in)
|
|
|
|
{
|
|
|
|
Op::compute(p_in, scale, p_out);
|
|
|
|
++p_in;
|
|
|
|
++p_out;
|
|
|
|
}
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
2018-09-10 13:52:18 +00:00
|
|
|
else
|
|
|
|
memcpy(out.data(), in.data(), in.size() * sizeof(T));
|
|
|
|
}
|
|
|
|
};
|
2015-05-25 13:35:04 +00:00
|
|
|
|
2018-09-10 13:52:18 +00:00
|
|
|
|
|
|
|
/** Select the appropriate processing algorithm depending on the scale.
|
|
|
|
*/
|
|
|
|
template <typename T, RoundingMode rounding_mode>
|
|
|
|
class Dispatcher
|
|
|
|
{
|
|
|
|
template <ScaleMode scale_mode>
|
|
|
|
using FunctionRoundingImpl = std::conditional_t<std::is_floating_point_v<T>,
|
|
|
|
FloatRoundingImpl<T, rounding_mode, scale_mode>,
|
|
|
|
IntegerRoundingImpl<T, rounding_mode, scale_mode>>;
|
|
|
|
|
|
|
|
static void apply(Block & block, const ColumnVector<T> * col, Int64 scale_arg, size_t result)
|
|
|
|
{
|
2017-12-14 01:43:19 +00:00
|
|
|
auto col_res = ColumnVector<T>::create();
|
2015-05-25 13:35:04 +00:00
|
|
|
|
2017-12-15 21:32:25 +00:00
|
|
|
typename ColumnVector<T>::Container & vec_res = col_res->getData();
|
2017-04-01 07:20:54 +00:00
|
|
|
vec_res.resize(col->getData().size());
|
2015-05-25 13:35:04 +00:00
|
|
|
|
2018-09-10 13:52:18 +00:00
|
|
|
if (!vec_res.empty())
|
2017-12-16 05:21:04 +00:00
|
|
|
{
|
2018-09-10 13:52:18 +00:00
|
|
|
if (scale_arg == 0)
|
|
|
|
{
|
|
|
|
size_t scale = 1;
|
|
|
|
FunctionRoundingImpl<ScaleMode::Zero>::apply(col->getData(), scale, vec_res);
|
|
|
|
}
|
|
|
|
else if (scale_arg > 0)
|
|
|
|
{
|
2018-09-10 14:13:03 +00:00
|
|
|
size_t scale = intExp10(scale_arg);
|
2018-09-10 13:52:18 +00:00
|
|
|
FunctionRoundingImpl<ScaleMode::Positive>::apply(col->getData(), scale, vec_res);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-09-10 14:13:03 +00:00
|
|
|
size_t scale = intExp10(-scale_arg);
|
2018-09-10 13:52:18 +00:00
|
|
|
FunctionRoundingImpl<ScaleMode::Negative>::apply(col->getData(), scale, vec_res);
|
|
|
|
}
|
2017-12-16 05:21:04 +00:00
|
|
|
}
|
2017-03-09 03:34:09 +00:00
|
|
|
|
2018-09-10 13:52:18 +00:00
|
|
|
block.getByPosition(result).column = std::move(col_res);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void apply(Block & block, const ColumnDecimal<T> * col, Int64 scale_arg, size_t result)
|
|
|
|
{
|
|
|
|
const typename ColumnDecimal<T>::Container & vec_src = col->getData();
|
|
|
|
|
|
|
|
auto col_res = ColumnDecimal<T>::create(vec_src.size(), vec_src.getScale());
|
|
|
|
auto & vec_res = col_res->getData();
|
|
|
|
|
|
|
|
if (!vec_res.empty())
|
|
|
|
DecimalRounding<T, rounding_mode>::apply(col->getData(), vec_res, scale_arg);
|
2017-12-16 05:21:04 +00:00
|
|
|
|
|
|
|
block.getByPosition(result).column = std::move(col_res);
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
2018-09-10 13:52:18 +00:00
|
|
|
|
|
|
|
public:
|
|
|
|
static void apply(Block & block, const IColumn * column, Int64 scale_arg, size_t result)
|
|
|
|
{
|
|
|
|
if constexpr (IsNumber<T>)
|
|
|
|
apply(block, checkAndGetColumn<ColumnVector<T>>(column), scale_arg, result);
|
|
|
|
else if constexpr (IsDecimalNumber<T>)
|
|
|
|
apply(block, checkAndGetColumn<ColumnDecimal<T>>(column), scale_arg, result);
|
|
|
|
}
|
2017-03-09 03:34:09 +00:00
|
|
|
};
|
|
|
|
|
2017-05-27 15:45:25 +00:00
|
|
|
/** A template for functions that round the value of an input parameter of type
|
2018-09-10 13:52:18 +00:00
|
|
|
* (U)Int8/16/32/64, Float32/64 or Decimal32/64/128, and accept an additional optional parameter (default is 0).
|
2017-09-16 18:36:16 +00:00
|
|
|
*/
|
2017-09-16 16:38:27 +00:00
|
|
|
template <typename Name, RoundingMode rounding_mode>
|
2017-03-09 03:34:09 +00:00
|
|
|
class FunctionRounding : public IFunction
|
|
|
|
{
|
|
|
|
public:
|
2017-04-01 07:20:54 +00:00
|
|
|
static constexpr auto name = Name::name;
|
2017-12-02 02:47:12 +00:00
|
|
|
static FunctionPtr create(const Context &) { return std::make_shared<FunctionRounding>(); }
|
2017-03-09 03:34:09 +00:00
|
|
|
|
|
|
|
public:
|
2017-04-01 07:20:54 +00:00
|
|
|
String getName() const override
|
|
|
|
{
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isVariadic() const override { return true; }
|
|
|
|
size_t getNumberOfArguments() const override { return 0; }
|
|
|
|
|
2017-05-27 15:45:25 +00:00
|
|
|
/// Get result types by argument types. If the function does not apply to these arguments, throw an exception.
|
2017-04-01 07:20:54 +00:00
|
|
|
DataTypePtr getReturnTypeImpl(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);
|
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
for (const auto & type : arguments)
|
2018-09-10 13:52:18 +00:00
|
|
|
if (!isNumber(type) && !isDecimal(type))
|
2017-09-16 18:36:16 +00:00
|
|
|
throw Exception("Illegal type " + arguments[0]->getName() + " of argument of function " + getName(),
|
|
|
|
ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
|
2017-04-01 07:20:54 +00:00
|
|
|
|
|
|
|
return arguments[0];
|
|
|
|
}
|
|
|
|
|
2018-09-10 13:52:18 +00:00
|
|
|
static Int64 getScaleArg(Block & block, const ColumnNumbers & arguments)
|
|
|
|
{
|
|
|
|
if (arguments.size() == 2)
|
|
|
|
{
|
|
|
|
const IColumn & scale_column = *block.getByPosition(arguments[1]).column;
|
|
|
|
if (!scale_column.isColumnConst())
|
|
|
|
throw Exception("Scale argument for rounding functions must be constant.", ErrorCodes::ILLEGAL_COLUMN);
|
|
|
|
|
|
|
|
Field scale_field = static_cast<const ColumnConst &>(scale_column).getField();
|
|
|
|
if (scale_field.getType() != Field::Types::UInt64
|
|
|
|
&& scale_field.getType() != Field::Types::Int64)
|
|
|
|
throw Exception("Scale argument for rounding functions must have integer type.", ErrorCodes::ILLEGAL_COLUMN);
|
|
|
|
|
|
|
|
return scale_field.get<Int64>();
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-07-23 08:40:43 +00:00
|
|
|
bool useDefaultImplementationForConstants() const override { return true; }
|
|
|
|
ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {1}; }
|
|
|
|
|
2018-04-24 07:16:39 +00:00
|
|
|
void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t /*input_rows_count*/) override
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2018-09-10 13:52:18 +00:00
|
|
|
const ColumnWithTypeAndName & column = block.getByPosition(arguments[0]);
|
|
|
|
Int64 scale_arg = getScaleArg(block, arguments);
|
|
|
|
|
|
|
|
auto call = [&](const auto & types) -> bool
|
|
|
|
{
|
|
|
|
using Types = std::decay_t<decltype(types)>;
|
|
|
|
using DataType = typename Types::LeftType;
|
|
|
|
|
|
|
|
if constexpr (IsDataTypeNumber<DataType> || IsDataTypeDecimal<DataType>)
|
|
|
|
{
|
|
|
|
using FieldType = typename DataType::FieldType;
|
|
|
|
Dispatcher<FieldType, rounding_mode>::apply(block, column.column.get(), scale_arg, result);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!callOnIndexAndDataType<void>(column.type->getTypeId(), call))
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
2018-09-10 13:52:18 +00:00
|
|
|
throw Exception("Illegal column " + column.name + " of argument of function " + getName(),
|
2017-04-01 07:20:54 +00:00
|
|
|
ErrorCodes::ILLEGAL_COLUMN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool hasInformationAboutMonotonicity() const override
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-12-02 02:47:12 +00:00
|
|
|
Monotonicity getMonotonicityForRange(const IDataType &, const Field &, const Field &) const override
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
PKCondition: infer index use with pk subexpression
By default only constraints explicitly matching
primary key expression (or expression wrapped in
a monotonic function) are eligible for part and
range selection. So for example, if index is:
(toStartOfHour(dt), UserID)
Then a query such as this resorts to full scan:
SELECT count() FROM t WHERE dt = now()
Intuitively, only parts with toStartOfHour(now())
could be selected, but it is less trivial to prove.
The primary key currently can be wrapped in a chain
of monotonic functions, so following would work:
toStartOfHour(dt) = toStartOfHour(now()) AND dt = now()
It must be however explicitly stated, if we wanted
to infer that we’d have to know the inverse function,
and prove that the inverse function is monotonic
on given interval. This is not practical as
there is no inverse function that for example undos
rounding, it isn’t strictly monotonic.
There are however functions that don’t transform
output range and preserve monotonicity on the
complete input range, such as rounding or casts
to a same or wider numeric type. This eliminates
the need to find inverse function, as no check for monotonicity over arbitrary interval is needed,
and thus makes this optimisation possible.
2017-07-06 05:39:05 +00:00
|
|
|
return { true, true, true };
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
2017-03-09 03:34:09 +00:00
|
|
|
};
|
2015-05-14 12:08:27 +00:00
|
|
|
|
2017-09-16 18:36:16 +00:00
|
|
|
|
2017-07-30 19:47:32 +00:00
|
|
|
struct NameRoundToExp2 { static constexpr auto name = "roundToExp2"; };
|
|
|
|
struct NameRoundDuration { static constexpr auto name = "roundDuration"; };
|
|
|
|
struct NameRoundAge { static constexpr auto name = "roundAge"; };
|
2017-09-16 16:38:27 +00:00
|
|
|
|
2017-07-30 19:47:32 +00:00
|
|
|
struct NameRound { static constexpr auto name = "round"; };
|
|
|
|
struct NameCeil { static constexpr auto name = "ceil"; };
|
|
|
|
struct NameFloor { static constexpr auto name = "floor"; };
|
2017-09-16 16:38:27 +00:00
|
|
|
struct NameTrunc { static constexpr auto name = "trunc"; };
|
2014-11-12 17:23:26 +00:00
|
|
|
|
2017-03-09 03:34:09 +00:00
|
|
|
using FunctionRoundToExp2 = FunctionUnaryArithmetic<RoundToExp2Impl, NameRoundToExp2, false>;
|
|
|
|
using FunctionRoundDuration = FunctionUnaryArithmetic<RoundDurationImpl, NameRoundDuration, false>;
|
|
|
|
using FunctionRoundAge = FunctionUnaryArithmetic<RoundAgeImpl, NameRoundAge, false>;
|
2016-01-16 00:45:19 +00:00
|
|
|
|
2017-09-16 16:38:27 +00:00
|
|
|
using FunctionRound = FunctionRounding<NameRound, RoundingMode::Round>;
|
|
|
|
using FunctionFloor = FunctionRounding<NameFloor, RoundingMode::Floor>;
|
|
|
|
using FunctionCeil = FunctionRounding<NameCeil, RoundingMode::Ceil>;
|
|
|
|
using FunctionTrunc = FunctionRounding<NameTrunc, RoundingMode::Trunc>;
|
2015-11-29 08:06:29 +00:00
|
|
|
|
|
|
|
|
2017-03-09 03:34:09 +00:00
|
|
|
struct PositiveMonotonicity
|
|
|
|
{
|
2017-04-01 07:20:54 +00:00
|
|
|
static bool has() { return true; }
|
2017-12-02 02:47:12 +00:00
|
|
|
static IFunction::Monotonicity get(const Field &, const Field &)
|
2017-04-01 07:20:54 +00:00
|
|
|
{
|
|
|
|
return { true };
|
|
|
|
}
|
2017-03-09 03:34:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
template <> struct FunctionUnaryArithmeticMonotonicity<NameRoundToExp2> : PositiveMonotonicity {};
|
|
|
|
template <> struct FunctionUnaryArithmeticMonotonicity<NameRoundDuration> : PositiveMonotonicity {};
|
|
|
|
template <> struct FunctionUnaryArithmeticMonotonicity<NameRoundAge> : PositiveMonotonicity {};
|
2015-11-29 08:06:29 +00:00
|
|
|
|
2012-12-24 08:16:25 +00:00
|
|
|
}
|