#pragma once #include #include #include #include #include #include #include /** Preceptually-correct number comparisons. * Example: Int8(-1) != UInt8(255) */ namespace accurate { using namespace DB; template bool lessOp(A a, B b) { if constexpr (std::is_same_v) return a < b; /// float vs float if constexpr (std::is_floating_point_v && std::is_floating_point_v) return a < b; /// anything vs NaN if (isNaN(a) || isNaN(b)) return false; /// int vs int if constexpr (is_integer && is_integer) { /// same signedness if constexpr (is_signed_v == is_signed_v) return a < b; /// different signedness if constexpr (is_signed_v && !is_signed_v) return a < 0 || static_cast>(a) < b; if constexpr (!is_signed_v && is_signed_v) return b >= 0 && a < static_cast>(b); } /// int vs float if constexpr (is_integer && std::is_floating_point_v) { if constexpr (sizeof(A) <= 4) return static_cast(a) < static_cast(b); return DecomposedFloat(b).greater(a); } if constexpr (std::is_floating_point_v && is_integer) { if constexpr (sizeof(B) <= 4) return static_cast(a) < static_cast(b); return DecomposedFloat(a).less(b); } static_assert(is_integer || std::is_floating_point_v); static_assert(is_integer || std::is_floating_point_v); __builtin_unreachable(); } template bool greaterOp(A a, B b) { return lessOp(b, a); } template bool greaterOrEqualsOp(A a, B b) { if (isNaN(a) || isNaN(b)) return false; return !lessOp(a, b); } template bool lessOrEqualsOp(A a, B b) { if (isNaN(a) || isNaN(b)) return false; return !lessOp(b, a); } template bool equalsOp(A a, B b) { if constexpr (std::is_same_v) return a == b; /// float vs float if constexpr (std::is_floating_point_v && std::is_floating_point_v) return a == b; /// anything vs NaN if (isNaN(a) || isNaN(b)) return false; /// int vs int if constexpr (is_integer && is_integer) { /// same signedness if constexpr (is_signed_v == is_signed_v) return a == b; /// different signedness if constexpr (is_signed_v && !is_signed_v) return a >= 0 && static_cast>(a) == b; if constexpr (!is_signed_v && is_signed_v) return b >= 0 && a == static_cast>(b); } /// int vs float if constexpr (is_integer && std::is_floating_point_v) { if constexpr (sizeof(A) <= 4) return static_cast(a) == static_cast(b); return DecomposedFloat(b).equals(a); } if constexpr (std::is_floating_point_v && is_integer) { if constexpr (sizeof(B) <= 4) return static_cast(a) == static_cast(b); return DecomposedFloat(a).equals(b); } /// e.g comparing UUID with integer. return false; } template bool notEqualsOp(A a, B b) { return !equalsOp(a, b); } /// Converts numeric to an equal numeric of other type. /// When `strict` is `true` check that result exactly same as input, otherwise just check overflow template inline bool NO_SANITIZE_UNDEFINED convertNumeric(From value, To & result) { /// If the type is actually the same it's not necessary to do any checks. if constexpr (std::is_same_v) { result = value; return true; } if constexpr (std::is_floating_point_v && std::is_floating_point_v) { /// Note that NaNs doesn't compare equal to anything, but they are still in range of any Float type. if (isNaN(value)) { result = value; return true; } if (value == std::numeric_limits::infinity()) { result = std::numeric_limits::infinity(); return true; } if (value == -std::numeric_limits::infinity()) { result = -std::numeric_limits::infinity(); return true; } } if (greaterOp(value, std::numeric_limits::max()) || lessOp(value, std::numeric_limits::lowest())) { return false; } result = static_cast(value); if constexpr (strict) return equalsOp(value, result); return true; } } 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); } }; }