#pragma once #include #include "yql_wide_int.h" #include #include namespace NYql { namespace NDecimal { #ifdef _win_ #ifndef DONT_USE_NATIVE_INT128 #define DONT_USE_NATIVE_INT128 #endif #endif #ifdef DONT_USE_NATIVE_INT128 using TInt128 = TWide; using TUint128 = TWide; #else using TInt128 = signed __int128; using TUint128 = unsigned __int128; #endif template struct TDivider; #if defined(__clang__) && defined(DONT_USE_NATIVE_INT128) template<> struct TDivider<0> { static inline constexpr TUint128 Value = 1U; }; template struct TDivider { static inline constexpr TInt128 Value = TDivider::Value * 10U; }; #else template<> struct TDivider<0> { static constexpr TUint128 Value = 1U; }; template struct TDivider { static constexpr TUint128 Value = TDivider::Value * 10U; }; #endif constexpr ui8 MaxPrecision = 35; static_assert(sizeof(TInt128) == 16, "Wrong size of TInt128, expected 16"); inline constexpr TInt128 Inf() { return TInt128(100000000000000000ULL) * TInt128(1000000000000000000ULL); } inline constexpr TInt128 Nan() { return Inf() + TInt128(1); } inline constexpr TInt128 Err() { return Nan() + TInt128(1); } TUint128 GetDivider(ui8 scale); template inline constexpr TUint128 GetDivider() { return TDivider::Value; } template inline constexpr std::pair GetBounds() { return std::make_pair(-GetDivider() + (IncLow ? 1 : 0), +GetDivider() - (DecHigh ? 1 : 0)); } bool IsError(TInt128 v); bool IsNan(TInt128 v); bool IsInf(TInt128 v); bool IsNormal(TInt128 v); bool IsComparable(TInt128 v); template inline bool IsNormal(TInt128 v) { const auto& b = GetBounds(); return v > b.first && v < b.second; } const char* ToString(TInt128 v, ui8 precision, ui8 scale = 0); TInt128 FromString(const TStringBuf& str, ui8 precision, ui8 scale = 0); // Accept string representation with exponent. TInt128 FromStringEx(const TStringBuf& str, ui8 precision, ui8 scale); template inline TInt128 FromProto(const TMkqlProto& val) { ui64 half[2] = {val.GetLow128(), val.GetHi128()}; TInt128 val128; std::memcpy(&val128, half, sizeof(val128)); return val128; } template inline constexpr TValue YtDecimalNan() { return std::numeric_limits::max(); } template<> inline constexpr TInt128 YtDecimalNan() { return ~(TInt128(1) << 127); } template inline constexpr TValue YtDecimalInf() { return YtDecimalNan() - 1; } template inline TInt128 FromYtDecimal(TValue val) { static_assert(std::is_same::value || std::is_signed::value, "Expected signed value"); if (YtDecimalNan() == val) { return Nan(); } else if (YtDecimalInf() == val) { return Inf(); } else if (-YtDecimalInf() == val) { return -Inf(); } else { return TInt128(val); } } template inline TValue ToYtDecimal(TInt128 val) { static_assert(std::is_same::value || std::is_signed::value, "Expected signed value"); if (IsNormal(val)) { return (TValue)val; } else if (val == Inf()) { return YtDecimalInf(); } else if (val == -Inf()) { return -YtDecimalInf(); } return YtDecimalNan(); } inline TInt128 FromHalfs(ui64 lo, i64 hi) { ui64 half[2] = {lo, static_cast(hi)}; TInt128 val128; std::memcpy(&val128, half, sizeof(val128)); return val128; } inline std::pair MakePair(const TInt128 v) { std::pair r; std::memcpy(&r, &v, sizeof(v)); return r; static_assert(sizeof(r) == sizeof(v), "Bad pair size."); } bool IsValid(const TStringBuf& str); // Round to nearest, ties to even. TInt128 Div(TInt128 a, TInt128 b); // a/b TInt128 Mul(TInt128 a, TInt128 b); // a*b TInt128 Mod(TInt128 a, TInt128 b); // a%b // a*b/c Only for non zero even normal positive divider. TInt128 MulAndDivNormalDivider(TInt128 a, TInt128 b, TInt128 c); // a*b/c Only for non zero normal positive multiplier. TInt128 MulAndDivNormalMultiplier(TInt128 a, TInt128 b, TInt128 c); struct TDecimal { TInt128 Value = 0; TDecimal() = default; template TDecimal(T t): Value(t) { } explicit operator TInt128() const { return Value; } TDecimal& operator+=(TDecimal right) { const auto l = Value; const auto r = right.Value; const auto a = l + r; if (IsNormal(l) && IsNormal(r) && IsNormal(a)) { Value = a; } else if (IsNan(l) || IsNan(r) || !a /* inf - inf*/) { Value = Nan(); } else { Value = a > 0 ? +Inf() : -Inf(); } return *this; } TDecimal& operator*=(TDecimal right) { Value = Mul(Value, right.Value); return *this; } TDecimal& operator/=(TDecimal right) { Value = Div(Value, right.Value); return *this; } friend TDecimal operator+(TDecimal left, TDecimal right) { left += right; return left; } friend TDecimal operator*(TDecimal left, TDecimal right) { left *= right; return left; } friend TDecimal operator/(TDecimal left, TDecimal right) { left /= right; return left; } }; template class TDecimalMultiplicator { protected: const TInt128 Bound; public: TDecimalMultiplicator( ui8 precision, ui8 scale = 0 /* unused */) : Bound(GetDivider(precision)) { Y_UNUSED(scale); } TInt128 Do(TInt128 left, TRight right) const { TInt128 mul = Mul(left, right); if (mul > -Bound && mul < +Bound) return mul; return IsNan(mul) ? Nan() : (mul > 0 ? +Inf() : -Inf()); } }; template<> class TDecimalMultiplicator { protected: const TInt128 Bound; const TInt128 Divider; public: TDecimalMultiplicator( ui8 precision, ui8 scale) : Bound(GetDivider(precision)) , Divider(GetDivider(scale)) { } TInt128 Do(TInt128 left, TInt128 right) const { TInt128 mul = Divider > 1 ? MulAndDivNormalDivider(left, right, Divider): Mul(left, right); if (mul > -Bound && mul < +Bound) return mul; return IsNan(mul) ? Nan() : (mul > 0 ? +Inf() : -Inf()); } }; template class TDecimalDivisor { public: TDecimalDivisor( ui8 precision = 0 /* unused */, ui8 scale = 0 /* unused */) { Y_UNUSED(precision); Y_UNUSED(scale); } TInt128 Do(TInt128 left, TRight right) const { return Div(left, right); } }; template<> class TDecimalDivisor { protected: const TInt128 Bound; const TInt128 Divider; public: TDecimalDivisor( ui8 precision, ui8 scale) : Bound(GetDivider(precision)) , Divider(GetDivider(scale)) { } TInt128 Do(TInt128 left, TInt128 right) const { TInt128 div = MulAndDivNormalMultiplier(left, Divider, right); if (div > -Bound && div < +Bound) { return div; } return IsNan(div) ? Nan() : (div > 0 ? +Inf() : -Inf()); } }; template class TDecimalRemainder { protected: const TInt128 Bound; const TInt128 Divider; public: TDecimalRemainder( ui8 precision, ui8 scale) : Bound(NYql::NDecimal::GetDivider(precision - scale)) , Divider(NYql::NDecimal::GetDivider(scale)) { } TInt128 Do(TInt128 left, TRight right) const { if constexpr (std::is_signed::value) { if (TInt128(right) >= +Bound || TInt128(right) <= -Bound) return left; } else { if (TInt128(right) >= Bound) return left; } return Mod(left, Mul(Divider, right)); } }; template<> class TDecimalRemainder { public: TDecimalRemainder( ui8 precision = 0 /*unused*/, ui8 scale = 0 /*unused*/) { Y_UNUSED(precision); Y_UNUSED(scale); } TInt128 Do(TInt128 left, TInt128 right) const { return NYql::NDecimal::Mod(left, right); } }; } }