#pragma once #include #include #include "Defines.h" #include "Types.h" #include #include /** Preceptually-correct number comparisons. * Example: Int8(-1) != UInt8(255) */ namespace accurate { using DB::UInt64; /** Cases: 1) Safe conversion (in case of default C++ operators) a) int vs any int b) uint vs any uint c) float vs any float 2) int vs uint a) sizeof(int) <= sizeof(uint). Accurate comparison with MAX_INT tresholds b) sizeof(int) > sizeof(uint). Casting to int 3) integral_type vs floating_type a) sizeof(integral_type) <= 4. Comparison via casting arguments to Float64 b) sizeof(integral_type) == 8. Accurate comparison. Consider 3 sets of intervals: 1) interval between adjacent floats less or equal 1 2) interval between adjacent floats greater then 2 3) float is outside [MIN_INT64; MAX_INT64] */ // Case 1. Is pair of floats or pair of ints or pair of uints template constexpr bool is_safe_conversion = (std::is_floating_point_v && std::is_floating_point_v) || (std::is_integral_v && std::is_integral_v && !(std::is_signed_v ^ std::is_signed_v)) || (std::is_same_v && std::is_same_v) || (std::is_integral_v && std::is_same_v) || (std::is_same_v && std::is_integral_v); template using bool_if_safe_conversion = std::enable_if_t, bool>; template using bool_if_not_safe_conversion = std::enable_if_t, bool>; /// Case 2. Are params IntXX and UIntYY ? template constexpr bool is_any_int_vs_uint = std::is_integral_v && std::is_integral_v && std::is_signed_v && std::is_unsigned_v; // Case 2a. Are params IntXX and UIntYY and sizeof(IntXX) >= sizeof(UIntYY) (in such case will use accurate compare) template constexpr bool is_le_int_vs_uint = is_any_int_vs_uint && (sizeof(TInt) <= sizeof(TUInt)); template using bool_if_le_int_vs_uint_t = std::enable_if_t, bool>; template inline bool_if_le_int_vs_uint_t greaterOpTmpl(TInt a, TUInt b) { return static_cast(a) > b && a >= 0 && b <= static_cast(std::numeric_limits::max()); } template inline bool_if_le_int_vs_uint_t greaterOpTmpl(TUInt a, TInt b) { return a > static_cast(b) || b < 0 || a > static_cast(std::numeric_limits::max()); } template inline bool_if_le_int_vs_uint_t equalsOpTmpl(TInt a, TUInt b) { return static_cast(a) == b && a >= 0 && b <= static_cast(std::numeric_limits::max()); } template inline bool_if_le_int_vs_uint_t equalsOpTmpl(TUInt a, TInt b) { return a == static_cast(b) && b >= 0 && a <= static_cast(std::numeric_limits::max()); } // Case 2b. Are params IntXX and UIntYY and sizeof(IntXX) > sizeof(UIntYY) (in such case will cast UIntYY to IntXX and compare) template constexpr bool is_gt_int_vs_uint = is_any_int_vs_uint && (sizeof(TInt) > sizeof(TUInt)); template using bool_if_gt_int_vs_uint = std::enable_if_t, bool>; template inline bool_if_gt_int_vs_uint greaterOpTmpl(TInt a, TUInt b) { return static_cast(a) > static_cast(b); } template inline bool_if_gt_int_vs_uint greaterOpTmpl(TUInt a, TInt b) { return static_cast(a) > static_cast(b); } template inline bool_if_gt_int_vs_uint equalsOpTmpl(TInt a, TUInt b) { return static_cast(a) == static_cast(b); } template inline bool_if_gt_int_vs_uint equalsOpTmpl(TUInt a, TInt b) { return static_cast(a) == static_cast(b); } // Case 3a. Comparison via conversion to double. template using bool_if_double_can_be_used = std::enable_if_t< std::is_integral_v && (sizeof(TAInt) <= 4) && std::is_floating_point_v, bool>; template inline bool_if_double_can_be_used greaterOpTmpl(TAInt a, TAFloat b) { return static_cast(a) > static_cast(b); } template inline bool_if_double_can_be_used greaterOpTmpl(TAFloat a, TAInt b) { return static_cast(a) > static_cast(b); } template inline bool_if_double_can_be_used equalsOpTmpl(TAInt a, TAFloat b) { return static_cast(a) == static_cast(b); } template inline bool_if_double_can_be_used equalsOpTmpl(TAFloat a, TAInt b) { return static_cast(a) == static_cast(b); } /* Final realiztions */ template inline bool_if_not_safe_conversion greaterOp(A a, B b) { return greaterOpTmpl(a, b); } template inline bool_if_safe_conversion greaterOp(A a, B b) { return a > b; } // Case 3b. 64-bit integers vs floats comparison. // See hint at https://github.com/JuliaLang/julia/issues/257 (but it doesn't work properly for -2**63) constexpr DB::Int64 MAX_INT64_WITH_EXACT_FLOAT64_REPR = 9007199254740992LL; // 2^53 template <> inline bool greaterOp(DB::Float64 f, DB::Int64 i) { if (-MAX_INT64_WITH_EXACT_FLOAT64_REPR <= i && i <= MAX_INT64_WITH_EXACT_FLOAT64_REPR) return f > static_cast(i); return (f >= static_cast(std::numeric_limits::max())) // rhs is 2**63 (not 2^63 - 1) || (f > static_cast(std::numeric_limits::min()) && static_cast(f) > i); } template <> inline bool greaterOp(DB::Int64 i, DB::Float64 f) { if (-MAX_INT64_WITH_EXACT_FLOAT64_REPR <= i && i <= MAX_INT64_WITH_EXACT_FLOAT64_REPR) return f < static_cast(i); return (f < static_cast(std::numeric_limits::min())) || (f < static_cast(std::numeric_limits::max()) && i > static_cast(f)); } template <> inline bool greaterOp(DB::Float64 f, DB::UInt64 u) { if (u <= static_cast(MAX_INT64_WITH_EXACT_FLOAT64_REPR)) return f > static_cast(u); return (f >= static_cast(std::numeric_limits::max())) || (f >= 0 && static_cast(f) > u); } template <> inline bool greaterOp(DB::UInt64 u, DB::Float64 f) { if (u <= static_cast(MAX_INT64_WITH_EXACT_FLOAT64_REPR)) return static_cast(u) > f; return (f < 0) || (f < static_cast(std::numeric_limits::max()) && u > static_cast(f)); } // Case 3b for float32 template <> inline bool greaterOp(DB::Float32 f, DB::Int64 i) { return greaterOp(static_cast(f), i); } template <> inline bool greaterOp(DB::Int64 i, DB::Float32 f) { return greaterOp(i, static_cast(f)); } template <> inline bool greaterOp(DB::Float32 f, DB::UInt64 u) { return greaterOp(static_cast(f), u); } template <> inline bool greaterOp(DB::UInt64 u, DB::Float32 f) { return greaterOp(u, static_cast(f)); } template <> inline bool greaterOp(DB::Float64 f, DB::UInt128 u) { return u.low == 0 && greaterOp(f, u.high); } template <> inline bool greaterOp(DB::UInt128 u, DB::Float64 f) { return u.low != 0 || greaterOp(u.high, f); } template <> inline bool greaterOp(DB::Float32 f, DB::UInt128 u) { return greaterOp(static_cast(f), u); } template <> inline bool greaterOp(DB::UInt128 u, DB::Float32 f) { return greaterOp(u, static_cast(f)); } template inline bool_if_not_safe_conversion equalsOp(A a, B b) { return equalsOpTmpl(a, b); } template inline bool_if_safe_conversion equalsOp(A a, B b) { using LargestType = std::conditional_t= sizeof(B), A, B>; return static_cast(a) == static_cast(b); } template <> inline bool NO_SANITIZE_UNDEFINED equalsOp(DB::Float64 f, DB::UInt64 u) { return static_cast(f) == u && f == static_cast(u); } template <> inline bool NO_SANITIZE_UNDEFINED equalsOp(DB::UInt64 u, DB::Float64 f) { return u == static_cast(f) && static_cast(u) == f; } template <> inline bool NO_SANITIZE_UNDEFINED equalsOp(DB::Float64 f, DB::Int64 u) { return static_cast(f) == u && f == static_cast(u); } template <> inline bool NO_SANITIZE_UNDEFINED equalsOp(DB::Int64 u, DB::Float64 f) { return u == static_cast(f) && static_cast(u) == f; } template <> inline bool NO_SANITIZE_UNDEFINED equalsOp(DB::Float32 f, DB::UInt64 u) { return static_cast(f) == u && f == static_cast(u); } template <> inline bool NO_SANITIZE_UNDEFINED equalsOp(DB::UInt64 u, DB::Float32 f) { return u == static_cast(f) && static_cast(u) == f; } template <> inline bool NO_SANITIZE_UNDEFINED equalsOp(DB::Float32 f, DB::Int64 u) { return static_cast(f) == u && f == static_cast(u); } template <> inline bool NO_SANITIZE_UNDEFINED equalsOp(DB::Int64 u, DB::Float32 f) { return u == static_cast(f) && static_cast(u) == f; } template <> inline bool NO_SANITIZE_UNDEFINED equalsOp(DB::UInt128 u, DB::Float64 f) { return u.low == 0 && equalsOp(static_cast(u.high), f); } template <> inline bool equalsOp(DB::UInt128 u, DB::Float32 f) { return equalsOp(u, static_cast(f)); } template <> inline bool equalsOp(DB::Float64 f, DB::UInt128 u) { return equalsOp(u, f); } template <> inline bool equalsOp(DB::Float32 f, DB::UInt128 u) { return equalsOp(static_cast(f), u); } inline bool NO_SANITIZE_UNDEFINED greaterOp(DB::Int128 i, DB::Float64 f) { static constexpr __int128 min_int128 = __int128(0x8000000000000000ll) << 64; static constexpr __int128 max_int128 = (__int128(0x7fffffffffffffffll) << 64) + 0xffffffffffffffffll; if (-MAX_INT64_WITH_EXACT_FLOAT64_REPR <= i && i <= MAX_INT64_WITH_EXACT_FLOAT64_REPR) return static_cast(i) > f; return (f < static_cast(min_int128)) || (f < static_cast(max_int128) && i > static_cast(f)); } inline bool NO_SANITIZE_UNDEFINED greaterOp(DB::Float64 f, DB::Int128 i) { static constexpr __int128 min_int128 = __int128(0x8000000000000000ll) << 64; static constexpr __int128 max_int128 = (__int128(0x7fffffffffffffffll) << 64) + 0xffffffffffffffffll; if (-MAX_INT64_WITH_EXACT_FLOAT64_REPR <= i && i <= MAX_INT64_WITH_EXACT_FLOAT64_REPR) return f > static_cast(i); return (f >= static_cast(max_int128)) || (f > static_cast(min_int128) && static_cast(f) > i); } inline bool greaterOp(DB::Int128 i, DB::Float32 f) { return greaterOp(i, static_cast(f)); } inline bool greaterOp(DB::Float32 f, DB::Int128 i) { return greaterOp(static_cast(f), i); } inline bool NO_SANITIZE_UNDEFINED equalsOp(DB::Int128 i, DB::Float64 f) { return i == static_cast(f) && static_cast(i) == f; } inline bool NO_SANITIZE_UNDEFINED equalsOp(DB::Int128 i, DB::Float32 f) { return i == static_cast(f) && static_cast(i) == f; } inline bool equalsOp(DB::Float64 f, DB::Int128 i) { return equalsOp(i, f); } inline bool equalsOp(DB::Float32 f, DB::Int128 i) { return equalsOp(i, f); } template inline bool_if_not_safe_conversion notEqualsOp(A a, B b) { return !equalsOp(a, b); } template inline bool_if_safe_conversion notEqualsOp(A a, B b) { return a != b; } template inline bool_if_not_safe_conversion lessOp(A a, B b) { return greaterOp(b, a); } template inline bool_if_safe_conversion lessOp(A a, B b) { return a < b; } template inline bool_if_not_safe_conversion lessOrEqualsOp(A a, B b) { if (isNaN(a) || isNaN(b)) return false; return !greaterOp(a, b); } template inline bool_if_safe_conversion lessOrEqualsOp(A a, B b) { return a <= b; } template inline bool_if_not_safe_conversion greaterOrEqualsOp(A a, B b) { if (isNaN(a) || isNaN(b)) return false; return !greaterOp(b, a); } template inline bool_if_safe_conversion greaterOrEqualsOp(A a, B b) { return a >= b; } /// Converts numeric to an equal numeric of other type. template inline bool NO_SANITIZE_UNDEFINED convertNumeric(From value, To & result) { /// Note that NaNs doesn't compare equal to anything, but they are still in range of any Float type. if (isNaN(value) && std::is_floating_point_v) { result = value; return true; } result = static_cast(value); return equalsOp(value, result); } } namespace DB { template struct EqualsOp { /// An operation that gives the same result, if arguments are passed in reverse order. using SymmetricOp = EqualsOp; static UInt8 apply(A a, B b) { return accurate::equalsOp(a, b); } }; template struct NotEqualsOp { using SymmetricOp = NotEqualsOp; static UInt8 apply(A a, B b) { return accurate::notEqualsOp(a, b); } }; template struct GreaterOp; template struct LessOp { using SymmetricOp = GreaterOp; static UInt8 apply(A a, B b) { return accurate::lessOp(a, b); } }; template struct GreaterOp { using SymmetricOp = LessOp; static UInt8 apply(A a, B b) { return accurate::greaterOp(a, b); } }; template struct GreaterOrEqualsOp; template struct LessOrEqualsOp { using SymmetricOp = GreaterOrEqualsOp; static UInt8 apply(A a, B b) { return accurate::lessOrEqualsOp(a, b); } }; template struct GreaterOrEqualsOp { using SymmetricOp = LessOrEqualsOp; static UInt8 apply(A a, B b) { return accurate::greaterOrEqualsOp(a, b); } }; }