diff options
author | vvvv <vvvv@yandex-team.com> | 2024-11-02 16:47:48 +0300 |
---|---|---|
committer | vvvv <vvvv@yandex-team.com> | 2024-11-02 16:59:45 +0300 |
commit | d9417f6a9bc844e4b6ac40255999684026433719 (patch) | |
tree | b656fd425d7e15cadf5819e168b2eb74820ae0ba | |
parent | 4c6bd8b8a7bc697bf7131f9d91bfe3804732e406 (diff) | |
download | ydb-d9417f6a9bc844e4b6ac40255999684026433719.tar.gz |
Moved yql/public/decimal YQL-19206
fresh
init
commit_hash:1bae0afa7e556cf64c1c39196765e52d82efdee1
-rw-r--r-- | yql/essentials/public/decimal/ut/ya.make | 5 | ||||
-rw-r--r-- | yql/essentials/public/decimal/ut/yql_decimal_ut.cpp | 351 | ||||
-rw-r--r-- | yql/essentials/public/decimal/ut/yql_wide_int_ut.cpp | 465 | ||||
-rw-r--r-- | yql/essentials/public/decimal/ya.make | 14 | ||||
-rw-r--r-- | yql/essentials/public/decimal/yql_decimal.cpp | 463 | ||||
-rw-r--r-- | yql/essentials/public/decimal/yql_decimal.h | 346 | ||||
-rw-r--r-- | yql/essentials/public/decimal/yql_decimal_serialize.cpp | 82 | ||||
-rw-r--r-- | yql/essentials/public/decimal/yql_decimal_serialize.h | 13 | ||||
-rw-r--r-- | yql/essentials/public/decimal/yql_wide_int.h | 344 | ||||
-rw-r--r-- | yql/essentials/public/ya.make | 1 |
10 files changed, 2084 insertions, 0 deletions
diff --git a/yql/essentials/public/decimal/ut/ya.make b/yql/essentials/public/decimal/ut/ya.make new file mode 100644 index 0000000000..aad0eb010b --- /dev/null +++ b/yql/essentials/public/decimal/ut/ya.make @@ -0,0 +1,5 @@ +UNITTEST_FOR(yql/essentials/public/decimal) + +SRCS(yql_decimal_ut.cpp yql_wide_int_ut.cpp) + +END() diff --git a/yql/essentials/public/decimal/ut/yql_decimal_ut.cpp b/yql/essentials/public/decimal/ut/yql_decimal_ut.cpp new file mode 100644 index 0000000000..c93993190f --- /dev/null +++ b/yql/essentials/public/decimal/ut/yql_decimal_ut.cpp @@ -0,0 +1,351 @@ +#include <yql/essentials/public/decimal/yql_decimal.h> +#include <yql/essentials/public/decimal/yql_decimal_serialize.h> + +#include <library/cpp/testing/unittest/registar.h> + +namespace NYql { +namespace NDecimal { +Y_UNIT_TEST_SUITE(TYqlDecimalTest) { + void SimplePositiveTest(TInt128 v, ui8 precision, ui8 scale, const TString& expected) { + TString result = ToString(v, precision, scale); + UNIT_ASSERT_VALUES_EQUAL(result, expected); + TInt128 parsed = FromString(result, precision, scale); + UNIT_ASSERT(parsed == v); + } + + void SimpleNegativeFormatTest(TInt128 v, ui8 precision, ui8 scale) { + TString result = ToString(v, precision, scale); + UNIT_ASSERT_VALUES_EQUAL(result, ""); + } + + void SimpleSerializeAndDeserialize(TInt128 v, size_t expectedSize) { + char buff[sizeof(TInt128)]; + const auto s = Serialize(v, buff); + UNIT_ASSERT_VALUES_EQUAL(s, expectedSize); + const auto& des = Deserialize(buff, expectedSize); + UNIT_ASSERT_VALUES_EQUAL(des.second, expectedSize); + UNIT_ASSERT(des.first == v); + const auto& e = Deserialize(buff, expectedSize - 1); + UNIT_ASSERT(e.first == Err()); + } + + template<ui8 Precision, ui8 Scale> + void CheckMulAndRescale(const TStringBuf& lhs, const TStringBuf& rhs, const TStringBuf& expected) { + const auto l = FromString(lhs, Precision, Scale); + const auto r = FromString(rhs, Precision, Scale); + const auto m = MulAndDivNormalDivider(l, r, GetDivider<Scale>()); + const auto result = ToString(m, Precision, Scale); + UNIT_ASSERT_VALUES_EQUAL(result, expected); + } + + template<ui8 Precision, ui8 Scale> + void CheckDivAndRescale(const TStringBuf& lhs, const TStringBuf& rhs, const TStringBuf& expected) { + const auto l = FromString(lhs, Precision, Scale); + const auto r = FromString(rhs, Precision, Scale); + const auto m = MulAndDivNormalMultiplier(l, GetDivider<Scale>(), r); + const auto result = ToString(m, Precision, Scale); + UNIT_ASSERT_VALUES_EQUAL(result, expected); + } + + template<ui8 Precision, ui8 Scale = 0> + void CheckMul(const TStringBuf& lhs, const TStringBuf& rhs, const TStringBuf& expected) { + const auto l = FromString(lhs, Precision, Scale); + const auto r = FromString(rhs, Precision, Scale); + const auto m = Mul(l, r); + const auto result = ToString(m, Precision, Scale); + UNIT_ASSERT_VALUES_EQUAL(result, expected); + } + + Y_UNIT_TEST(TestZeroFormat) { + UNIT_ASSERT_VALUES_EQUAL(ToString(0, 1, 0), "0"); + UNIT_ASSERT_VALUES_EQUAL(ToString(0, 15, 6), "0"); + UNIT_ASSERT_VALUES_EQUAL(ToString(0, 15, 0), "0"); + } + + Y_UNIT_TEST(TestZeroScale) { + SimplePositiveTest(1, 5, 0, "1"); + SimplePositiveTest(10, 5, 0, "10"); + SimplePositiveTest(100, 5, 0, "100"); + SimplePositiveTest(1000, 5, 0, "1000"); + SimplePositiveTest(10000, 5, 0, "10000"); + SimpleNegativeFormatTest(100000, 5, 0); + SimpleNegativeFormatTest(1000000, 5, 0); + + // negative numbers + SimplePositiveTest(-1, 5, 0, "-1"); + SimplePositiveTest(-10, 5, 0, "-10"); + SimplePositiveTest(-100, 5, 0, "-100"); + SimplePositiveTest(-1000, 5, 0, "-1000"); + SimplePositiveTest(-10000, 5, 0, "-10000"); + SimpleNegativeFormatTest(-100000, 5, 0); + } + + Y_UNIT_TEST(TestFormats) { + // we have no trailing zeros + SimplePositiveTest(1, 15, 6, "0.000001"); + SimplePositiveTest(10, 15, 6, "0.00001"); + SimplePositiveTest(100, 15, 6, "0.0001"); + SimplePositiveTest(1000, 15, 6, "0.001"); + SimplePositiveTest(10000, 15, 6, "0.01"); + SimplePositiveTest(100000, 15, 6, "0.1"); + SimplePositiveTest(1000000, 15, 6, "1"); + SimplePositiveTest(10000000, 15, 6, "10"); + SimplePositiveTest(100000000, 15, 6, "100"); + + SimplePositiveTest(2020000, 15, 6, "2.02"); + SimplePositiveTest(3003000, 15, 6, "3.003"); + + // negative numbers + SimplePositiveTest(-1, 15, 6, "-0.000001"); + SimplePositiveTest(-10, 15, 6, "-0.00001"); + SimplePositiveTest(-100, 15, 6, "-0.0001"); + SimplePositiveTest(-1000, 15, 6, "-0.001"); + SimplePositiveTest(-10000, 15, 6, "-0.01"); + SimplePositiveTest(-100000, 15, 6, "-0.1"); + SimplePositiveTest(-1000000, 15, 6, "-1"); + SimplePositiveTest(-10000000, 15, 6, "-10"); + SimplePositiveTest(-100000000, 15, 6, "-100"); + + SimplePositiveTest(-2020000, 15, 6, "-2.02"); + SimplePositiveTest(-3003000, 15, 6, "-3.003"); + + SimplePositiveTest(1, 15, 6, "0.000001"); + SimplePositiveTest(12, 15, 6, "0.000012"); + SimplePositiveTest(123, 15, 6, "0.000123"); + SimplePositiveTest(1234, 15, 6, "0.001234"); + SimplePositiveTest(12345, 15, 6, "0.012345"); + SimplePositiveTest(123456, 15, 6, "0.123456"); + SimplePositiveTest(1234567, 15, 6, "1.234567"); + SimplePositiveTest(12345678, 15, 6, "12.345678"); + SimplePositiveTest(123456789, 15, 6, "123.456789"); + SimplePositiveTest(1234567898, 15, 6, "1234.567898"); + SimplePositiveTest(12345678987ll, 15, 6, "12345.678987"); + SimplePositiveTest(123456789876ll, 15, 6, "123456.789876"); + } + + Y_UNIT_TEST(TestHugeNumberFormat) { + TInt128 max120 = Inf() - 1; + const char max120String[] = "99999999999999999999999999999999999"; // 35 digits + static_assert(sizeof(max120String) == 36, "sizeof(max120String) == 36"); + SimplePositiveTest(max120, MaxPrecision, 0, max120String); + SimplePositiveTest(max120 + 1, MaxPrecision, 0, "inf"); + + TInt128 min120 = -Inf() + 1; + const char min120String[] = "-99999999999999999999999999999999999"; + static_assert(sizeof(min120String) == 37, "sizeof(min120String) == 37"); + SimplePositiveTest(min120, MaxPrecision, 0, min120String); + SimplePositiveTest(min120 - 1, MaxPrecision, 0, "-inf"); + + // take spot for sign and zero before dot + const char min120StringAfterDot[] = "-0.99999999999999999999999999999999999"; // 35 by nine + leading zero + static_assert(sizeof(min120StringAfterDot) == 39, "sizeof(min120StringAfterDot) == 39"); + SimplePositiveTest(min120, MaxPrecision, MaxPrecision, min120StringAfterDot); + + SimpleNegativeFormatTest(1, MaxPrecision + 1, MaxPrecision + 1); + SimpleNegativeFormatTest(1, MaxPrecision + 1, 0); + SimpleNegativeFormatTest(1, 2, 3); + } + + Y_UNIT_TEST(TestFormStringRoundToEven) { + UNIT_ASSERT(FromString(".51", 1, 0) == 1); + UNIT_ASSERT(FromString("-0.51", 1, 0) == -1); + + UNIT_ASSERT(FromString("+00000008.5", 1, 0) == 8); + UNIT_ASSERT(FromString("-8.5000000000000000000000000000000", 1, 0) == -8); + + UNIT_ASSERT(FromString("00008.51", 1, 0) == 9); + UNIT_ASSERT(FromString("-8.5000000000000000000000000000001", 1, 0) == -9); + + UNIT_ASSERT(FromString("09.499999999999999999999999999999999999999999999999999999999", 1, 0) == 9); + UNIT_ASSERT(FromString("-9.499999999999999999999999999999999999999999999999999999999", 1, 0) == -9); + + UNIT_ASSERT(FromString("9.50", 2, 0) == 10); + UNIT_ASSERT(FromString("-9.5", 2, 0) == -10); + + UNIT_ASSERT(FromString("+0.9949", 2, 2) == 99); + UNIT_ASSERT(FromString("-0.9949", 2, 2) == -99); + } + + Y_UNIT_TEST(TestInfinityValues) { + UNIT_ASSERT(FromString("+1", 1, 1) == Inf()); + UNIT_ASSERT(FromString("-1", 1, 1) == -Inf()); + + UNIT_ASSERT(FromString("10.000", 1, 0) == Inf()); + UNIT_ASSERT(FromString("-10.000", 1, 0) == -Inf()); + + UNIT_ASSERT(FromString("9.500", 1, 0) == Inf()); + UNIT_ASSERT(FromString("-9.500", 1, 0) == -Inf()); + + UNIT_ASSERT(FromString("+0.950", 1, 1) == Inf()); + UNIT_ASSERT(FromString("-0.950", 1, 1) == -Inf()); + + UNIT_ASSERT(FromString("+0.9950", 2, 2) == Inf()); + UNIT_ASSERT(FromString("-0.9950", 2, 2) == -Inf()); + + UNIT_ASSERT(FromString("9999999999999999999999999999999999999.5", 35, 0) == Inf()); + UNIT_ASSERT(FromString("-9999999999999999999999999999999999999.5", 35, 0) == -Inf()); + } + + Y_UNIT_TEST(TestInvalidValues) { + UNIT_ASSERT(IsValid("+999999999999999991234567890.039493804903849038490312345678909999999999999999990")); + + UNIT_ASSERT(!IsValid("")); // empty + UNIT_ASSERT(!IsValid("12.2.3")); // double dot + UNIT_ASSERT(!IsValid("+-12")); // extra sign + UNIT_ASSERT(!IsValid("463786378O74674")); // letter inside + + UNIT_ASSERT(IsError(FromString("", 35, 15))); // empty + UNIT_ASSERT(IsError(FromString("12.2.3", 35, 15))); // double dot + UNIT_ASSERT(IsError(FromString("+-12", 35, 15))); // extra sign + UNIT_ASSERT(IsError(FromString("463786378O74674", 35, 15))); // letter inside + UNIT_ASSERT(IsError(FromString("+7.039493804E1", 35, 5))); // letter in tail after scale + } + + Y_UNIT_TEST(TestFormStringEx) { + UNIT_ASSERT(FromStringEx("NAN", 13, 1) == Nan()); + UNIT_ASSERT(FromStringEx("+inf", 11, 7) == Inf()); + UNIT_ASSERT(FromStringEx("-inf", 7, 7) == -Inf()); + + UNIT_ASSERT(FromStringEx("0.1E3", 10, 1) == 1000); + UNIT_ASSERT(FromStringEx("0.51e-3", 10, 3) == 1); + + UNIT_ASSERT(FromStringEx("1E30", 10, 0) == Inf()); + UNIT_ASSERT(FromStringEx("1e-30", 10, 0) == 0); + UNIT_ASSERT(FromStringEx("-1E+99", 10, 2) == -Inf()); + UNIT_ASSERT(FromStringEx("-1e-99", 10, 2) == 0); + UNIT_ASSERT(FromStringEx("-510e-3", 1, 0) == -1); + UNIT_ASSERT(FromStringEx("+99E3", 5, 0) == 99000); + UNIT_ASSERT(FromStringEx("2.1E-130", 35, 2) == 0); + UNIT_ASSERT(FromStringEx("2.1E0", 35, 2) == 210); + } + + Y_UNIT_TEST(TestFormStringExInvalidValues) { + UNIT_ASSERT(IsError(FromStringEx("", 35, 15))); // empty + UNIT_ASSERT(IsError(FromStringEx("12.2.3", 35, 15))); // double dot + UNIT_ASSERT(IsError(FromStringEx("+-12", 35, 15))); // extra sign + UNIT_ASSERT(IsError(FromStringEx("463786378O74674", 35, 15))); // letter inside + + UNIT_ASSERT(IsError(FromStringEx("E2", 35, 15))); // empty + UNIT_ASSERT(IsError(FromStringEx("E2E4", 35, 15))); // empty + UNIT_ASSERT(IsError(FromStringEx("NANE5", 35, 15))); // nan with exp + UNIT_ASSERT(IsError(FromStringEx("infE5", 35, 15))); // inf with exp + UNIT_ASSERT(IsError(FromStringEx("-infe-5", 35, 15))); // inf with exp + UNIT_ASSERT(IsError(FromStringEx("2.1E0X", 35, 2))); // not fully parsed exp + UNIT_ASSERT(IsError(FromStringEx("2.1E+-1", 35, 2))); // two signs + UNIT_ASSERT(IsError(FromStringEx("ae30", 10, 0))); // bad mantissa + } + + Y_UNIT_TEST(TestSpecialAsString) { + UNIT_ASSERT(IsValid("Nan")); + UNIT_ASSERT(IsValid("INF")); + UNIT_ASSERT(IsValid("-inf")); + + UNIT_ASSERT_VALUES_EQUAL(ToString(Nan(), 10, 2), "nan"); + + UNIT_ASSERT_VALUES_EQUAL(ToString(+Inf(), 10, 2), "inf"); + UNIT_ASSERT_VALUES_EQUAL(ToString(-Inf(), 10, 2), "-inf"); + + UNIT_ASSERT(IsNan(FromString("nan", 10, 2))); + UNIT_ASSERT(IsInf(FromString("+INf", MaxPrecision, 6))); + UNIT_ASSERT(IsInf(FromString("-inF", 4, 2))); + } + + Y_UNIT_TEST(TestToStringOfNonNormal) { + // above Inf + for (TInt128 i = Inf() + 2, end = Inf() + 100; i < end; i++) { + UNIT_ASSERT(!IsNormal(i)); + UNIT_ASSERT(ToString(i, MaxPrecision, 0) == nullptr); + } + + // below -Inf + for (TInt128 i = -Inf() - 2, end = -Inf() - 100; i < end; i--) { + UNIT_ASSERT(!IsNormal(i)); + UNIT_ASSERT(ToString(i, MaxPrecision, 0) == nullptr); + } + } + + Y_UNIT_TEST(TestSerializeAndDeserialize) { + SimpleSerializeAndDeserialize(-Nan(), 1U); + SimpleSerializeAndDeserialize(-Inf(), 1U); + + SimpleSerializeAndDeserialize(-Inf() + 1, 16U); + SimpleSerializeAndDeserialize(-Inf() + 2, 16U); + + SimpleSerializeAndDeserialize(-65537, 4U); + SimpleSerializeAndDeserialize(-65536, 3U); + + SimpleSerializeAndDeserialize(-257, 3U); + SimpleSerializeAndDeserialize(-256, 2U); + + SimpleSerializeAndDeserialize(-3, 2U); + SimpleSerializeAndDeserialize(-2, 2U); + + SimpleSerializeAndDeserialize(-1, 1U); + SimpleSerializeAndDeserialize(0, 1U); + + SimpleSerializeAndDeserialize(+1, 2U); + SimpleSerializeAndDeserialize(+2, 2U); + + SimpleSerializeAndDeserialize(+255, 2U); + SimpleSerializeAndDeserialize(+256, 3U); + + SimpleSerializeAndDeserialize(+65535, 3U); + SimpleSerializeAndDeserialize(+65536, 4U); + + SimpleSerializeAndDeserialize(+Inf() - 2, 16U); + SimpleSerializeAndDeserialize(+Inf() - 1, 16U); + + SimpleSerializeAndDeserialize(+Inf(), 1U); + SimpleSerializeAndDeserialize(+Nan(), 1U); + } + + Y_UNIT_TEST(TestMulAndRescale) { + CheckMulAndRescale<35,35>("0.99999999999999999999999999999999999", "-0.99999999999999999999999999999999999", "-0.99999999999999999999999999999999998"); + CheckMulAndRescale<35,35>("-0.99999999999999999999999999999999999", "0.33333333333333333333333333333333333", "-0.33333333333333333333333333333333333"); + CheckMulAndRescale<35,35>("0.33333333333333333333333333333333333", "0.33333333333333333333333333333333333", "0.11111111111111111111111111111111111"); + CheckMulAndRescale<35,35>("0.99999999999999999999999999999999999", "0.000000000000001", "0.000000000000001"); + CheckMulAndRescale<35,35>("0.99999999999999999999999999999999999", "0.00000000000000101010101", "0.00000000000000101010101"); + CheckMulAndRescale<35,35>("0.12345678901234567890123456789012345", "0.12345678901234567890123456789012345", "0.01524157875323883675049535156256668"); + + CheckMulAndRescale<35,34>("9.9999999999999999999999999999999999", "-1.9999999999999999999999999999999999", "-inf"); + CheckMulAndRescale<35,34>("3.3333333333333333333333333333333333", "3.3333333333333333333333333333333333", "inf"); + CheckMulAndRescale<35,34>("3.3333333333333333333333333333333333", "1.3333333333333333333333333333333333", "4.4444444444444444444444444444444443"); + CheckMulAndRescale<35,34>("-1.3333333333333333333333333333333333", "1.3333333333333333333333333333333333", "-1.7777777777777777777777777777777777"); + + CheckMulAndRescale<35,34>("-7", "0", "0"); + CheckMulAndRescale<35,34>("inf", "nan", "nan"); + CheckMulAndRescale<35,34>("inf", "0", "nan"); + CheckMulAndRescale<35,34>("-inf", "-inf", "inf"); + } + + Y_UNIT_TEST(TestDivAndRescale) { + CheckDivAndRescale<35,35>("-0.99999999999999999999999999999999999", "0.33333333333333333333333333333333333", "-inf"); + CheckDivAndRescale<35,35>("0.33333333333333333333333333333333333", "-0.33333333333333333333333333333333333", "-inf"); + CheckDivAndRescale<35,35>("0.12345678901234567890123456789012345", "0.12345678901234567890123456789012345", "inf"); + + CheckDivAndRescale<35,34>("9.9999999999999999999999999999999999", "-1.9999999999999999999999999999999999", "-5.0000000000000000000000000000000002"); + CheckDivAndRescale<35,34>("3.3333333333333333333333333333333333", "3.3333333333333333333333333333333333", "1"); + CheckDivAndRescale<35,34>("3.3333333333333333333333333333333333", "1.3333333333333333333333333333333333", "2.5"); + CheckDivAndRescale<35,34>("-1.7777777777777777777777777777777777", "1.3333333333333333333333333333333333", "-1.3333333333333333333333333333333333"); + + CheckDivAndRescale<35,34>("-7", "0", "-inf"); + CheckDivAndRescale<35,34>("inf", "0", "inf"); + CheckDivAndRescale<35,34>("inf", "0", "inf"); + CheckDivAndRescale<35,34>("-inf", "inf", "nan"); + } + + Y_UNIT_TEST(TestWideMul) { + CheckMul<35>("999999999999999", "99999999999999999999", "99999999999999899999000000000000001"); + CheckMul<35>("9999999999999999", "99999999999999999999", "inf"); + CheckMul<35>("-99999999999999999999999999999999999", "10000000000000000000000000000000000", "-inf"); + CheckMul<35>("-99999999999999999999999999999999999", "-1", "99999999999999999999999999999999999"); + CheckMul<35>("-99999999999999999999999999999999999", "-2", "inf"); + + CheckMul<35>("nan", "0", "nan"); + CheckMul<35>("inf", "-inf", "-inf"); + CheckMul<35>("inf", "nan", "nan"); + } +} + +} +} diff --git a/yql/essentials/public/decimal/ut/yql_wide_int_ut.cpp b/yql/essentials/public/decimal/ut/yql_wide_int_ut.cpp new file mode 100644 index 0000000000..5238249da1 --- /dev/null +++ b/yql/essentials/public/decimal/ut/yql_wide_int_ut.cpp @@ -0,0 +1,465 @@ +#include <yql/essentials/public/decimal/yql_wide_int.h> +#include <library/cpp/testing/unittest/registar.h> + +namespace NYql { +Y_UNIT_TEST_SUITE(TYqlWideIntTest) { + template<typename T> + void TestUnary(const T aa) { + using Test = TWide<typename THalfOf<T>::Type>; + const Test at(aa); + static_assert(sizeof(at) == sizeof(aa), "Bad wide int size!"); + + UNIT_ASSERT_VALUES_EQUAL(static_cast<i8>(aa), static_cast<i8>(at)); + UNIT_ASSERT_VALUES_EQUAL(static_cast<ui8>(aa), static_cast<ui8>(at)); + UNIT_ASSERT_VALUES_EQUAL(static_cast<i16>(aa), static_cast<i16>(at)); + UNIT_ASSERT_VALUES_EQUAL(static_cast<ui16>(aa), static_cast<ui16>(at)); + UNIT_ASSERT_VALUES_EQUAL(static_cast<i32>(aa), static_cast<i32>(at)); + UNIT_ASSERT_VALUES_EQUAL(static_cast<ui32>(aa), static_cast<ui32>(at)); + UNIT_ASSERT_VALUES_EQUAL(static_cast<i64>(aa), static_cast<i64>(at)); + UNIT_ASSERT_VALUES_EQUAL(static_cast<ui64>(aa), static_cast<ui64>(at)); +#ifndef _win_ + UNIT_ASSERT(static_cast<i128_t>(aa) == static_cast<i128_t>(at)); + UNIT_ASSERT(static_cast<ui128_t>(aa) == static_cast<ui128_t>(at)); +#endif + + { + const auto exp = ~aa; + const auto tst = ~at; + UNIT_ASSERT(T(tst) == T(exp)); + } + + { + const auto exp = +aa; + const auto tst = +at; + UNIT_ASSERT(T(tst) == T(exp)); + } + + { + const auto exp = -aa; + const auto tst = -at; + UNIT_ASSERT(T(tst) == T(exp)); + } + + { + auto exp = aa; + auto tst = at; + ++exp; + ++tst; + UNIT_ASSERT(T(tst) == T(exp)); + } + + { + auto exp = aa; + auto tst = at; + --exp; + --tst; + UNIT_ASSERT(T(tst) == T(exp)); + } + + { + auto exp = aa; + auto tst = at; + exp++; + tst++; + UNIT_ASSERT(T(tst) == T(exp)); + } + + { + auto exp = aa; + auto tst = at; + exp--; + tst--; + UNIT_ASSERT(T(tst) == T(exp)); + } + } + + template<typename T> + void TestBinary(const T ll, const T rr) { + using Test = TWide<typename THalfOf<T>::Type>; + const Test lt(ll), rt(rr); + + { + const auto exp = ll & rr; + const auto tst = lt & rt; + UNIT_ASSERT(T(tst) == T(exp)); + } + + { + const auto exp = ll | rr; + const auto tst = lt | rt; + UNIT_ASSERT(T(tst) == T(exp)); + } + + { + const auto exp = ll ^ rr; + const auto tst = lt ^ rt; + UNIT_ASSERT(T(tst) == T(exp)); + } + + { + const auto exp = ll + rr; + const auto tst = lt + rt; + UNIT_ASSERT(T(tst) == T(exp)); + } + + { + const auto exp = ll - rr; + const auto tst = lt - rt; + UNIT_ASSERT(T(tst) == T(exp)); + } + + if (rr > 0 && rr < T(sizeof(T) << 3U)) + { + const auto exp = ll >> rr; + const auto tst = lt >> rt; + UNIT_ASSERT(T(tst) == T(exp)); + } + + if (rr > 0 && rr < T(sizeof(T) << 3U)) + { + const auto exp = ll << rr; + const auto tst = lt << rt; + UNIT_ASSERT(T(tst) == T(exp)); + } + + { + const auto exp = ll * rr; + const auto tst = lt * rt; + UNIT_ASSERT(T(tst) == T(exp)); + } + + if (rr) + { + const auto exp = ll / rr; + const auto tst = lt / rt; + UNIT_ASSERT(T(tst) == T(exp)); + } + + if (rr) + { + const auto exp = ll % rr; + const auto tst = lt % rt; + UNIT_ASSERT(T(tst) == T(exp)); + } + + { + const auto exp = ll == rr; + const auto tst = lt == rt; + UNIT_ASSERT_VALUES_EQUAL(tst, exp); + } + + { + const auto exp = ll != rr; + const auto tst = lt != rt; + UNIT_ASSERT_VALUES_EQUAL(tst, exp); + } + + { + const auto exp = ll > rr; + const auto tst = lt > rt; + UNIT_ASSERT_VALUES_EQUAL(tst, exp); + } + + { + const auto exp = ll < rr; + const auto tst = lt < rt; + UNIT_ASSERT_VALUES_EQUAL(tst, exp); + } + + { + const auto exp = ll >= rr; + const auto tst = lt >= rt; + UNIT_ASSERT_VALUES_EQUAL(tst, exp); + } + + { + const auto exp = ll <= rr; + const auto tst = lt <= rt; + UNIT_ASSERT_VALUES_EQUAL(tst, exp); + } + } + + template<typename T> + void TestsForUnsignedType() { + static_assert(std::is_unsigned<T>::value, "Tests for unsigned type."); + TestUnary<T>(2U); + TestUnary<T>(4U); + TestUnary<T>(17U); + TestUnary<T>(42U); + TestUnary<T>(127U); + TestUnary<T>(128U); + TestUnary<T>(129U); + TestUnary<T>(200U); + TestUnary<T>(255U); + TestUnary<T>(256U); + TestUnary<T>(257U); + + TestUnary<T>(std::numeric_limits<T>::min()); + TestUnary<T>(std::numeric_limits<T>::max()); + TestUnary<T>(std::numeric_limits<T>::max() - 1U); + TestUnary<T>(std::numeric_limits<T>::max() >> 1U); + TestUnary<T>(std::numeric_limits<T>::max() >> 3U); + TestUnary<T>(std::numeric_limits<T>::max() >> 7U); + + + TestUnary<T>(std::numeric_limits<T>::min() + 1U); + TestUnary<T>(std::numeric_limits<T>::max() - 1U); + + TestUnary<T>(std::numeric_limits<T>::min() + 3U); + TestUnary<T>(std::numeric_limits<T>::max() - 3U); + + TestUnary<T>(std::numeric_limits<T>::min() + 7U); + TestUnary<T>(std::numeric_limits<T>::max() - 7U); + + + TestBinary<T>(1U, 1U); + TestBinary<T>(7U, 31U); + TestBinary<T>(30000U, 13U); + TestBinary<T>(127U, 13U); + TestBinary<T>(128U, 19U); + TestBinary<T>(129U, 17U); + + + TestBinary<T>(std::numeric_limits<T>::min(), 7U); + TestBinary<T>(std::numeric_limits<T>::max(), 7U); + + TestBinary<T>(std::numeric_limits<T>::min(), 8U); + TestBinary<T>(std::numeric_limits<T>::max(), 8U); + + TestBinary<T>(std::numeric_limits<T>::min(), 9U); + TestBinary<T>(std::numeric_limits<T>::max(), 9U); + + TestBinary<T>(std::numeric_limits<T>::max() - 1U, std::numeric_limits<T>::min()); + TestBinary<T>(std::numeric_limits<T>::max() - 1U, std::numeric_limits<T>::min() + 1U); + + + TestBinary<T>(std::numeric_limits<T>::max(), std::numeric_limits<T>::max()); + TestBinary<T>(std::numeric_limits<T>::max(), std::numeric_limits<T>::min()); + TestBinary<T>(std::numeric_limits<T>::min(), std::numeric_limits<T>::min()); + TestBinary<T>(std::numeric_limits<T>::max(), std::numeric_limits<T>::min()); + + TestBinary<T>(std::numeric_limits<T>::max(), std::numeric_limits<T>::min() + 1U); + TestBinary<T>(std::numeric_limits<T>::max() - 1U, std::numeric_limits<T>::min()); + TestBinary<T>(std::numeric_limits<T>::max() - 1U, std::numeric_limits<T>::min() + 1U); + + TestBinary<T>(std::numeric_limits<T>::max() - 1U, std::numeric_limits<T>::min()); + TestBinary<T>(std::numeric_limits<T>::min() + 1U, std::numeric_limits<T>::min()); + + TestBinary<T>(std::numeric_limits<T>::max(), 1U); + TestBinary<T>(std::numeric_limits<T>::min(), 1U); + TestBinary<T>(std::numeric_limits<T>::max() - 1U, 1U); + TestBinary<T>(std::numeric_limits<T>::min() + 1U, 1U); + + TestBinary<T>(std::numeric_limits<T>::max(), 7U); + TestBinary<T>(std::numeric_limits<T>::min(), 7U); + TestBinary<T>(std::numeric_limits<T>::max() - 1U, 7U); + TestBinary<T>(std::numeric_limits<T>::min() + 1U, 7U); + + TestBinary<T>(std::numeric_limits<T>::max() >> 1U, std::numeric_limits<T>::min() >> 1U); + TestBinary<T>(std::numeric_limits<T>::max() >> 1U, std::numeric_limits<T>::min() + 1U); + TestBinary<T>(std::numeric_limits<T>::max() - 1U, std::numeric_limits<T>::min() >> 1U); + TestBinary<T>(std::numeric_limits<T>::max() >> 1U, std::numeric_limits<T>::min()); + TestBinary<T>(std::numeric_limits<T>::max(), std::numeric_limits<T>::min() >> 1U); + + TestBinary<T>(std::numeric_limits<T>::max() >> 3U, std::numeric_limits<T>::min() >> 3U); + TestBinary<T>(std::numeric_limits<T>::max() >> 3U, std::numeric_limits<T>::min() + 3U); + TestBinary<T>(std::numeric_limits<T>::max() - 3U, std::numeric_limits<T>::min() >> 3U); + TestBinary<T>(std::numeric_limits<T>::max() >> 3U, std::numeric_limits<T>::min()); + TestBinary<T>(std::numeric_limits<T>::max(), std::numeric_limits<T>::min() >> 3U); + } + + template<typename T> + void TestsForSignedType() { + static_assert(std::is_signed<T>::value, "Tests for signed type."); + TestUnary<T>(0); + + TestUnary<T>(1); + TestUnary<T>(-1); + + TestUnary<T>(2); + TestUnary<T>(-2); + + TestUnary<T>(3); + TestUnary<T>(-3); + + TestUnary<T>(4); + TestUnary<T>(-4); + + TestUnary<T>(17); + TestUnary<T>(-17); + + TestUnary<T>(42); + TestUnary<T>(-42); + + TestUnary<T>(127); + TestUnary<T>(-127); + + TestUnary<T>(128); + TestUnary<T>(-128); + + TestUnary<T>(129); + TestUnary<T>(-129); + + TestUnary<T>(200); + TestUnary<T>(-200); + + TestUnary<T>(255); + TestUnary<T>(-255); + + TestUnary<T>(256); + TestUnary<T>(-256); + + TestUnary<T>(257); + TestUnary<T>(-257); + + TestUnary<T>(258); + TestUnary<T>(-258); + + TestUnary<T>(std::numeric_limits<T>::min()); + TestUnary<T>(std::numeric_limits<T>::max()); + + TestUnary<T>(std::numeric_limits<T>::min() + 1); + TestUnary<T>(std::numeric_limits<T>::max() - 1); + + TestUnary<T>(std::numeric_limits<T>::min() + 2); + TestUnary<T>(std::numeric_limits<T>::max() - 2); + + TestUnary<T>(std::numeric_limits<T>::min() + 3); + TestUnary<T>(std::numeric_limits<T>::max() - 3); + + TestUnary<T>(std::numeric_limits<T>::min() + 7); + TestUnary<T>(std::numeric_limits<T>::max() - 7); + + TestUnary<T>(std::numeric_limits<T>::min() >> 1); + TestUnary<T>(std::numeric_limits<T>::max() >> 1); + + TestUnary<T>(std::numeric_limits<T>::min() >> 3); + TestUnary<T>(std::numeric_limits<T>::max() >> 3); + + TestUnary<T>(std::numeric_limits<T>::min() >> 7); + TestUnary<T>(std::numeric_limits<T>::max() >> 7); + + + TestUnary<T>(std::numeric_limits<T>::min() + 1); + TestUnary<T>(std::numeric_limits<T>::max() - 1); + + TestUnary<T>(std::numeric_limits<T>::min() + 3); + TestUnary<T>(std::numeric_limits<T>::max() - 3); + + TestUnary<T>(std::numeric_limits<T>::min() + 7); + TestUnary<T>(std::numeric_limits<T>::max() - 7); + + + TestBinary<T>(0, 0); + TestBinary<T>(1, 0); + TestBinary<T>(0, -1); + + TestBinary<T>(1, 1); + TestBinary<T>(1, -1); + TestBinary<T>(-1, 1); + TestBinary<T>(-1, -1); + + TestBinary<T>(7, 42); + TestBinary<T>(-7, 42); + + TestBinary<T>(0, -43); + + TestBinary<T>(-30000, 64); + TestBinary<T>(30000, -64); + TestBinary<T>(30000, 64); + + TestBinary<T>(21, 0); + TestBinary<T>(13, -127); + TestBinary<T>(-19, 128); + TestBinary<T>(-77, -129); + TestBinary<T>(13, 127); + TestBinary<T>(19, 128); + TestBinary<T>(77, 129); + + TestBinary<T>(std::numeric_limits<T>::max(), -1); + + TestBinary<T>(std::numeric_limits<T>::min(), -7); + TestBinary<T>(std::numeric_limits<T>::max(), -7); + + TestBinary<T>(std::numeric_limits<T>::min(), -8); + TestBinary<T>(std::numeric_limits<T>::max(), -8); + + TestBinary<T>(std::numeric_limits<T>::min(), -9); + TestBinary<T>(std::numeric_limits<T>::max(), -9); + + TestBinary<T>(std::numeric_limits<T>::min(), std::numeric_limits<T>::min() >> 5); + TestBinary<T>(std::numeric_limits<T>::max(), std::numeric_limits<T>::min() >> 5); + + + TestBinary<T>(std::numeric_limits<T>::min(), 7); + TestBinary<T>(std::numeric_limits<T>::max(), 7); + + TestBinary<T>(std::numeric_limits<T>::min(), 8); + TestBinary<T>(std::numeric_limits<T>::max(), 8); + + TestBinary<T>(std::numeric_limits<T>::min(), 9); + TestBinary<T>(std::numeric_limits<T>::max(), 9); + + TestBinary<T>(std::numeric_limits<T>::max() - 1, std::numeric_limits<T>::min()); + TestBinary<T>(std::numeric_limits<T>::max() - 1, std::numeric_limits<T>::min() + 1); + + + TestBinary<T>(std::numeric_limits<T>::max(), std::numeric_limits<T>::max()); + TestBinary<T>(std::numeric_limits<T>::max(), std::numeric_limits<T>::min()); + TestBinary<T>(std::numeric_limits<T>::min(), std::numeric_limits<T>::min()); + TestBinary<T>(std::numeric_limits<T>::max(), std::numeric_limits<T>::min()); + + TestBinary<T>(std::numeric_limits<T>::max(), std::numeric_limits<T>::min() + 1); + TestBinary<T>(std::numeric_limits<T>::max() - 1, std::numeric_limits<T>::min()); + TestBinary<T>(std::numeric_limits<T>::max() - 1, std::numeric_limits<T>::min() + 1); + + TestBinary<T>(std::numeric_limits<T>::max(), 0); + TestBinary<T>(std::numeric_limits<T>::min(), 0); + TestBinary<T>(std::numeric_limits<T>::max() - 1, 0); + TestBinary<T>(std::numeric_limits<T>::min() + 1, 0); + + TestBinary<T>(std::numeric_limits<T>::max(), 1); + TestBinary<T>(std::numeric_limits<T>::min(), 1); + TestBinary<T>(std::numeric_limits<T>::max() - 1, 1); + TestBinary<T>(std::numeric_limits<T>::min() + 1, 1); + + TestBinary<T>(std::numeric_limits<T>::max(), 7); + TestBinary<T>(std::numeric_limits<T>::min(), 7); + TestBinary<T>(std::numeric_limits<T>::max() - 1, 7); + TestBinary<T>(std::numeric_limits<T>::min() + 1, 7); + + TestBinary<T>(std::numeric_limits<T>::max() >> 1, std::numeric_limits<T>::min() >> 1); + TestBinary<T>(std::numeric_limits<T>::max() >> 1, std::numeric_limits<T>::min() + 1); + TestBinary<T>(std::numeric_limits<T>::max() - 1, std::numeric_limits<T>::min() >> 1); + TestBinary<T>(std::numeric_limits<T>::max() >> 1, std::numeric_limits<T>::min()); + TestBinary<T>(std::numeric_limits<T>::max(), std::numeric_limits<T>::min() >> 1); + + TestBinary<T>(std::numeric_limits<T>::max() >> 3, std::numeric_limits<T>::min() >> 3); + TestBinary<T>(std::numeric_limits<T>::max() >> 3, std::numeric_limits<T>::min() + 3); + TestBinary<T>(std::numeric_limits<T>::max() - 3, std::numeric_limits<T>::min() >> 3); + TestBinary<T>(std::numeric_limits<T>::max() >> 3, std::numeric_limits<T>::min()); + TestBinary<T>(std::numeric_limits<T>::max(), std::numeric_limits<T>::min() >> 3); + } + + Y_UNIT_TEST(CheckUnsignedByCompilerIntegrals) { + TestsForUnsignedType<ui32>(); + TestsForUnsignedType<ui64>(); +#ifndef _win_ + TestsForUnsignedType<ui128_t>(); +#endif + } + +#ifndef _ubsan_enabled_ +#ifndef _msan_enabled_ + Y_UNIT_TEST(CheckSignedByCompilerIntegrals) { + TestsForSignedType<i32>(); + TestsForSignedType<i64>(); +#ifndef _win_ + TestsForSignedType<i128_t>(); +#endif + } +#endif +#endif +} + +} diff --git a/yql/essentials/public/decimal/ya.make b/yql/essentials/public/decimal/ya.make new file mode 100644 index 0000000000..a9c23995b3 --- /dev/null +++ b/yql/essentials/public/decimal/ya.make @@ -0,0 +1,14 @@ +LIBRARY() + +SRCS( + yql_decimal.h + yql_decimal.cpp + yql_decimal_serialize.h + yql_decimal_serialize.cpp +) + +END() + +RECURSE_FOR_TESTS( + ut +) diff --git a/yql/essentials/public/decimal/yql_decimal.cpp b/yql/essentials/public/decimal/yql_decimal.cpp new file mode 100644 index 0000000000..91564a1a5f --- /dev/null +++ b/yql/essentials/public/decimal/yql_decimal.cpp @@ -0,0 +1,463 @@ +#include "yql_decimal.h" + +#include <cstring> +#include <ostream> +#include <string> +#include <charconv> + +namespace NYql { +namespace NDecimal { + +static const TUint128 Ten(10U); + +TUint128 GetDivider(ui8 scale) { + if (scale > MaxPrecision) { + return Inf(); + } + + TUint128 d(1U); + while (scale--) + d *= Ten; + return d; +} + +bool IsError(TInt128 v) { + return v > Nan() || v < -Inf(); +} + +bool IsNan(TInt128 v) { + return v == Nan(); +} + +bool IsInf(TInt128 v) { + return v == Inf() || v == -Inf(); +} + +bool IsNormal(TInt128 v) { + return v < Inf() && v > -Inf(); +} + +bool IsComparable(TInt128 v) { + return v <= Inf() && v >= -Inf(); +} + +const char* ToString(TInt128 val, ui8 precision, ui8 scale) { + if (!precision || precision > MaxPrecision || scale > precision) { + return nullptr; + } + + if (val == Inf()) + return "inf"; + if (val == -Inf()) + return "-inf"; + if (val == Nan()) + return "nan"; + + if (!IsNormal(val)) { + return nullptr; + } + + if (!val) { + return "0"; + } + + const bool neg = val < 0; + TUint128 v = neg ? -val : val; + + // log_{10}(2^120) ~= 36.12, 37 decimal places + // plus dot, zero before dot, sign and zero byte at the end + static thread_local char str[40]; + auto end = str + sizeof(str); + *--end = 0; + + auto s = end; + + do { + if (!precision--) { + return nullptr; + } + + + const auto digit = ui8(v % Ten); + if (digit || !scale || s != end) { + *--s = "0123456789"[digit]; + } + + if (scale && !--scale && s != end) { + *--s = '.'; + } + } while (v /= Ten); + + if (scale) { + do { + if (!precision--) { + return nullptr; + } + + *--s = '0'; + } while (--scale); + + *--s = '.'; + } + + if (*s == '.') { + *--s = '0'; + } + + if (neg) { + *--s = '-'; + } + + return s; +} + +namespace { + bool IsNan(const char* s) { + return (s[0] == 'N' || s[0] == 'n') && (s[1] == 'A' || s[1] == 'a') && (s[2] == 'N' || s[2] == 'n'); + } + + bool IsInf(const char* s) { + return (s[0] == 'I' || s[0] == 'i') && (s[1] == 'N' || s[1] == 'n') && (s[2] == 'F' || s[2] == 'f'); + } +} + + +TInt128 FromString(const TStringBuf& str, ui8 precision, ui8 scale) { + if (scale > precision) + return Err(); + + auto s = str.data(); + auto l = str.size(); + + if (!s || !l) + return Err(); + + const bool neg = '-' == *s; + if (neg || '+' == *s) { + ++s; + --l; + } + + if (3U == l) { + if (IsInf(s)) + return neg ? -Inf() : Inf(); + if (IsNan(s)) + return Nan(); + } + + TUint128 v = 0U; + auto integral = precision - scale; + + for (bool dot = false; l; --l) { + if (*s == '.') { + if (dot) + return Err(); + + ++s; + dot = true; + continue; + } + + if (dot) { + if (scale) + --scale; + else + break; + } + + const char c = *s++; + if (!std::isdigit(c)) + return Err(); + + v *= Ten; + v += c - '0'; + + if (!dot && v && !integral--) { + return neg ? -Inf() : Inf(); + } + } + + if (l--) { + const char c = *s++; + if (!std::isdigit(c)) + return Err(); + + bool plus = c > '5'; + if (!plus && c == '5') { + for (plus = v & 1; !plus && l; --l) { + const char c = *s++; + if (!std::isdigit(c)) + return Err(); + + plus = c != '0'; + } + } + + while (l--) + if (!std::isdigit(*s++)) + return Err(); + + if (plus) + if (++v >= GetDivider(precision)) + v = Inf(); + } + + while (scale--) + v *= Ten; + + return neg ? -v : v; +} + +TInt128 FromStringEx(const TStringBuf& str, ui8 precision, ui8 scale) { + if (scale > precision) + return Err(); + + const auto s = str.data(); + + for (auto ptr = s + str.size() - 1U; ptr > s; --ptr) { + if (*ptr == 'E' || *ptr == 'e') { + const auto len = ptr - s; + if (!len) + return Err(); + + ++ptr; + if (ptr != s + str.size() && *ptr == '+') { + ++ptr; + if (ptr != s + str.size() && *ptr == '-') + return Err(); + } + + int exp; + auto [finish, ec] = std::from_chars(ptr, s + str.size(), exp); + if (ec != std::errc() || finish != s + str.size()) + return Err(); + + const int p = precision, s = int(scale) + exp; + + const auto r = exp > 0 ? + FromString(str.Head(len), precision, std::min(s, p)): + FromString(str.Head(len), std::min(p - exp, int(MaxPrecision)), std::max(s, 0)); + + if (IsError(r) || IsNan(r)) { + return Err(); + } + + if (IsInf(r)) { + auto p = str.data(); + if (*p == '+' || *p == '-') + ++p; + + if (!std::isdigit(*p)) + return Err(); + + return r; + } + + if (const auto e = exp > 0 ? std::max(0, s - p) : std::min(0, s)) { + if (r) { + if (exp > 0) + return Mul(r, GetDivider(+e)); + if (exp < 0) + return Div(r, GetDivider(-e)); + } + } + + return r; + } + } + + return FromString(str, precision, scale); +} + +bool IsValid(const TStringBuf& str) { + auto s = str.data(); + auto l = str.size(); + + if (!s || !l) + return false; + + if ('-' == *s || '+' == *s) { + ++s; + --l; + } + + if (3U == l && (IsInf(s) || IsNan(s))) { + return true; + } + + for (bool dot = false; l--;) { + const char c = *s++; + if (c == '.') { + if (dot) + return false; + + dot = true; + continue; + } + + if (!std::isdigit(c)) + return false; + } + + return true; +} + +TInt128 Mod(TInt128 a, TInt128 b) { + if (!b || !(IsNormal(a) && IsNormal(b))) + return Nan(); + + return a % b; +} + +TInt128 Div(TInt128 a, TInt128 b) { + if (IsNan(a) || IsNan(b)) + return Nan(); + + if (!b) { + if (a > 0) + return Inf(); + else if (a < 0) + return -Inf(); + else + return Nan(); + } else if (IsInf(b)) { + return IsInf(a) ? Nan() : TInt128(0); + } else if (IsInf(a)) { + return b > 0 ? a : -a; + } + + if (b & 1) + a = TUint128(a) << 1U; + else + b >>= 1; + + auto d = a / b; + + if (d & 1) { + if (const auto m = a % b) { + if (m > 0) ++d; + // else --d; + } else { + if (d & 2) ++d; + } + } + + return d >>= 1; +} + +namespace { + +using TInt256 = TWide<TInt128, TInt128, TUint128>; + +TInt128 Normalize(const TInt256& v) { + static const TInt256 PInf256(+Inf()), NInf256(-Inf()); + + if (v > PInf256) + return +Inf(); + if (v < NInf256) + return -Inf(); + return *reinterpret_cast<const TInt128*>(&v); +} + +constexpr auto HalfBitSize = sizeof(TUint128) << 2U; + +TUint128 GetUpperHalf(const TUint128& v) { + return v >> HalfBitSize; +} + +TUint128 GetLowerHalf(const TUint128& v) { + return v & TUint128(0xFFFFFFFFFFFFFFFFULL); +} + +TInt256 WidenMul(const TInt128& lhs, const TInt128& rhs) { + const bool nl = lhs < 0; + const bool nr = rhs < 0; + + const TUint128 l = nl ? -lhs : +lhs; + const TUint128 r = nr ? -rhs : +rhs; + + const TUint128 lh[] = {GetLowerHalf(l), GetUpperHalf(l)}; + const TUint128 rh[] = {GetLowerHalf(r), GetUpperHalf(r)}; + + const TUint128 prods[] = {lh[0] * rh[0], lh[0] * rh[1], lh[1] * rh[0], lh[1] * rh[1]}; + + const TUint128 fourthQ = GetLowerHalf(prods[0]); + const TUint128 thirdQ = GetUpperHalf(prods[0]) + GetLowerHalf(prods[1]) + GetLowerHalf(prods[2]); + const TUint128 secondQ = GetUpperHalf(thirdQ) + GetUpperHalf(prods[1]) + GetUpperHalf(prods[2]) + GetLowerHalf(prods[3]); + const TUint128 firstQ = GetUpperHalf(secondQ) + GetUpperHalf(prods[3]); + + const TInt256 combine((firstQ << HalfBitSize) | GetLowerHalf(secondQ), (thirdQ << HalfBitSize) | fourthQ); + return nl == nr ? +combine : -combine; +} + +template<bool MayOddDivider> +TInt256 Div(TInt256&& a, TInt256&& b) { + if (MayOddDivider && b & 1) + a <<= 1; + else + b >>= 1; + + auto d = a / b; + + if (d & 1) { + if (const auto m = a % b) { + if (m > 0) ++d; + // else --d; + } else { + if (d & 2) ++d; + } + } + + return d >>= 1; +} + +} + +TInt128 Mul(TInt128 a, TInt128 b) { + if (IsNan(a) || IsNan(b)) + return Nan(); + + if (IsInf(a)) + return !b ? Nan() : (b > 0 ? a : -a); + + if (IsInf(b)) + return !a ? Nan() : (a > 0 ? b : -b); + + return Normalize(WidenMul(a, b)); +} + +TInt128 MulAndDivNormalMultiplier(TInt128 a, TInt128 b, TInt128 c) { + if (IsNan(a) || IsNan(c)) + return Nan(); + + if (!c) { + if (a > 0) + return Inf(); + else if (a < 0) + return -Inf(); + else + return Nan(); + } else if (IsInf(c)) { + return IsInf(a) ? Nan() : TInt128(0); + } else if (IsInf(a)) { + return c > 0 ? a : -a; + } + + return Normalize(Div<true>(WidenMul(a, b), TInt256(c))); +} + +TInt128 MulAndDivNormalDivider(TInt128 a, TInt128 b, TInt128 c) { + if (IsNan(a) || IsNan(b)) + return Nan(); + + if (IsInf(a)) + return !b ? Nan() : (b > 0 ? a : -a); + + if (IsInf(b)) + return !a ? Nan() : (a > 0 ? b : -b); + + return Normalize(Div<false>(WidenMul(a, b), TInt256(c))); +} + +} +} diff --git a/yql/essentials/public/decimal/yql_decimal.h b/yql/essentials/public/decimal/yql_decimal.h new file mode 100644 index 0000000000..a53ed3ffa4 --- /dev/null +++ b/yql/essentials/public/decimal/yql_decimal.h @@ -0,0 +1,346 @@ +#pragma once + +#include <util/generic/strbuf.h> +#include "yql_wide_int.h" + +#include <type_traits> +#include <limits> + +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<i64>; +using TUint128 = TWide<ui64>; +#else +using TInt128 = signed __int128; +using TUint128 = unsigned __int128; +#endif + +template<ui8 Scale> struct TDivider; +#if defined(__clang__) && defined(DONT_USE_NATIVE_INT128) +template<> struct TDivider<0> { static inline constexpr TUint128 Value = 1U; }; +template<ui8 Scale> struct TDivider { static inline constexpr TInt128 Value = TDivider<Scale - 1U>::Value * 10U; }; +#else +template<> struct TDivider<0> { static constexpr TUint128 Value = 1U; }; +template<ui8 Scale> struct TDivider { static constexpr TUint128 Value = TDivider<Scale - 1U>::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<ui8 Precision> +inline constexpr TUint128 GetDivider() { + return TDivider<Precision>::Value; +} + +template<ui8 Precision, bool IncLow = false, bool DecHigh = false> +inline constexpr std::pair<TInt128, TInt128> GetBounds() { + return std::make_pair(-GetDivider<Precision>() + (IncLow ? 1 : 0), +GetDivider<Precision>() - (DecHigh ? 1 : 0)); +} + +bool IsError(TInt128 v); +bool IsNan(TInt128 v); +bool IsInf(TInt128 v); + +bool IsNormal(TInt128 v); +bool IsComparable(TInt128 v); + +template<ui8 Precision> +inline bool IsNormal(TInt128 v) { + const auto& b = GetBounds<Precision>(); + 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<typename TMkqlProto> +inline TInt128 FromProto(const TMkqlProto& val) { + ui64 half[2] = {val.GetLow128(), val.GetHi128()}; + TInt128 val128; + std::memcpy(&val128, half, sizeof(val128)); + return val128; +} + +template<typename TValue> +inline constexpr TValue YtDecimalNan() { + return std::numeric_limits<TValue>::max(); +} + +template<> +inline constexpr TInt128 YtDecimalNan<TInt128>() { + return ~(TInt128(1) << 127); +} + +template<typename TValue> +inline constexpr TValue YtDecimalInf() { + return YtDecimalNan<TValue>() - 1; +} + +template<typename TValue> +inline TInt128 FromYtDecimal(TValue val) { + static_assert(std::is_same<TInt128, TValue>::value || std::is_signed<TValue>::value, "Expected signed value"); + if (YtDecimalNan<TValue>() == val) { + return Nan(); + } else if (YtDecimalInf<TValue>() == val) { + return Inf(); + } else if (-YtDecimalInf<TValue>() == val) { + return -Inf(); + } else { + return TInt128(val); + } +} + +template<typename TValue> +inline TValue ToYtDecimal(TInt128 val) { + static_assert(std::is_same<TInt128, TValue>::value || std::is_signed<TValue>::value, "Expected signed value"); + if (IsNormal(val)) { + return (TValue)val; + } else if (val == Inf()) { + return YtDecimalInf<TValue>(); + } else if (val == -Inf()) { + return -YtDecimalInf<TValue>(); + } + return YtDecimalNan<TValue>(); +} + +inline TInt128 FromHalfs(ui64 lo, i64 hi) { + ui64 half[2] = {lo, static_cast<ui64>(hi)}; + TInt128 val128; + std::memcpy(&val128, half, sizeof(val128)); + return val128; +} + +inline std::pair<ui64, ui64> MakePair(const TInt128 v) { + std::pair<ui64, ui64> 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<typename T> + 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<typename TRight> +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<TInt128> { +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<typename TRight> +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<TInt128> { +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<typename TRight> +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<TRight>::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<TInt128> { +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); + } +}; + +} +} diff --git a/yql/essentials/public/decimal/yql_decimal_serialize.cpp b/yql/essentials/public/decimal/yql_decimal_serialize.cpp new file mode 100644 index 0000000000..5fb844f5f8 --- /dev/null +++ b/yql/essentials/public/decimal/yql_decimal_serialize.cpp @@ -0,0 +1,82 @@ +#include "yql_decimal_serialize.h" + +#include <utility> + +namespace NYql { +namespace NDecimal { + +size_t Serialize(TInt128 value, char* buf) { + if (value == -Nan()) { + *buf = 0x00; + return 1U; + } + + if (value == -Inf()) { + *buf = 0x01; + return 1U; + } + + if (value == +Inf()) { + *buf = 0xFE; + return 1U; + } + + if (value == +Nan()) { + *buf = 0xFF; + return 1U; + } + + auto size = sizeof(value); + auto p = reinterpret_cast<const char*>(&value) + size - 1U; + + if (*(p - 1U) & 0x80) { + while (size > 1U && ~0 == *--p) + --size; + *buf = 0x80 - size; + } else { + while (size > 1U && 0 == *--p) + --size; + *buf = 0x7F + size; + } + + for (auto i = 1U; i < size; ++i) { + *++buf = *p--; + } + + return size; +} + +std::pair<TInt128, size_t> Deserialize(const char* b, size_t len) { + if (!b || len == 0U) + return std::make_pair(Err(), 0U); + + const auto mark = ui8(*b); + const bool neg = mark < 0x80u; + if (mark == 0x00u || mark == 0xFFu) + return std::make_pair(neg ? -Nan() : +Nan(), 1U); + if (mark == 0x01u || mark == 0xFEu) + return std::make_pair(neg ? -Inf() : +Inf(), 1U); + + if (mark < 0x70u || mark > 0x8Fu) { + return std::make_pair(Err(), 0U); + } + + const auto used = neg ? 0x80u - mark : mark - 0x7Fu; + if (len < used) + return std::make_pair(Err(), 0U); + + TInt128 v; + const auto size = sizeof(v); + auto p = reinterpret_cast<char*>(&v) + size; + + for (auto fill = size - used + 2U; --fill;) + *--p = neg ? ~0 : 0; + + for (auto copy = used; --copy;) + *--p = *++b; + + return std::make_pair(v, used); +} + +} +} diff --git a/yql/essentials/public/decimal/yql_decimal_serialize.h b/yql/essentials/public/decimal/yql_decimal_serialize.h new file mode 100644 index 0000000000..6fdb32e6f2 --- /dev/null +++ b/yql/essentials/public/decimal/yql_decimal_serialize.h @@ -0,0 +1,13 @@ +#pragma once + +#include <yql/essentials/public/decimal/yql_decimal.h> + +namespace NYql { +namespace NDecimal { + +// big-endian 16 bytes buffer. +size_t Serialize(TInt128 v, char* buff); +std::pair<TInt128, size_t> Deserialize(const char* buff, size_t len); + +} +} diff --git a/yql/essentials/public/decimal/yql_wide_int.h b/yql/essentials/public/decimal/yql_wide_int.h new file mode 100644 index 0000000000..0f650213b9 --- /dev/null +++ b/yql/essentials/public/decimal/yql_wide_int.h @@ -0,0 +1,344 @@ +#pragma once + +#include <util/system/types.h> + +#include <type_traits> +#include <tuple> +#include <cmath> + +namespace NYql { + +#ifndef _win_ +typedef __int128 i128_t; +typedef unsigned __int128 ui128_t; +#endif + +template<typename TOneHalf, typename TSignedPart = std::make_signed_t<TOneHalf>, typename TUnsignedPart = std::make_unsigned_t<TOneHalf>> +class TWide { +private: + using THalf = TOneHalf; + using TPart = TUnsignedPart; + + using TIsSigned = std::is_same<THalf, TSignedPart>; + using TIsUnsigned = std::is_same<THalf, TUnsignedPart>; + + static_assert(TIsSigned::value || TIsUnsigned::value, "Invalid using of TWide."); + static_assert(sizeof(TSignedPart) == sizeof(TUnsignedPart), "Different sizes of TWide parts."); + + TPart Lo; + THalf Hi; + + static constexpr auto FullBitSize = sizeof(TPart) << 4U; + static constexpr auto PartBitSize = sizeof(TPart) << 3U; + static constexpr auto QuarterBitSize = sizeof(TPart) << 2U; + + static constexpr TPart GetUpperQuarter(TPart h) { + return h >> QuarterBitSize; + } + + static constexpr TPart GetLowerQuarter(TPart h) { + const auto mask = TPart(~TPart(0U)) >> QuarterBitSize; + return h & mask; + } + + template<typename T> + constexpr std::enable_if_t<sizeof(T) <= sizeof(THalf), T> CastImpl() const { + return static_cast<T>(Lo); + } + + template<typename T> + constexpr std::enable_if_t<sizeof(T) == sizeof(THalf) << 1U, T> CastImpl() const { + return *reinterpret_cast<const T*>(this); + } + + template<typename T> + constexpr std::enable_if_t<sizeof(THalf) << 1U < sizeof(T), T> CastImpl() const { + return (T(std::make_unsigned_t<T>(Hi) << PartBitSize)) | Lo; + } + + constexpr size_t GetBits() const { + size_t out = Hi ? PartBitSize : 0U; + for (auto up = TPart(out ? Hi : Lo); up; up >>= 1U) { + ++out; + } + return out; + } + + using TUnsigned = TWide<TUnsignedPart, TSignedPart, TUnsignedPart>; + + using TSibling = std::conditional_t<std::is_same<THalf, TPart>::value, + TWide<TSignedPart, TSignedPart, TUnsignedPart>, TWide<TUnsignedPart, TSignedPart, TUnsignedPart>>; + friend TSibling; +public: + constexpr TWide() = default; + constexpr TWide(const TWide& rhs) = default; + constexpr TWide(TWide&& rhs) = default; + + constexpr TWide& operator=(const TWide& rhs) = default; + constexpr TWide& operator=(TWide&& rhs) = default; + + constexpr TWide(const TSibling& rhs) + : Lo(rhs.Lo), Hi(rhs.Hi) + {} + + template<typename U, typename L> + constexpr TWide(const U upper_rhs, const L lower_rhs) + : Lo(lower_rhs), Hi(upper_rhs) + {} + + template <typename T, std::enable_if_t<std::is_integral<T>::value && sizeof(THalf) < sizeof(T), size_t> Shift = PartBitSize> + constexpr TWide(const T rhs) + : Lo(rhs), Hi(rhs >> Shift) + {} + + template <typename T, std::enable_if_t<std::is_integral<T>::value && sizeof(T) <= sizeof(THalf), bool> Signed = std::is_signed<T>::value> + constexpr TWide(const T rhs) + : Lo(rhs), Hi(Signed && rhs < 0 ? ~0 : 0) + {} + + template <typename T, typename TArg = std::enable_if_t<std::is_class<T>::value && std::is_same<T, THalf>::value, THalf>> + constexpr explicit TWide(const T& rhs) + : Lo(rhs), Hi(TIsSigned::value && rhs < 0 ? ~0 : 0) + {} + + template <typename T, std::enable_if_t<std::is_integral<T>::value && sizeof(THalf) < sizeof(T), size_t> Shift = PartBitSize> + constexpr TWide& operator=(const T rhs) { + Hi = rhs >> Shift; + Lo = rhs; + return *this; + } + + template <typename T, std::enable_if_t<std::is_integral<T>::value && sizeof(T) <= sizeof(THalf), bool> Signed = std::is_signed<T>::value> + constexpr TWide& operator=(const T rhs) { + Hi = Signed && rhs < 0 ? ~0 : 0; + Lo = rhs; + return *this; + } + + constexpr explicit operator bool() const { return bool(Hi) || bool(Lo); } + + constexpr explicit operator i8() const { return CastImpl<i8>(); } + constexpr explicit operator ui8() const { return CastImpl<ui8>(); } + constexpr explicit operator i16() const { return CastImpl<i16>(); } + constexpr explicit operator ui16() const { return CastImpl<ui16>(); } + constexpr explicit operator i32() const { return CastImpl<i32>(); } + constexpr explicit operator ui32() const { return CastImpl<ui32>(); } + constexpr explicit operator i64() const { return CastImpl<i64>(); } + constexpr explicit operator ui64() const { return CastImpl<ui64>(); } +#ifndef _win_ + constexpr explicit operator i128_t() const { return CastImpl<i128_t>(); } + constexpr explicit operator ui128_t() const { return CastImpl<ui128_t>(); } +#endif + constexpr explicit operator float() const { + return TIsSigned::value && Hi < 0 ? -float(-*this) : std::fma(float(Hi), std::exp2(float(PartBitSize)), float(Lo)); + } + + constexpr explicit operator double() const { + return TIsSigned::value && Hi < 0 ? -double(-*this) : std::fma(double(Hi), std::exp2(double(PartBitSize)), double(Lo)); + } + + constexpr TWide operator~() const { + return TWide(~Hi, ~Lo); + } + + constexpr TWide operator+() const { + return TWide(Hi, Lo); + } + + constexpr TWide operator-() const { + const auto sign = THalf(1) << PartBitSize - 1U; + if (TIsSigned::value && !Lo && Hi == sign) + return *this; + return ++~*this; + } + + constexpr TWide& operator++() { + if (!++Lo) ++Hi; + return *this; + } + + constexpr TWide& operator--() { + if (!Lo--) --Hi; + return *this; + } + + constexpr TWide operator++(int) { + const TWide r(*this); + ++*this; + return r; + } + + constexpr TWide operator--(int) { + const TWide r(*this); + --*this; + return r; + } + + constexpr TWide& operator&=(const TWide& rhs) { + Hi &= rhs.Hi; + Lo &= rhs.Lo; + return *this; + } + + constexpr TWide& operator|=(const TWide& rhs) { + Hi |= rhs.Hi; + Lo |= rhs.Lo; + return *this; + } + + constexpr TWide& operator^=(const TWide& rhs) { + Hi ^= rhs.Hi; + Lo ^= rhs.Lo; + return *this; + } + + constexpr TWide& operator+=(const TWide& rhs) { + const auto l = Lo; + Lo += rhs.Lo; + Hi += rhs.Hi; + if (l > Lo) ++Hi; + return *this; + } + + constexpr TWide& operator-=(const TWide& rhs) { + const auto l = Lo; + Lo -= rhs.Lo; + Hi -= rhs.Hi; + if (l < Lo) --Hi; + return *this; + } + + constexpr TWide& operator<<=(const TWide& rhs) { + if (const auto shift = size_t(rhs.Lo) % FullBitSize) { + if (shift < PartBitSize) { + Hi = TPart(Hi) << shift; + Hi |= Lo >> (PartBitSize - shift); + Lo <<= shift; + } else { + Hi = Lo << (shift - PartBitSize); + Lo = 0; + } + } + + return *this; + } + + constexpr TWide& operator>>=(const TWide& rhs) { + if (const auto shift = size_t(rhs.Lo) % FullBitSize) { + if (shift < PartBitSize) { + Lo >>= shift; + Lo |= TPart(Hi) << (PartBitSize - shift); + Hi >>= shift; + } else { + Lo = Hi >> (shift - PartBitSize); + Hi = TIsSigned::value && Hi < 0 ? ~0 : 0; + } + } + + return *this; + } + + constexpr TWide& operator*=(const TWide& rhs) { return *this = Mul(*this, rhs); } + constexpr TWide& operator/=(const TWide& rhs) { return *this = DivMod(*this, rhs).first; } + constexpr TWide& operator%=(const TWide& rhs) { return *this = DivMod(*this, rhs).second; } + + constexpr TWide operator&(const TWide& rhs) const { return TWide(*this) &= rhs; } + constexpr TWide operator|(const TWide& rhs) const { return TWide(*this) |= rhs; } + constexpr TWide operator^(const TWide& rhs) const { return TWide(*this) ^= rhs; } + constexpr TWide operator+(const TWide& rhs) const { return TWide(*this) += rhs; } + constexpr TWide operator-(const TWide& rhs) const { return TWide(*this) -= rhs; } + constexpr TWide operator*(const TWide& rhs) const { return Mul(*this, rhs); } + constexpr TWide operator/(const TWide& rhs) const { return DivMod(*this, rhs).first; } + constexpr TWide operator%(const TWide& rhs) const { return DivMod(*this, rhs).second; } + constexpr TWide operator<<(const TWide& rhs) const { return TWide(*this) <<= rhs; } + constexpr TWide operator>>(const TWide& rhs) const { return TWide(*this) >>= rhs; } + + template <typename T> + constexpr std::enable_if_t<std::is_integral<T>::value, T> operator&(const T rhs) const { return T(*this) & rhs; } + + constexpr bool operator==(const TWide& rhs) const { return std::tie(Hi, Lo) == std::tie(rhs.Hi, rhs.Lo); } + constexpr bool operator!=(const TWide& rhs) const { return std::tie(Hi, Lo) != std::tie(rhs.Hi, rhs.Lo); } + constexpr bool operator>=(const TWide& rhs) const { return std::tie(Hi, Lo) >= std::tie(rhs.Hi, rhs.Lo); } + constexpr bool operator<=(const TWide& rhs) const { return std::tie(Hi, Lo) <= std::tie(rhs.Hi, rhs.Lo); } + constexpr bool operator>(const TWide& rhs) const { return std::tie(Hi, Lo) > std::tie(rhs.Hi, rhs.Lo); } + constexpr bool operator<(const TWide& rhs) const { return std::tie(Hi, Lo) < std::tie(rhs.Hi, rhs.Lo); } + +private: + static constexpr TWide Mul(const TWide& lhs, const TWide& rhs) { + const TPart lq[] = {GetLowerQuarter(lhs.Lo), GetUpperQuarter(lhs.Lo), GetLowerQuarter(lhs.Hi), GetUpperQuarter(lhs.Hi)}; + const TPart rq[] = {GetLowerQuarter(rhs.Lo), GetUpperQuarter(rhs.Lo), GetLowerQuarter(rhs.Hi), GetUpperQuarter(rhs.Hi)}; + + const TPart prod0[] = {TPart(lq[0] * rq[0])}; + const TPart prod1[] = {TPart(lq[0] * rq[1]), TPart(lq[1] * rq[0])}; + const TPart prod2[] = {TPart(lq[0] * rq[2]), TPart(lq[1] * rq[1]), TPart(lq[2] * rq[0])}; + const TPart prod3[] = {TPart(lq[0] * rq[3]), TPart(lq[1] * rq[2]), TPart(lq[2] * rq[1]), TPart(lq[3] * rq[0])}; + + const TPart fourthQ = GetLowerQuarter(prod0[0]); + const TPart thirdQ = GetUpperQuarter(prod0[0]) + + GetLowerQuarter(prod1[0]) + GetLowerQuarter(prod1[1]); + const TPart secondQ = GetUpperQuarter(thirdQ) + + GetUpperQuarter(prod1[0]) + GetUpperQuarter(prod1[1]) + + GetLowerQuarter(prod2[0]) + GetLowerQuarter(prod2[1]) + GetLowerQuarter(prod2[2]); + const TPart firstQ = GetUpperQuarter(secondQ) + + GetUpperQuarter(prod2[0]) + GetUpperQuarter(prod2[1]) + GetUpperQuarter(prod2[2]) + + GetLowerQuarter(prod3[0]) + GetLowerQuarter(prod3[1]) + GetLowerQuarter(prod3[2]) + GetLowerQuarter(prod3[3]); + + return TWide((firstQ << QuarterBitSize) | GetLowerQuarter(secondQ), (thirdQ << QuarterBitSize) | fourthQ); + } + + static constexpr std::pair<TWide, TWide> DivMod(const TWide& lhs, const TWide& rhs) { + const bool nl = TIsSigned::value && lhs.Hi < 0; + const bool nr = TIsSigned::value && rhs.Hi < 0; + + const TUnsigned l = nl ? -lhs : +lhs, r = nr ? -rhs : +rhs; + + TUnsigned div = 0, mod = 0; + + for (auto x = l.GetBits(); x;) { + mod <<= 1; + div <<= 1; + + if (--x < PartBitSize ? l.Lo & (TPart(1U) << x) : l.Hi & (TPart(1U) << x - PartBitSize)) { + ++mod; + } + + if (mod >= r) { + mod -= r; + ++div; + } + } + + if (nl) mod = -mod; + if (nr != nl) div = -div; + + return {div, mod}; + } +}; + +template<typename T> struct THalfOf; +template<> struct THalfOf<i16> { typedef i8 Type; }; +template<> struct THalfOf<ui16> { typedef ui8 Type; }; +template<> struct THalfOf<i32> { typedef i16 Type; }; +template<> struct THalfOf<ui32> { typedef ui16 Type; }; +template<> struct THalfOf<i64> { typedef i32 Type; }; +template<> struct THalfOf<ui64> { typedef ui32 Type; }; +#ifndef _win_ +template<> struct THalfOf<i128_t> { typedef i64 Type; }; +template<> struct THalfOf<ui128_t> { typedef ui64 Type; }; +#endif +template<typename T> struct THalfOf {}; + +template<typename T> struct TPairOf; +template<> struct TPairOf<i8> { typedef i16 Type; }; +template<> struct TPairOf<ui8> { typedef ui16 Type; }; +template<> struct TPairOf<i16> { typedef i32 Type; }; +template<> struct TPairOf<ui16> { typedef ui32 Type; }; +template<> struct TPairOf<i32> { typedef i64 Type; }; +template<> struct TPairOf<ui32> { typedef ui64 Type; }; +#ifndef _win_ +template<> struct TPairOf<i64> { typedef i128_t Type; }; +template<> struct TPairOf<ui64> { typedef ui128_t Type; }; +#endif +template<typename T> struct TPairOf {}; + +} diff --git a/yql/essentials/public/ya.make b/yql/essentials/public/ya.make index 57308f1d25..182e389b07 100644 --- a/yql/essentials/public/ya.make +++ b/yql/essentials/public/ya.make @@ -1,4 +1,5 @@ RECURSE( + decimal issue result_format types |