ClickHouse/src/Core/AccurateComparison.h

247 lines
6.0 KiB
C++
Raw Normal View History

#pragma once
#include <cmath>
#include <limits>
2021-10-02 07:13:14 +00:00
#include <base/DecomposedFloat.h>
2021-04-25 09:30:43 +00:00
#include <Core/Defines.h>
#include <Core/Types.h>
2021-10-02 07:13:14 +00:00
#include <base/extended_types.h>
#include <Common/NaNUtils.h>
/** Preceptually-correct number comparisons.
* Example: Int8(-1) != UInt8(255)
*/
namespace accurate
{
2021-01-27 00:54:57 +00:00
using namespace DB;
template <typename A, typename B>
2021-04-25 09:30:43 +00:00
bool lessOp(A a, B b)
{
2021-05-06 15:45:58 +00:00
if constexpr (std::is_same_v<A, B>)
return a < b;
2021-04-25 09:30:43 +00:00
/// float vs float
if constexpr (std::is_floating_point_v<A> && std::is_floating_point_v<B>)
return a < b;
2021-04-25 09:30:43 +00:00
/// anything vs NaN
2021-05-04 23:07:14 +00:00
if (isNaN(a) || isNaN(b))
return false;
2021-04-25 09:30:43 +00:00
/// int vs int
2021-09-10 11:49:22 +00:00
if constexpr (is_integer<A> && is_integer<B>)
2021-04-25 09:30:43 +00:00
{
/// same signedness
if constexpr (is_signed_v<A> == is_signed_v<B>)
return a < b;
2021-04-25 09:30:43 +00:00
/// different signedness
2021-04-25 09:30:43 +00:00
if constexpr (is_signed_v<A> && !is_signed_v<B>)
2021-05-04 23:07:14 +00:00
return a < 0 || static_cast<make_unsigned_t<A>>(a) < b;
2021-04-25 09:30:43 +00:00
if constexpr (!is_signed_v<A> && is_signed_v<B>)
2021-05-04 23:07:14 +00:00
return b >= 0 && a < static_cast<make_unsigned_t<B>>(b);
2021-04-25 09:30:43 +00:00
}
2021-04-25 09:30:43 +00:00
/// int vs float
2021-09-10 11:49:22 +00:00
if constexpr (is_integer<A> && std::is_floating_point_v<B>)
2021-04-25 09:30:43 +00:00
{
if constexpr (sizeof(A) <= 4)
return static_cast<double>(a) < static_cast<double>(b);
2021-04-25 09:30:43 +00:00
return DecomposedFloat<B>(b).greater(a);
}
2021-09-10 11:49:22 +00:00
if constexpr (std::is_floating_point_v<A> && is_integer<B>)
2021-04-25 09:30:43 +00:00
{
if constexpr (sizeof(B) <= 4)
return static_cast<double>(a) < static_cast<double>(b);
2021-04-25 09:30:43 +00:00
return DecomposedFloat<A>(a).less(b);
}
2021-05-02 22:42:01 +00:00
2021-09-10 11:49:22 +00:00
static_assert(is_integer<A> || std::is_floating_point_v<A>);
static_assert(is_integer<B> || std::is_floating_point_v<B>);
2021-05-02 22:42:01 +00:00
__builtin_unreachable();
}
template <typename A, typename B>
2021-04-25 09:30:43 +00:00
bool greaterOp(A a, B b)
{
2021-04-25 09:30:43 +00:00
return lessOp(b, a);
}
template <typename A, typename B>
2021-04-25 09:30:43 +00:00
bool greaterOrEqualsOp(A a, B b)
{
2021-05-05 22:02:00 +00:00
if (isNaN(a) || isNaN(b))
return false;
2021-04-25 09:30:43 +00:00
return !lessOp(a, b);
}
template <typename A, typename B>
2021-04-25 09:30:43 +00:00
bool lessOrEqualsOp(A a, B b)
{
2021-05-05 22:02:00 +00:00
if (isNaN(a) || isNaN(b))
return false;
2021-04-25 09:30:43 +00:00
return !lessOp(b, a);
}
2021-04-25 09:30:43 +00:00
template <typename A, typename B>
bool equalsOp(A a, B b)
{
2021-05-06 15:45:58 +00:00
if constexpr (std::is_same_v<A, B>)
return a == b;
2021-04-25 09:30:43 +00:00
/// float vs float
if constexpr (std::is_floating_point_v<A> && std::is_floating_point_v<B>)
return a == b;
2021-04-25 09:30:43 +00:00
/// anything vs NaN
2021-05-04 23:09:32 +00:00
if (isNaN(a) || isNaN(b))
return false;
2021-04-25 09:30:43 +00:00
/// int vs int
2021-09-10 11:49:22 +00:00
if constexpr (is_integer<A> && is_integer<B>)
2021-04-25 09:30:43 +00:00
{
/// same signedness
if constexpr (is_signed_v<A> == is_signed_v<B>)
return a == b;
2021-04-25 09:30:43 +00:00
/// different signedness
2021-04-25 09:30:43 +00:00
if constexpr (is_signed_v<A> && !is_signed_v<B>)
2021-05-04 23:08:42 +00:00
return a >= 0 && static_cast<make_unsigned_t<A>>(a) == b;
2021-04-25 09:30:43 +00:00
if constexpr (!is_signed_v<A> && is_signed_v<B>)
2021-05-04 23:08:42 +00:00
return b >= 0 && a == static_cast<make_unsigned_t<B>>(b);
2021-04-25 09:30:43 +00:00
}
2021-04-25 09:30:43 +00:00
/// int vs float
2021-09-10 11:49:22 +00:00
if constexpr (is_integer<A> && std::is_floating_point_v<B>)
2021-04-25 09:30:43 +00:00
{
if constexpr (sizeof(A) <= 4)
return static_cast<double>(a) == static_cast<double>(b);
2021-04-25 09:30:43 +00:00
return DecomposedFloat<B>(b).equals(a);
}
2021-09-10 11:49:22 +00:00
if constexpr (std::is_floating_point_v<A> && is_integer<B>)
2021-04-25 09:30:43 +00:00
{
if constexpr (sizeof(B) <= 4)
return static_cast<double>(a) == static_cast<double>(b);
2021-04-25 09:30:43 +00:00
return DecomposedFloat<A>(a).equals(b);
}
2021-05-02 22:42:01 +00:00
2021-05-06 15:45:58 +00:00
/// e.g comparing UUID with integer.
return false;
}
template <typename A, typename B>
2021-04-25 09:30:43 +00:00
bool notEqualsOp(A a, B b)
{
return !equalsOp(a, b);
}
/// Converts numeric to an equal numeric of other type.
template <typename From, typename To>
inline bool NO_SANITIZE_UNDEFINED convertNumeric(From value, To & result)
{
2019-05-13 23:44:55 +00:00
/// If the type is actually the same it's not necessary to do any checks.
if constexpr (std::is_same_v<From, To>)
{
result = value;
return true;
}
if constexpr (std::is_floating_point_v<From> && std::is_floating_point_v<To>)
{
/// 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<From>::infinity())
{
result = std::numeric_limits<To>::infinity();
return true;
}
if (value == -std::numeric_limits<From>::infinity())
{
result = -std::numeric_limits<To>::infinity();
return true;
}
}
2021-05-04 23:07:14 +00:00
if (greaterOp(value, std::numeric_limits<To>::max())
|| lessOp(value, std::numeric_limits<To>::lowest()))
{
return false;
}
2020-12-05 13:46:14 +00:00
result = static_cast<To>(value);
return equalsOp(value, result);
}
}
namespace DB
{
template <typename A, typename B> struct EqualsOp
{
/// An operation that gives the same result, if arguments are passed in reverse order.
using SymmetricOp = EqualsOp<B, A>;
static UInt8 apply(A a, B b) { return accurate::equalsOp(a, b); }
};
template <typename A, typename B> struct NotEqualsOp
{
using SymmetricOp = NotEqualsOp<B, A>;
static UInt8 apply(A a, B b) { return accurate::notEqualsOp(a, b); }
};
template <typename A, typename B> struct GreaterOp;
template <typename A, typename B> struct LessOp
{
using SymmetricOp = GreaterOp<B, A>;
static UInt8 apply(A a, B b) { return accurate::lessOp(a, b); }
};
template <typename A, typename B> struct GreaterOp
{
using SymmetricOp = LessOp<B, A>;
static UInt8 apply(A a, B b) { return accurate::greaterOp(a, b); }
};
template <typename A, typename B> struct GreaterOrEqualsOp;
template <typename A, typename B> struct LessOrEqualsOp
{
using SymmetricOp = GreaterOrEqualsOp<B, A>;
static UInt8 apply(A a, B b) { return accurate::lessOrEqualsOp(a, b); }
};
template <typename A, typename B> struct GreaterOrEqualsOp
{
using SymmetricOp = LessOrEqualsOp<B, A>;
static UInt8 apply(A a, B b) { return accurate::greaterOrEqualsOp(a, b); }
};
}