#pragma once #include /** Preceptually-correct number comparisons. * Example: Int8(-1) != UInt8(255) */ namespace accurate { /** 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 using is_safe_convervsion = std::integral_constant::value && std::is_floating_point::value) || (std::is_integral::value && std::is_integral::value && !(std::is_signed::value ^ std::is_signed::value))>; template using bool_if_safe_convervsion = std::enable_if_t::value, bool>; template using bool_if_not_safe_convervsion = std::enable_if_t::value, bool>; /// Case 2. Are params IntXX and UIntYY ? template using is_any_int_vs_uint = std::integral_constant::value && std::is_integral::value && std::is_signed::value && std::is_unsigned::value>; // Case 2a. Are params IntXX and UIntYY and sizeof(IntXX) >= sizeof(UIntYY) (in such case will use accurate compare) template using is_le_int_vs_uint_t = std::integral_constant::value && (sizeof(TInt) <= sizeof(TUInt))>; template using bool_if_le_int_vs_uint_t = std::enable_if_t::value, 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 using is_gt_int_vs_uint = std::integral_constant::value && (sizeof(TInt) > sizeof(TUInt))>; template using bool_if_gt_int_vs_uint = std::enable_if_t::value, 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::value && (sizeof(TAInt) <= 4) && std::is_floating_point::value, 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_convervsion greaterOp(A a, B b) { return greaterOpTmpl(a, b); } template inline bool_if_safe_convervsion 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_if_not_safe_convervsion equalsOp(A a, B b) { return equalsOpTmpl(a, b); } template inline bool_if_safe_convervsion equalsOp(A a, B b) { return a == b; } template<> inline bool equalsOp(DB::Float64 f, DB::UInt64 u) { return static_cast(f) == u && f == static_cast(u); } template<> inline bool equalsOp(DB::UInt64 u, DB::Float64 f) { return u == static_cast(f) && static_cast(u) == f; } template<> inline bool equalsOp(DB::Float64 f, DB::Int64 u) { return static_cast(f) == u && f == static_cast(u); } template<> inline bool equalsOp(DB::Int64 u, DB::Float64 f) { return u == static_cast(f) && static_cast(u) == f; } template<> inline bool equalsOp(DB::Float32 f, DB::UInt64 u) { return static_cast(f) == u && f == static_cast(u); } template<> inline bool equalsOp(DB::UInt64 u, DB::Float32 f) { return u == static_cast(f) && static_cast(u) == f; } template<> inline bool equalsOp(DB::Float32 f, DB::Int64 u) { return static_cast(f) == u && f == static_cast(u); } template<> inline bool equalsOp(DB::Int64 u, DB::Float32 f) { return u == static_cast(f) && static_cast(u) == f; } template inline bool_if_not_safe_convervsion notEqualsOp(A a, B b) { return !equalsOp(a, b); } template inline bool_if_safe_convervsion notEqualsOp(A a, B b) { return a != b; } template inline bool_if_not_safe_convervsion lessOp(A a, B b) { return greaterOp(b, a); } template inline bool_if_safe_convervsion lessOp(A a, B b) { return a < b; } template inline bool_if_not_safe_convervsion lessOrEqualsOp(A a, B b) { return !greaterOp(a, b); } template inline bool_if_safe_convervsion lessOrEqualsOp(A a, B b) { return a <= b; } template inline bool_if_not_safe_convervsion greaterOrEqualsOp(A a, B b) { return !greaterOp(b, a); } template inline bool_if_safe_convervsion greaterOrEqualsOp(A a, B b) { return a >= b; } }