diff --git a/include/common/tcommon.h b/include/common/tcommon.h index 96bc4a1a59..d3e58d8afe 100644 --- a/include/common/tcommon.h +++ b/include/common/tcommon.h @@ -177,7 +177,7 @@ typedef struct SColumnDataAgg { struct { uint64_t decimal128Sum[2]; uint64_t decimal128Max[2]; - uint64_t decimal128Min[2]; + uint64_t decimal128Min[2]; // TODO wjm 1. use deicmal128Sum for decimal64, 2. add overflow flag }; }; } SColumnDataAgg; diff --git a/source/common/src/tdataformat.c b/source/common/src/tdataformat.c index 5171b956c9..5643067f0c 100644 --- a/source/common/src/tdataformat.c +++ b/source/common/src/tdataformat.c @@ -4253,7 +4253,7 @@ static FORCE_INLINE void tColDataCalcSMADecimal64Type(SColData* pColData, SColum pAggs->numOfNull++; break; case 2: - CALC_DECIMAL_SUM_MAX_MIN(Decimal64, pOps, pColData, pSum, pMax, pMin); + CALC_DECIMAL_SUM_MAX_MIN(Decimal64, pOps, pColData, pSum, pMax, pMin);// TODO wjm what if overflow break; default: break; diff --git a/source/libs/decimal/inc/wideInteger.h b/source/libs/decimal/inc/wideInteger.h index 2cedb0e53e..8f1e341f91 100644 --- a/source/libs/decimal/inc/wideInteger.h +++ b/source/libs/decimal/inc/wideInteger.h @@ -27,6 +27,10 @@ typedef struct _UInt128 { uint64_t high; } _UInt128; +typedef struct Int256 { + char data[32]; +} Int256; + // TODO wjm use cmake to check if the compiler supports __int128_t #if defined(__GNUC__) || defined(__clang__) // #if 0 diff --git a/source/libs/decimal/src/decimal.c b/source/libs/decimal/src/decimal.c index f96300cf4f..603fd34f0c 100644 --- a/source/libs/decimal/src/decimal.c +++ b/source/libs/decimal/src/decimal.c @@ -170,8 +170,7 @@ static int32_t decimalVarFromStr(const char* str, int32_t len, DecimalVar* resul if (afterPoint) { break; } else { - // TODO wjm value too large - return TSDB_CODE_INVALID_DATA_FMT; + return TSDB_CODE_DECIMAL_OVERFLOW; } } else { result->precision += places; @@ -250,9 +249,6 @@ static int32_t decimalGetWhole(const DecimalType* pDec, DecimalInternalType type DECIMAL64_CLONE(pWhole, pDec); Decimal64 scaleMul = SCALE_MULTIPLIER_64[scale]; pOps->divide(pWhole, &scaleMul, 1, NULL); - if (TSDB_CODE_SUCCESS != 0) { - // TODO wjm - } pOps->abs(pWhole); } else { memcpy(pWhole, pDec, DECIMAL_GET_WORD_NUM(type) * sizeof(DecimalWord)); @@ -646,7 +642,7 @@ static void extractDecimal128Digits(const Decimal128* pDec, uint64_t* digits, in *digitNum = 0; makeUInt128(&a, DECIMAL128_HIGH_WORD(pDec), DECIMAL128_LOW_WORD(pDec)); while (!uInt128Eq(&a, &uInt128Zero)) { - uint64_t hi = a >> 64; // TODO wjm ??? + uint64_t hi = a >> 64; // TODO wjm use function, UInt128 may be a struct. uint64_t lo = a; uint64_t hiQuotient = hi / k1e18; @@ -901,6 +897,7 @@ int32_t decimalOp(EOperatorType op, const SDataType* pLeftT, const SDataType* pR switch (op) { case OP_TYPE_ADD: + // TODO wjm check overflow decimalAdd(&left, <, &right, &rt, pOutT); break; case OP_TYPE_SUB: diff --git a/source/libs/decimal/test/decimalTest.cpp b/source/libs/decimal/test/decimalTest.cpp index b4c2c2135b..60a3086f6d 100644 --- a/source/libs/decimal/test/decimalTest.cpp +++ b/source/libs/decimal/test/decimalTest.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "decimal.h" #include "tdatablock.h" @@ -124,6 +125,7 @@ struct NumericType<64> { using Type = Numeric64; static constexpr int8_t dataType = TSDB_DATA_TYPE_DECIMAL64; static constexpr int8_t maxPrec = TSDB_DECIMAL64_MAX_PRECISION; + static constexpr int8_t bytes = DECIMAL64_BYTES; }; template <> @@ -131,6 +133,7 @@ struct NumericType<128> { using Type = Numeric128; static constexpr int8_t dataType = TSDB_DATA_TYPE_DECIMAL; static constexpr int8_t maxPrec = TSDB_DECIMAL_MAX_PRECISION; + static constexpr int8_t bytes = DECIMAL128_BYTES; }; template @@ -174,15 +177,19 @@ class Numeric { throw std::string(tstrerror(code)); } } + Numeric() = default; Numeric(const Numeric& o) = default; ~Numeric() = default; Numeric& operator=(const Numeric& o) = default; - SDataType getRetType(EOperatorType op, const SDataType& lt, const SDataType& rt) const { + static SDataType getRetType(EOperatorType op, const SDataType& lt, const SDataType& rt) { SDataType ot = {0}; decimalGetRetType(<, &rt, op, &ot); return ot; } + SDataType type() const { + return {.type = NumericType::dataType, .precision = prec(), .scale = scale(), .bytes = NumericType::bytes}; + } uint8_t prec() const { return prec_; } uint8_t scale() const { return scale_; } const Type& dec() const { return dec_; } @@ -195,8 +202,8 @@ class Numeric { template Numeric binaryOp(const Numeric& r, EOperatorType op) { - SDataType lt{.type = NumericType::dataType, .precision = prec_, .scale = scale_, .bytes = BitNum}; - SDataType rt{.type = NumericType::dataType, .precision = r.prec(), .scale = r.scale(), .bytes = BitNum2}; + SDataType lt{.type = NumericType::dataType, .precision = prec_, .scale = scale_, .bytes = NumericType::bytes}; + SDataType rt{.type = NumericType::dataType, .precision = r.prec(), .scale = r.scale(), .bytes = NumericType::bytes}; SDataType ot = getRetType(op, lt, rt); Numeric out{ot.precision, ot.scale, "0"}; int32_t code = decimalOp(op, <, &rt, &ot, &dec_, &r.dec(), &out); @@ -207,12 +214,13 @@ class Numeric { template Numeric binaryOp(const T& r, EOperatorType op) { using TypeInfo = TrivialTypeInfo; - SDataType lt{.type = NumericType::dataType, .precision = prec_, .scale = scale_, .bytes = BitNum}; + SDataType lt{.type = NumericType::dataType, .precision = prec_, .scale = scale_, .bytes = NumericType::bytes}; SDataType rt{.type = TypeInfo::dataType, .precision = 0, .scale = 0, .bytes = TypeInfo::bytes}; SDataType ot = getRetType(op, lt, rt); Numeric out{ot.precision, ot.scale, "0"}; int32_t code = decimalOp(op, <, &rt, &ot, &dec_, &r, &out); - if (code != 0) throw std::overflow_error(tstrerror(code)); + if (code == TSDB_CODE_DECIMAL_OVERFLOW) throw std::overflow_error(tstrerror(code)); + if (code != 0) throw std::runtime_error(tstrerror(code)); return out; } #define DEFINE_OPERATOR(op, op_type) \ @@ -233,7 +241,13 @@ class Numeric { template \ Numeric operator op(const T & r) { \ cout << *this << " " #op " " << r << "(" << typeid(T).name() << ")" << " = "; \ - auto res = binaryOp(r, op_type); \ + Numeric res = {}; \ + try { \ + res = binaryOp(r, op_type); \ + } catch (...) { \ + cout << "Exception caught during binaryOp" << endl; \ + throw; \ + } \ cout << res << endl; \ return res; \ } @@ -306,7 +320,7 @@ class Numeric { DEFINE_OPERATOR_T(float); DEFINE_OPERATOR_T(double); - Numeric operator=(const char* str) { + Numeric& operator=(const char* str) { std::string s = str; int32_t code = 0; if (BitNum == 64) { @@ -330,7 +344,7 @@ class Numeric { } #define DEFINE_OPERATOR_EQ_T(type) \ - Numeric operator=(type v) { \ + Numeric& operator=(type v) { \ int32_t code = 0; \ if (BitNum == 64) { \ DEFINE_OPERATOR_FROM_FOR_BITNUM(type, 64); \ @@ -352,6 +366,23 @@ class Numeric { DEFINE_OPERATOR_EQ_T(bool); DEFINE_OPERATOR_EQ_T(double); DEFINE_OPERATOR_EQ_T(float); + + Numeric& operator=(const Decimal128& d) { + SDataType inputDt = {.type = TSDB_DATA_TYPE_DECIMAL, .precision = prec(), .scale = scale(), .bytes = DECIMAL128_BYTES}; + SDataType outputDt = {.type = NumericType::dataType, .precision = prec(), .scale = scale(), .bytes = NumericType::bytes}; + int32_t code = convertToDecimal(&d, &inputDt, &dec_, &outputDt); + if (code == TSDB_CODE_DECIMAL_OVERFLOW) throw std::overflow_error(tstrerror(code)); + if (code != 0) throw std::runtime_error(tstrerror(code)); + return *this; + } + Numeric& operator=(const Decimal64& d) { + SDataType inputDt = {.type = TSDB_DATA_TYPE_DECIMAL64, .precision = prec(), .scale = scale(), .bytes = DECIMAL64_BYTES}; + SDataType outputDt = {.type = NumericType::dataType, .precision = prec_, .scale = scale_, .bytes = NumericType::bytes}; + int32_t code = convertToDecimal(&d, &inputDt, &dec_, &outputDt); + if (code == TSDB_CODE_DECIMAL_OVERFLOW) throw std::overflow_error(tstrerror(code)); + if (code != 0) throw std::runtime_error(tstrerror(code)); + return *this; + } }; template @@ -897,6 +928,69 @@ TEST(decimal, randomGenerator) { } } +TEST(deicmal, decimalFromStr_all) { + // TODO test e/E +} + +#define ASSERT_OVERFLOW(op) \ + try { \ + auto res = op; \ + } catch (std::overflow_error & e) { \ + } catch (std::exception & e) { \ + FAIL(); \ + } + +TEST(decimal, op_overflow) { + // divide 0 error + Numeric<128> dec{38, 2, string(36, '9') + ".99"}; + ASSERT_OVERFLOW(dec / 0); // TODO wjm add divide by 0 error code + + // test decimal128Max + Numeric<128> max{38, 10, "0"}; + max = decimal128Max; + ASSERT_EQ(max.toString(), "9999999999999999999999999999.9999999999"); + + { + // multiply overflow + ASSERT_OVERFLOW(max * 10); + } + { + // multiply not overflow, no trim scale + Numeric<64> dec64{18, 10, "99999999.9999999999"}; + Numeric<128> dec128{19, 10, "999999999.9999999999"}; + + auto rett = Numeric<64>::getRetType(OP_TYPE_MULTI, dec64.type(), dec128.type()); + ASSERT_EQ(rett.precision, 38); + ASSERT_EQ(rett.type, TSDB_DATA_TYPE_DECIMAL); + ASSERT_EQ(rett.scale, dec64.scale() + dec128.scale()); + + auto res = dec64 * dec128; + ASSERT_EQ(res.toString(), "99999999999999999.89000000000000000001"); + + // multiply not overflow, trim scale from 20 - 19 + Numeric<128> dec128_2{20, 10, "9999999999.9999999999"}; + rett = Numeric<128>::getRetType(OP_TYPE_MULTI, dec64.type(), dec128_2.type()); + ASSERT_EQ(rett.scale, 19); + res = dec64 * dec128_2; + ASSERT_EQ(res.toString(), "999999999999999998.9900000000000000000"); + + // trim scale from 20 - 18 + dec128_2 = {21, 10, "99999999999.9999999999"}; + rett = Numeric<128>::getRetType(OP_TYPE_MULTI, dec64.type(), dec128_2.type()); + ASSERT_EQ(rett.scale, 18); + res = dec64 * dec128_2; + ASSERT_EQ(res.toString(), "9999999999999999990.000000000000000000"); + } + + + { + // multiply middle res overflow, but final res not overflow + // same scale multiply + // different scale multiply + } + +} + int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();