998 lines
34 KiB
C++
998 lines
34 KiB
C++
#include <gtest/gtest.h>
|
|
#include <iostream>
|
|
#include <array>
|
|
#include <random>
|
|
#include <memory>
|
|
|
|
#include "decimal.h"
|
|
#include "tdatablock.h"
|
|
using namespace std;
|
|
|
|
template <int N>
|
|
void printArray(const std::array<uint64_t, N>& arr) {
|
|
auto it = arr.rbegin();
|
|
for (; it != arr.rend(); ++it) {
|
|
cout << *it;
|
|
}
|
|
cout << endl;
|
|
}
|
|
|
|
template <int DIGIT_NUM>
|
|
void extractWideInteger(__int128 a) {
|
|
uint64_t k = 10;
|
|
std::array<uint64_t, 38 / DIGIT_NUM> segments{};
|
|
int seg_num = 0;
|
|
for (int i = 0; i < DIGIT_NUM; ++i) {
|
|
k *= 10;
|
|
}
|
|
|
|
while (a != 0) {
|
|
uint64_t hi = a >> 64;
|
|
uint64_t lo = a;
|
|
cout << "hi: " << hi << " lo: " << lo << endl;
|
|
uint64_t hi_quotient = hi / k;
|
|
uint64_t hi_remainder = hi % k;
|
|
// cout << "hi % 1e9: " << hi_remainder << endl;
|
|
__int128 tmp = ((__int128)hi_remainder << 64) | (__int128)lo;
|
|
uint64_t lo_remainder = tmp % k;
|
|
uint64_t lo_quotient = tmp / k;
|
|
a = (__int128)hi_quotient << 64 | (__int128)lo_quotient;
|
|
segments[seg_num++] = lo_remainder;
|
|
}
|
|
printArray<38 / DIGIT_NUM>(segments);
|
|
}
|
|
|
|
void printDecimal(const DecimalType* pDec, uint8_t type, uint8_t prec, uint8_t scale) {
|
|
char buf[64] = {0};
|
|
int32_t code = decimalToStr(pDec, type, prec, scale, buf, 64);
|
|
ASSERT_EQ(code, 0);
|
|
cout << buf;
|
|
}
|
|
|
|
__int128 generate_big_int128(uint32_t digitNum) {
|
|
__int128 a = 0;
|
|
for (int i = 0; i < digitNum + 1; ++i) {
|
|
a *= 10;
|
|
a += (i % 10);
|
|
}
|
|
return a;
|
|
}
|
|
|
|
void checkDecimal(const DecimalType* pDec, uint8_t t, uint8_t prec, uint8_t scale, const char* valExpect) {
|
|
ASSERT_TRUE(t == prec > 18 ? TSDB_DATA_TYPE_DECIMAL : TSDB_DATA_TYPE_DECIMAL64);
|
|
ASSERT_TRUE(scale <= prec);
|
|
char buf[64] = {0};
|
|
int32_t code = decimalToStr(pDec, t, prec, scale, buf, 64);
|
|
ASSERT_EQ(code, 0);
|
|
ASSERT_STREQ(buf, valExpect);
|
|
cout << "decimal" << (prec > 18 ? 128 : 64) << " " << (int32_t)prec << ":" << (int32_t)scale << " -> " << buf << endl;
|
|
}
|
|
|
|
class Numeric128;
|
|
class Numeric64 {
|
|
Decimal64 dec_;
|
|
static constexpr uint8_t WORD_NUM = WORD_NUM(Decimal64);
|
|
|
|
public:
|
|
friend class Numeric128;
|
|
Numeric64() { dec_ = {0}; }
|
|
int32_t fromStr(const string& str, uint8_t prec, uint8_t scale) {
|
|
return decimal64FromStr(str.c_str(), str.size(), prec, scale, &dec_);
|
|
}
|
|
Numeric64& operator+=(const Numeric64& r) {
|
|
getOps()->add(&dec_, &r.dec_, WORD_NUM);
|
|
return *this;
|
|
}
|
|
// Numeric64& operator+=(const Numeric128& r);
|
|
|
|
bool operator==(const Numeric64& r) const { return getOps()->eq(&dec_, &r.dec_, WORD_NUM); }
|
|
Numeric64& operator=(const Numeric64& r);
|
|
Numeric64& operator=(const Numeric128& r);
|
|
|
|
static SDecimalOps* getOps() { return getDecimalOps(TSDB_DATA_TYPE_DECIMAL64); }
|
|
};
|
|
|
|
class Numeric128 {
|
|
Decimal128 dec_;
|
|
static constexpr uint8_t WORD_NUM = WORD_NUM(Decimal128);
|
|
|
|
public:
|
|
friend Numeric64;
|
|
Numeric128() { dec_ = {0}; }
|
|
Numeric128(const Numeric128& r) = default;
|
|
int32_t fromStr(const string& str, uint8_t prec, uint8_t scale) {
|
|
return decimal128FromStr(str.c_str(), str.size(), prec, scale, &dec_);
|
|
}
|
|
Numeric128& operator+=(const Numeric128& r) { return *this; }
|
|
Numeric128& operator+=(const Numeric64& r) {
|
|
getOps()->add(&dec_, &r.dec_, Numeric64::WORD_NUM);
|
|
return *this;
|
|
}
|
|
|
|
static SDecimalOps* getOps() { return getDecimalOps(TSDB_DATA_TYPE_DECIMAL); }
|
|
};
|
|
|
|
Numeric64& Numeric64::operator=(const Numeric64& r) {
|
|
dec_ = r.dec_;
|
|
return *this;
|
|
}
|
|
|
|
template <int BitNum>
|
|
struct NumericType {};
|
|
|
|
template <>
|
|
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 <>
|
|
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 <typename T>
|
|
struct TrivialTypeInfo {
|
|
using TrivialType = T;
|
|
};
|
|
|
|
#define DEFINE_TRIVIAL_TYPE_HELPER(type, tsdb_type) \
|
|
template <> \
|
|
struct TrivialTypeInfo<type> { \
|
|
static constexpr int8_t dataType = tsdb_type; \
|
|
static constexpr int32_t bytes = sizeof(type); \
|
|
}
|
|
|
|
DEFINE_TRIVIAL_TYPE_HELPER(int8_t, TSDB_DATA_TYPE_TINYINT);
|
|
DEFINE_TRIVIAL_TYPE_HELPER(uint8_t, TSDB_DATA_TYPE_UTINYINT);
|
|
DEFINE_TRIVIAL_TYPE_HELPER(int16_t, TSDB_DATA_TYPE_SMALLINT);
|
|
DEFINE_TRIVIAL_TYPE_HELPER(uint16_t, TSDB_DATA_TYPE_USMALLINT);
|
|
DEFINE_TRIVIAL_TYPE_HELPER(int32_t, TSDB_DATA_TYPE_INT);
|
|
DEFINE_TRIVIAL_TYPE_HELPER(uint32_t, TSDB_DATA_TYPE_UINT);
|
|
DEFINE_TRIVIAL_TYPE_HELPER(int64_t, TSDB_DATA_TYPE_BIGINT);
|
|
DEFINE_TRIVIAL_TYPE_HELPER(uint64_t, TSDB_DATA_TYPE_UBIGINT);
|
|
DEFINE_TRIVIAL_TYPE_HELPER(float, TSDB_DATA_TYPE_FLOAT);
|
|
DEFINE_TRIVIAL_TYPE_HELPER(double, TSDB_DATA_TYPE_DOUBLE);
|
|
DEFINE_TRIVIAL_TYPE_HELPER(bool, TSDB_DATA_TYPE_BOOL);
|
|
|
|
template <int BitNum>
|
|
class Numeric {
|
|
static_assert(BitNum == 64 || BitNum == 128, "only support Numeric64 and Numeric128");
|
|
using Type = typename NumericType<BitNum>::Type;
|
|
Type dec_;
|
|
uint8_t prec_;
|
|
uint8_t scale_;
|
|
|
|
public:
|
|
Numeric(uint8_t prec, uint8_t scale, const std::string& str) : prec_(prec), scale_(scale) {
|
|
if (prec > NumericType<BitNum>::maxPrec) throw std::string("prec too big") + std::to_string(prec);
|
|
int32_t code = dec_.fromStr(str, prec, scale) != 0;
|
|
if (code != 0) {
|
|
cout << "failed to init decimal from str: " << str << endl;
|
|
throw std::string(tstrerror(code));
|
|
}
|
|
}
|
|
Numeric() = default;
|
|
Numeric(const Numeric& o) = default;
|
|
~Numeric() = default;
|
|
Numeric& operator=(const Numeric& o) = default;
|
|
|
|
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<BitNum>::dataType, .precision = prec(), .scale = scale(), .bytes = NumericType<BitNum>::bytes};
|
|
}
|
|
uint8_t prec() const { return prec_; }
|
|
uint8_t scale() const { return scale_; }
|
|
const Type& dec() const { return dec_; }
|
|
|
|
template <int BitNum2>
|
|
Numeric& binaryOp(const Numeric<BitNum2>& r, EOperatorType op) {
|
|
auto out = binaryOp<BitNum2, BitNum>(r, op);
|
|
return *this = out;
|
|
}
|
|
|
|
template <int BitNum2, int BitNumO>
|
|
Numeric<BitNumO> binaryOp(const Numeric<BitNum2>& r, EOperatorType op) {
|
|
SDataType lt{.type = NumericType<BitNum>::dataType, .precision = prec_, .scale = scale_, .bytes = NumericType<BitNum>::bytes};
|
|
SDataType rt{.type = NumericType<BitNum2>::dataType, .precision = r.prec(), .scale = r.scale(), .bytes = NumericType<BitNum2>::bytes};
|
|
SDataType ot = getRetType(op, lt, rt);
|
|
Numeric<BitNumO> out{ot.precision, ot.scale, "0"};
|
|
int32_t code = decimalOp(op, <, &rt, &ot, &dec_, &r.dec(), &out);
|
|
if (code != 0) throw std::overflow_error(tstrerror(code));
|
|
return out;
|
|
}
|
|
|
|
template <int BitNumO, typename T>
|
|
Numeric<BitNumO> binaryOp(const T& r, EOperatorType op) {
|
|
using TypeInfo = TrivialTypeInfo<T>;
|
|
SDataType lt{.type = NumericType<BitNum>::dataType, .precision = prec_, .scale = scale_, .bytes = NumericType<BitNum>::bytes};
|
|
SDataType rt{.type = TypeInfo::dataType, .precision = 0, .scale = 0, .bytes = TypeInfo::bytes};
|
|
SDataType ot = getRetType(op, lt, rt);
|
|
Numeric<BitNumO> out{ot.precision, ot.scale, "0"};
|
|
int32_t code = decimalOp(op, <, &rt, &ot, &dec_, &r, &out);
|
|
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) \
|
|
template <int BitNum2, int BitNumO = 128> \
|
|
Numeric<BitNumO> operator op(const Numeric<BitNum2>& r) { \
|
|
cout << *this << " " #op " " << r << " = "; \
|
|
auto res = binaryOp<BitNum2, BitNumO>(r, op_type); \
|
|
cout << res << endl; \
|
|
return res; \
|
|
}
|
|
|
|
DEFINE_OPERATOR(+, OP_TYPE_ADD);
|
|
DEFINE_OPERATOR(-, OP_TYPE_SUB);
|
|
DEFINE_OPERATOR(*, OP_TYPE_MULTI);
|
|
DEFINE_OPERATOR(/, OP_TYPE_DIV);
|
|
|
|
#define DEFINE_TYPE_OP(op, op_type) \
|
|
template <typename T, int BitNumO = 128> \
|
|
Numeric<BitNumO> operator op(const T & r) { \
|
|
cout << *this << " " #op " " << r << "(" << typeid(T).name() << ")" << " = "; \
|
|
Numeric<BitNumO> res = {}; \
|
|
try { \
|
|
res = binaryOp<BitNumO, T>(r, op_type); \
|
|
} catch (...) { \
|
|
cout << "Exception caught during binaryOp" << endl; \
|
|
throw; \
|
|
} \
|
|
cout << res << endl; \
|
|
return res; \
|
|
}
|
|
DEFINE_TYPE_OP(+, OP_TYPE_ADD);
|
|
DEFINE_TYPE_OP(-, OP_TYPE_SUB);
|
|
DEFINE_TYPE_OP(*, OP_TYPE_MULTI);
|
|
DEFINE_TYPE_OP(/, OP_TYPE_DIV);
|
|
|
|
#define DEFINE_REAL_OP(op) \
|
|
double operator op(double v) { \
|
|
if (BitNum == 128) \
|
|
return TEST_decimal128ToDouble((Decimal128*)&dec_, prec(), scale()) / v; \
|
|
else if (BitNum == 64) \
|
|
return TEST_decimal64ToDouble((Decimal64*)&dec_, prec(), scale()) / v; \
|
|
return 0; \
|
|
}
|
|
DEFINE_REAL_OP(+);
|
|
DEFINE_REAL_OP(-);
|
|
DEFINE_REAL_OP(*);
|
|
DEFINE_REAL_OP(/);
|
|
|
|
template <int BitNum2>
|
|
Numeric& operator+=(const Numeric<BitNum2>& r) {
|
|
return binaryOp(r, OP_TYPE_ADD);
|
|
}
|
|
|
|
template <int BitNum2>
|
|
bool operator==(const Numeric<BitNum2>& r) {
|
|
return binaryOp(r, OP_TYPE_EQUAL);
|
|
}
|
|
std::string toString() const {
|
|
char buf[64] = {0};
|
|
int32_t code = decimalToStr(&dec_, NumericType<BitNum>::dataType, prec(), scale(), buf, 64);
|
|
if (code != 0) throw std::string(tstrerror(code));
|
|
return {buf};
|
|
}
|
|
|
|
std::string toStringTrimTailingZeros() const {
|
|
auto ret = toString();
|
|
int32_t sizeToRemove = 0;
|
|
auto it = ret.rbegin();
|
|
for (; it != ret.rend(); ++it) {
|
|
if (*it == '0') {
|
|
++sizeToRemove;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (ret.size() - sizeToRemove > 0) ret.resize(ret.size() - sizeToRemove);
|
|
return ret;
|
|
}
|
|
#define DEFINE_OPERATOR_T(type) \
|
|
operator type() { \
|
|
if (BitNum == 64) { \
|
|
return type##FromDecimal64(&dec_, prec(), scale()); \
|
|
} else if (BitNum == 128) { \
|
|
return type##FromDecimal128(&dec_, prec(), scale()); \
|
|
} \
|
|
return 0; \
|
|
}
|
|
DEFINE_OPERATOR_T(bool);
|
|
DEFINE_OPERATOR_T(int8_t);
|
|
DEFINE_OPERATOR_T(uint8_t);
|
|
DEFINE_OPERATOR_T(int16_t);
|
|
DEFINE_OPERATOR_T(uint16_t);
|
|
DEFINE_OPERATOR_T(int32_t);
|
|
DEFINE_OPERATOR_T(uint32_t);
|
|
DEFINE_OPERATOR_T(int64_t);
|
|
DEFINE_OPERATOR_T(uint64_t);
|
|
DEFINE_OPERATOR_T(float);
|
|
DEFINE_OPERATOR_T(double);
|
|
|
|
Numeric& operator=(const char* str) {
|
|
std::string s = str;
|
|
int32_t code = 0;
|
|
if (BitNum == 64) {
|
|
code = decimal64FromStr(s.c_str(), s.size(), prec(), scale(), (Decimal64*)&dec_);
|
|
} else if (BitNum == 128) {
|
|
code = decimal128FromStr(s.c_str(), s.size(), prec(), scale(), (Decimal128*)&dec_);
|
|
}
|
|
if (TSDB_CODE_SUCCESS != code) {
|
|
throw std::string("failed to convert str to decimal64: ") + s + " " + tstrerror(code);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
#define DEFINE_OPERATOR_FROM_FOR_BITNUM(type, BitNum) \
|
|
if (std::is_floating_point<type>::value) { \
|
|
code = TEST_decimal##BitNum##From_double((Decimal##BitNum*)&dec_, prec(), scale(), v); \
|
|
} else if (std::is_signed<type>::value) { \
|
|
code = TEST_decimal##BitNum##From_int64_t((Decimal##BitNum*)&dec_, prec(), scale(), v); \
|
|
} else if (std::is_unsigned<type>::value) { \
|
|
code = TEST_decimal##BitNum##From_uint64_t((Decimal##BitNum*)&dec_, prec(), scale(), v); \
|
|
}
|
|
|
|
#define DEFINE_OPERATOR_EQ_T(type) \
|
|
Numeric& operator=(type v) { \
|
|
int32_t code = 0; \
|
|
if (BitNum == 64) { \
|
|
DEFINE_OPERATOR_FROM_FOR_BITNUM(type, 64); \
|
|
} else if (BitNum == 128) { \
|
|
DEFINE_OPERATOR_FROM_FOR_BITNUM(type, 128); \
|
|
} \
|
|
return *this; \
|
|
}
|
|
DEFINE_OPERATOR_EQ_T(int64_t);
|
|
DEFINE_OPERATOR_EQ_T(int32_t);
|
|
DEFINE_OPERATOR_EQ_T(int16_t);
|
|
DEFINE_OPERATOR_EQ_T(int8_t);
|
|
|
|
DEFINE_OPERATOR_EQ_T(uint64_t);
|
|
DEFINE_OPERATOR_EQ_T(uint32_t);
|
|
DEFINE_OPERATOR_EQ_T(uint16_t);
|
|
DEFINE_OPERATOR_EQ_T(uint8_t);
|
|
|
|
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<BitNum>::dataType, .precision = prec(), .scale = scale(), .bytes = NumericType<BitNum>::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<BitNum>::dataType, .precision = prec_, .scale = scale_, .bytes = NumericType<BitNum>::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 <int BitNum>
|
|
ostream& operator<<(ostream& os, const Numeric<BitNum>& n) {
|
|
os << n.toString() << "(" << (int32_t)n.prec() << ":" << (int32_t)n.scale() << ")";
|
|
return os;
|
|
}
|
|
|
|
TEST(decimal, numeric) {
|
|
Numeric<64> dec{10, 4, "123.456"};
|
|
Numeric<64> dec2{18, 10, "123456.123123"};
|
|
auto o = dec + dec2;
|
|
ASSERT_EQ(o.toString(), "123579.5791230000");
|
|
|
|
Numeric<128> dec128{37, 10, "123456789012300.09876543"};
|
|
o = dec + dec128;
|
|
ASSERT_EQ(o.toStringTrimTailingZeros(), "123456789012423.55476543");
|
|
ASSERT_EQ(o.toString(), "123456789012423.5547654300");
|
|
|
|
auto os = o - dec;
|
|
ASSERT_EQ(os.toStringTrimTailingZeros(), dec128.toStringTrimTailingZeros());
|
|
|
|
auto os2 = o - dec128;
|
|
ASSERT_EQ(os2.toStringTrimTailingZeros(), dec.toStringTrimTailingZeros());
|
|
|
|
os = dec * dec2;
|
|
ASSERT_EQ(os.toStringTrimTailingZeros(), "15241399.136273088");
|
|
ASSERT_EQ(os.toString(), "15241399.13627308800000");
|
|
|
|
os = dec * dec128;
|
|
ASSERT_EQ(os.toStringTrimTailingZeros(), "15241481344302520.993184");
|
|
ASSERT_EQ(os.toString(), "15241481344302520.993184");
|
|
|
|
os2 = os / dec128;
|
|
ASSERT_EQ(os2.toStringTrimTailingZeros(), "123.456");
|
|
ASSERT_EQ(os2.toString(), "123.456000");
|
|
|
|
os = dec2 / dec;
|
|
ASSERT_EQ(os.toString(), "1000.000997302682737169518");
|
|
|
|
int32_t a = 123;
|
|
os = dec + a;
|
|
ASSERT_EQ(os.toString(), "246.4560");
|
|
|
|
os = dec * a;
|
|
ASSERT_EQ(os.toString(), "15185.0880");
|
|
|
|
os = dec / 2;
|
|
ASSERT_EQ(os.toStringTrimTailingZeros(), "61.728");
|
|
|
|
os = dec2 / 2;
|
|
ASSERT_EQ(os.toStringTrimTailingZeros(), "61728.0615615");
|
|
|
|
os = dec128 / 2;
|
|
ASSERT_EQ(os.toStringTrimTailingZeros(), "61728394506150.049382715");
|
|
|
|
auto dec3 = Numeric<64>(10, 2, "171154.38");
|
|
os = dec3 / 2;
|
|
ASSERT_EQ(os.toStringTrimTailingZeros(), "85577.19");
|
|
|
|
auto dec4 = Numeric<64>(10, 5, "1.23456");
|
|
os = dec4 / 2;
|
|
ASSERT_EQ(os.toStringTrimTailingZeros(), "0.61728");
|
|
|
|
os = dec4 / 123123123;
|
|
ASSERT_EQ(os.toStringTrimTailingZeros(), "0.0000000100270361");
|
|
|
|
os = dec4 / (int64_t)123123123;
|
|
ASSERT_EQ(os.toStringTrimTailingZeros(), "0.0000000100270361075880117");
|
|
|
|
double dv = dec4 / 123123.123;
|
|
|
|
Numeric<128> max{38, 0, "99999999999999999999999999999999999999.000"};
|
|
ASSERT_EQ(max.toString(), "99999999999999999999999999999999999999");
|
|
Numeric<128> zero{38, 0, "0"};
|
|
auto min = zero - max;
|
|
ASSERT_EQ(min.toString(), "-99999999999999999999999999999999999999");
|
|
|
|
dec = 123.456;
|
|
ASSERT_EQ(dec.toString(), "123.4560");
|
|
|
|
dec = 47563.36;
|
|
dec128 = 0;
|
|
o = dec128 + dec; // (37, 10) + (10, 4) = (38, 10)
|
|
ASSERT_EQ(o.toString(), "47563.3600000000");
|
|
dec = 3749.00;
|
|
o = o + dec; // (38, 10) + (10, 4) = (38, 9)
|
|
ASSERT_EQ(o.toString(), "51312.360000000");
|
|
}
|
|
|
|
TEST(decimal, decimalFromType) {
|
|
Numeric<128> dec1{20, 4, "0"};
|
|
dec1 = 123.123;
|
|
ASSERT_EQ(dec1.toString(), "123.1230");
|
|
dec1 = (float)123.123;
|
|
ASSERT_EQ(dec1.toString(), "123.1230");
|
|
dec1 = (int64_t)-9999999;
|
|
ASSERT_EQ(dec1.toString(), "-9999999.0000");
|
|
dec1 = "99.99999";
|
|
ASSERT_EQ(dec1.toString(), "99.9999");
|
|
}
|
|
|
|
TEST(decimal, typeFromDecimal) {
|
|
Numeric<128> dec1{18, 4, "1234"};
|
|
Numeric<64> dec2{18, 4, "1234"};
|
|
int64_t intv = dec1;
|
|
uint64_t uintv = dec1;
|
|
double doublev = dec1;
|
|
ASSERT_EQ(intv, 1234);
|
|
ASSERT_EQ(uintv, 1234);
|
|
ASSERT_EQ(doublev, 1234);
|
|
doublev = dec2;
|
|
ASSERT_EQ(doublev, 1234);
|
|
intv = dec1 = "123.43";
|
|
uintv = dec1;
|
|
doublev = dec1;
|
|
ASSERT_EQ(intv, 123);
|
|
ASSERT_EQ(uintv, 123);
|
|
ASSERT_EQ(doublev, 123.43);
|
|
doublev = dec2 = "123.54";
|
|
ASSERT_EQ(doublev, 123.54);
|
|
intv = dec1 = "123.66";
|
|
uintv = dec1;
|
|
doublev = dec1;
|
|
ASSERT_EQ(intv, 124);
|
|
ASSERT_EQ(uintv, 124);
|
|
ASSERT_EQ(doublev, 123.66);
|
|
|
|
intv = dec1 = "-123.44";
|
|
uintv = dec1;
|
|
doublev = dec1;
|
|
ASSERT_EQ(intv, -123);
|
|
ASSERT_EQ(uintv, 0);
|
|
ASSERT_EQ(doublev, -123.44);
|
|
intv = dec1 = "-123.99";
|
|
uintv = dec1;
|
|
doublev = dec1;
|
|
ASSERT_EQ(intv, -124);
|
|
ASSERT_EQ(uintv, 0);
|
|
ASSERT_EQ(doublev, -123.99);
|
|
|
|
bool boolv = false;
|
|
boolv = dec1;
|
|
ASSERT_TRUE(boolv);
|
|
boolv = dec1 = "0";
|
|
ASSERT_FALSE(boolv);
|
|
}
|
|
|
|
// TODO wjm TEST where decimal column in (...)
|
|
// TEST same decimal type with different scale doing comparing or operations
|
|
// TEST case when select common type
|
|
|
|
TEST(decimal, a) {
|
|
__int128 a = generate_big_int128(37);
|
|
extractWideInteger<9>(a);
|
|
ASSERT_TRUE(1);
|
|
}
|
|
|
|
TEST(decimal128, to_string) {
|
|
__int128 i = generate_big_int128(37);
|
|
int64_t hi = i >> 64;
|
|
uint64_t lo = i;
|
|
Decimal128 d;
|
|
makeDecimal128(&d, hi, lo);
|
|
char buf[64] = {0};
|
|
decimalToStr(d.words, TSDB_DATA_TYPE_DECIMAL, 38, 10, buf, 64);
|
|
ASSERT_STREQ(buf, "123456789012345678901234567.8901234567");
|
|
|
|
buf[0] = '\0';
|
|
decimalToStr(d.words, TSDB_DATA_TYPE_DECIMAL, 38, 9, buf, 64);
|
|
ASSERT_STREQ(buf, "1234567890123456789012345678.901234567");
|
|
}
|
|
|
|
TEST(decimal128, divide) {
|
|
__int128 i = generate_big_int128(15);
|
|
int64_t hi = i >> 64;
|
|
uint64_t lo = i;
|
|
Decimal128 d;
|
|
makeDecimal128(&d, hi, lo);
|
|
|
|
Decimal128 d2 = {0};
|
|
makeDecimal128(&d2, 0, 12345678);
|
|
|
|
auto ops = getDecimalOps(TSDB_DATA_TYPE_DECIMAL);
|
|
Decimal128 remainder = {0};
|
|
int8_t precision1 = 38, scale1 = 5, precision2 = 10, scale2 = 2;
|
|
int8_t out_scale = 25;
|
|
int8_t out_precision = std::min(precision1 - scale1 + scale2 + out_scale, 38);
|
|
int8_t delta_scale = out_scale + scale2 - scale1;
|
|
printDecimal(&d, TSDB_DATA_TYPE_DECIMAL, precision1, scale1);
|
|
__int128 a = 1;
|
|
while (delta_scale-- > 0) a *= 10;
|
|
Decimal128 multiplier = {0};
|
|
makeDecimal128(&multiplier, a >> 64, a);
|
|
ops->multiply(d.words, multiplier.words, 2);
|
|
cout << " / ";
|
|
printDecimal(&d2, TSDB_DATA_TYPE_DECIMAL, precision2, scale2);
|
|
cout << " = ";
|
|
ops->divide(d.words, d2.words, 2, remainder.words);
|
|
printDecimal(&d, TSDB_DATA_TYPE_DECIMAL, out_precision, out_scale);
|
|
}
|
|
|
|
TEST(decimal, api_taos_fetch_rows) {
|
|
const char* host = "127.0.0.1";
|
|
const char* user = "root";
|
|
const char* passwd = "taosdata";
|
|
const char* db = "test_api";
|
|
const char* create_tb = "create table if not exists test_api.nt(ts timestamp, c1 decimal(10, 2), c2 decimal(38, 10))";
|
|
const char* sql = "select c1, c2 from test_api.nt";
|
|
const char* sql_insert = "insert into test_api.nt values(now, 123456.123, 98472981092.1209111)";
|
|
|
|
TAOS* pTaos = taos_connect(host, user, passwd, NULL, 0);
|
|
if (!pTaos) {
|
|
cout << "taos connect failed: " << host << " " << taos_errstr(NULL);
|
|
FAIL();
|
|
}
|
|
|
|
auto* res = taos_query(pTaos, (std::string("create database if not exists ") + db).c_str());
|
|
taos_free_result(res);
|
|
res = taos_query(pTaos, create_tb);
|
|
taos_free_result(res);
|
|
res = taos_query(pTaos, sql_insert);
|
|
taos_free_result(res);
|
|
|
|
res = taos_query(pTaos, sql);
|
|
int32_t code = taos_errno(res);
|
|
if (code != 0) {
|
|
cout << "taos_query with sql: " << sql << " failed: " << taos_errstr(res);
|
|
FAIL();
|
|
}
|
|
|
|
char buf[1024] = {0};
|
|
auto* fields = taos_fetch_fields(res);
|
|
auto fieldNum = taos_field_count(res);
|
|
while (auto row = taos_fetch_row(res)) {
|
|
taos_print_row(buf, row, fields, fieldNum);
|
|
cout << buf << endl;
|
|
}
|
|
taos_free_result(res);
|
|
|
|
res = taos_query(pTaos, sql);
|
|
code = taos_errno(res);
|
|
if (code != 0) {
|
|
cout << "taos_query with sql: " << sql << " failed: " << taos_errstr(res);
|
|
taos_free_result(res);
|
|
FAIL();
|
|
}
|
|
|
|
void* pData = NULL;
|
|
int32_t numOfRows = 0;
|
|
code = taos_fetch_raw_block(res, &numOfRows, &pData);
|
|
if (code != 0) {
|
|
cout << "taos_query with sql: " << sql << " failed: " << taos_errstr(res);
|
|
FAIL();
|
|
}
|
|
if (numOfRows > 0) {
|
|
int32_t version = *(int32_t*)pData;
|
|
ASSERT_EQ(version, BLOCK_VERSION_1);
|
|
int32_t rows = *(int32_t*)((char*)pData + 4 + 4);
|
|
int32_t colNum = *(int32_t*)((char*)pData + 4 + 4 + 4);
|
|
int32_t bytes_skip = 4 + 4 + 4 + 4 + 4 + 8;
|
|
char* p = (char*)pData + bytes_skip;
|
|
// col1
|
|
int8_t t = *(int8_t*)p;
|
|
int32_t type_mod = *(int32_t*)(p + 1);
|
|
|
|
ASSERT_EQ(t, TSDB_DATA_TYPE_DECIMAL64);
|
|
auto check_type_mod = [](char* pStart, uint8_t prec, uint8_t scale, int32_t bytes) {
|
|
ASSERT_EQ(*pStart, bytes);
|
|
ASSERT_EQ(*(pStart + 2), prec);
|
|
ASSERT_EQ(*(pStart + 3), scale);
|
|
};
|
|
check_type_mod(p + 1, 10, 2, 8);
|
|
|
|
// col2
|
|
p += 5;
|
|
t = *(int8_t*)p;
|
|
type_mod = *(int32_t*)(p + 1);
|
|
check_type_mod(p + 1, 38, 10, 16);
|
|
|
|
p = p + 5 + BitmapLen(numOfRows) + colNum * 4;
|
|
int64_t row1Val = *(int64_t*)p;
|
|
ASSERT_EQ(row1Val, 12345612);
|
|
}
|
|
taos_free_result(res);
|
|
|
|
taos_close(pTaos);
|
|
taos_cleanup();
|
|
}
|
|
|
|
TEST(decimal, conversion) {
|
|
// convert uint8 to decimal
|
|
char buf[64] = {0};
|
|
int8_t i8 = 22;
|
|
SDataType inputType = {.type = TSDB_DATA_TYPE_TINYINT, .bytes = 1};
|
|
uint8_t prec = 10, scale = 2;
|
|
SDataType decType = {.type = TSDB_DATA_TYPE_DECIMAL64, .precision = prec, .scale = scale, .bytes = 8};
|
|
Decimal64 dec64 = {0};
|
|
int32_t code = convertToDecimal(&i8, &inputType, &dec64, &decType);
|
|
ASSERT_TRUE(code == 0);
|
|
cout << "convert uint8: " << (int32_t)i8 << " to ";
|
|
checkDecimal(&dec64, TSDB_DATA_TYPE_DECIMAL64, prec, scale, "22.00");
|
|
|
|
Decimal128 dec128 = {0};
|
|
decType.type = TSDB_DATA_TYPE_DECIMAL;
|
|
decType.precision = 38;
|
|
decType.scale = 10;
|
|
decType.bytes = 16;
|
|
code = convertToDecimal(&i8, &inputType, &dec128, &decType);
|
|
ASSERT_TRUE(code == 0);
|
|
cout << "convert uint8: " << (int32_t)i8 << " to ";
|
|
checkDecimal(&dec128, TSDB_DATA_TYPE_DECIMAL, decType.precision, decType.scale, "22.0000000000");
|
|
|
|
char inputBuf[64] = "123.000000000000000000000000000000001";
|
|
code = decimal128FromStr(inputBuf, strlen(inputBuf), 38, 35, &dec128);
|
|
ASSERT_EQ(code, 0);
|
|
checkDecimal(&dec128, TSDB_DATA_TYPE_DECIMAL, 38, 35, "123.00000000000000000000000000000000100");
|
|
|
|
inputType.type = TSDB_DATA_TYPE_DECIMAL64;
|
|
inputType.precision = prec;
|
|
inputType.scale = scale;
|
|
code = convertToDecimal(&dec64, &inputType, &dec128, &decType);
|
|
ASSERT_EQ(code, 0);
|
|
checkDecimal(&dec128, TSDB_DATA_TYPE_DECIMAL, 38, 10, "22.0000000000");
|
|
}
|
|
|
|
static constexpr uint64_t k1E16 = 10000000000000000LL;
|
|
|
|
TEST(decimal, decimalFromStr) {
|
|
char inputBuf[64] = "123.000000000000000000000000000000001";
|
|
Decimal128 dec128 = {0};
|
|
int32_t code = decimal128FromStr(inputBuf, strlen(inputBuf), 38, 35, &dec128);
|
|
ASSERT_EQ(code, 0);
|
|
__int128 res = decimal128ToInt128(&dec128);
|
|
__int128 resExpect = 123;
|
|
resExpect *= k1E16;
|
|
resExpect *= k1E16;
|
|
resExpect *= 10;
|
|
resExpect += 1;
|
|
resExpect *= 100;
|
|
ASSERT_EQ(res, resExpect);
|
|
|
|
char buf[64] = "999.999";
|
|
Decimal64 dec64 = {0};
|
|
code = decimal64FromStr(buf, strlen(buf), 6, 3, &dec64);
|
|
ASSERT_EQ(code, 0);
|
|
ASSERT_EQ(999999, DECIMAL64_GET_VALUE(&dec64));
|
|
}
|
|
|
|
TEST(decimal, toStr) {
|
|
Decimal64 dec = {0};
|
|
char buf[64] = {0};
|
|
int32_t code = decimalToStr(&dec, TSDB_DATA_TYPE_DECIMAL64, 10, 2, buf, 64);
|
|
ASSERT_EQ(code, 0);
|
|
ASSERT_STREQ(buf, "0");
|
|
|
|
Decimal128 dec128 = {0};
|
|
code = decimalToStr(&dec128, TSDB_DATA_TYPE_DECIMAL, 38, 10, buf, 64);
|
|
ASSERT_EQ(code, 0);
|
|
ASSERT_STREQ(buf, "0");
|
|
}
|
|
|
|
SDataType getDecimalType(uint8_t prec, uint8_t scale) {
|
|
if (prec > TSDB_DECIMAL_MAX_PRECISION) throw std::string("invalid prec: ") + std::to_string(prec);
|
|
uint8_t type = decimalTypeFromPrecision(prec);
|
|
return {.type = type, .precision = prec, .scale = scale, .bytes = tDataTypes[type].bytes};
|
|
}
|
|
|
|
bool operator==(const SDataType& lt, const SDataType& rt) {
|
|
return lt.type == rt.type && lt.precision == rt.precision && lt.scale == rt.scale && lt.bytes == rt.bytes;
|
|
}
|
|
|
|
TEST(decimal, decimalOpRetType) {
|
|
EOperatorType op = OP_TYPE_ADD;
|
|
auto ta = getDecimalType(10, 2);
|
|
auto tb = getDecimalType(10, 2);
|
|
SDataType tc{}, tExpect = {.type = TSDB_DATA_TYPE_DECIMAL, .precision = 11, .scale = 2, .bytes = sizeof(Decimal)};
|
|
int32_t code = decimalGetRetType(&ta, &tb, op, &tc);
|
|
ASSERT_EQ(code, 0);
|
|
ASSERT_EQ(tExpect, tc);
|
|
|
|
ta.bytes = 8;
|
|
ta.type = TSDB_DATA_TYPE_TIMESTAMP;
|
|
|
|
code = decimalGetRetType(&ta, &tb, op, &tc);
|
|
ASSERT_EQ(code, 0);
|
|
tExpect.type = TSDB_DATA_TYPE_DECIMAL;
|
|
tExpect.precision = 22;
|
|
tExpect.scale = 2;
|
|
tExpect.bytes = sizeof(Decimal);
|
|
ASSERT_EQ(tExpect, tc);
|
|
|
|
ta.bytes = 8;
|
|
ta.type = TSDB_DATA_TYPE_DOUBLE;
|
|
tc = {0};
|
|
code = decimalGetRetType(&ta, &tb, op, &tc);
|
|
ASSERT_EQ(code, 0);
|
|
tExpect.type = TSDB_DATA_TYPE_DOUBLE;
|
|
tExpect.precision = 0;
|
|
tExpect.scale = 0;
|
|
tExpect.bytes = 8;
|
|
ASSERT_EQ(tExpect, tc);
|
|
|
|
op = OP_TYPE_DIV;
|
|
ta = getDecimalType(10, 2);
|
|
tb = getDecimalType(10, 2);
|
|
tExpect.type = TSDB_DATA_TYPE_DECIMAL;
|
|
tExpect.precision = 23;
|
|
tExpect.scale = 13;
|
|
tExpect.bytes = sizeof(Decimal);
|
|
code = decimalGetRetType(&ta, &tb, op, &tc);
|
|
}
|
|
|
|
TEST(decimal, op) {
|
|
const char * stra = "123.99", *strb = "456.12";
|
|
EOperatorType op = OP_TYPE_ADD;
|
|
auto ta = getDecimalType(10, 2);
|
|
Decimal64 a = {0};
|
|
int32_t code = decimal64FromStr(stra, strlen(stra), ta.precision, ta.scale, &a);
|
|
ASSERT_EQ(code, 0);
|
|
|
|
auto tb = getDecimalType(10, 2);
|
|
Decimal64 b{0};
|
|
code = decimal64FromStr(strb, strlen(strb), tb.precision, tb.scale, &b);
|
|
ASSERT_EQ(code, 0);
|
|
|
|
SDataType tc{}, tExpect{.type = TSDB_DATA_TYPE_DECIMAL, .precision = 11, .scale = 2, .bytes = sizeof(Decimal)};
|
|
code = decimalGetRetType(&ta, &tb, op, &tc);
|
|
ASSERT_EQ(code, 0);
|
|
ASSERT_EQ(tc, tExpect);
|
|
Decimal res{};
|
|
code = decimalOp(op, &ta, &tb, &tc, &a, &b, &res);
|
|
ASSERT_EQ(code, 0);
|
|
|
|
checkDecimal(&res, TSDB_DATA_TYPE_DECIMAL, tc.precision, tc.scale, "580.11");
|
|
|
|
a = {1234567890};
|
|
b = {9876543210};
|
|
ta = getDecimalType(18, 5);
|
|
tb = getDecimalType(15, 3);
|
|
code = decimalGetRetType(&ta, &tb, op, &tc);
|
|
ASSERT_EQ(code, 0);
|
|
tExpect.precision = 19;
|
|
tExpect.scale = 5;
|
|
tExpect.type = TSDB_DATA_TYPE_DECIMAL;
|
|
tExpect.bytes = sizeof(Decimal128);
|
|
ASSERT_EQ(tExpect, tc);
|
|
Decimal128 res128 = {0};
|
|
code = decimalOp(op, &ta, &tb, &tc, &a, &b, &res128);
|
|
ASSERT_EQ(code, 0);
|
|
checkDecimal(&res128, 0, tExpect.precision, tExpect.scale, "9888888.88890");
|
|
}
|
|
|
|
struct DecimalStringRandomGeneratorConfig {
|
|
uint8_t prec = 38;
|
|
uint8_t scale = 10;
|
|
bool enableWeightOverflow = false;
|
|
float weightOverflowRatio = 0.001;
|
|
bool enableScaleOverflow = true;
|
|
float scaleOverFlowRatio = 0.1;
|
|
bool enablePositiveSign = false;
|
|
bool withCornerCase = true;
|
|
float cornerCaseRatio = 0.1;
|
|
float positiveRatio = 0.7;
|
|
};
|
|
|
|
class DecimalStringRandomGenerator {
|
|
std::random_device rd_;
|
|
std::mt19937 gen_;
|
|
std::uniform_int_distribution<int> dis_;
|
|
static const std::array<string, 5> cornerCases;
|
|
static const unsigned int ratio_base = 1000000;
|
|
|
|
public:
|
|
DecimalStringRandomGenerator() : gen_(rd_()), dis_(0, ratio_base) {}
|
|
std::string generate(const DecimalStringRandomGeneratorConfig& config) {
|
|
std::string ret;
|
|
auto sign = generateSign(config.positiveRatio);
|
|
if (config.enablePositiveSign || sign != '+') ret.push_back(sign);
|
|
if (config.withCornerCase && currentShouldGenerateCornerCase(config.cornerCaseRatio)) {
|
|
ret += generateCornerCase(config);
|
|
} else {
|
|
uint8_t prec = randomInt(config.prec - config.scale), scale = randomInt(config.scale);
|
|
for (int i = 0; i < prec; ++i) {
|
|
ret.push_back(generateDigit());
|
|
}
|
|
if (config.enableWeightOverflow && possible(config.weightOverflowRatio)) {
|
|
int extra_weight = config.prec - prec + 1 + randomInt(TSDB_DECIMAL_MAX_PRECISION);
|
|
while (extra_weight--) {
|
|
ret.push_back(generateDigit());
|
|
}
|
|
}
|
|
ret.push_back('.');
|
|
for (int i = 0; i < scale; ++i) {
|
|
ret.push_back(generateDigit());
|
|
}
|
|
if (config.enableScaleOverflow && possible(config.scaleOverFlowRatio)) {
|
|
int extra_scale = config.scale - scale + 1 + randomInt(TSDB_DECIMAL_MAX_SCALE);
|
|
while (extra_scale--) {
|
|
ret.push_back(generateDigit());
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
private:
|
|
int randomInt(int modulus) { return dis_(gen_) % modulus; }
|
|
char generateSign(float positive_ratio) { return possible(positive_ratio) ? '+' : '-'; }
|
|
char generateDigit() { return randomInt(10) + '0'; }
|
|
bool currentShouldGenerateCornerCase(float corner_case_ratio) {
|
|
return possible(corner_case_ratio);
|
|
}
|
|
string generateCornerCase(const DecimalStringRandomGeneratorConfig& config) {
|
|
string res{};
|
|
if (possible(0.8)) {
|
|
res = cornerCases[randomInt(cornerCases.size())];
|
|
} else {
|
|
res = std::string(config.prec - config.scale, generateDigit());
|
|
if (possible(0.8)) {
|
|
res.push_back('.');
|
|
if (possible(0.8)) {
|
|
res += std::string(config.scale, generateDigit());
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool possible(float ratio) {
|
|
return randomInt(ratio_base) <= ratio * ratio_base;
|
|
}
|
|
};
|
|
|
|
const std::array<string, 5> DecimalStringRandomGenerator::cornerCases = {"0", "NULL", "0.", ".0", "00000.000000"};
|
|
|
|
TEST(decimal, randomGenerator) {
|
|
DecimalStringRandomGeneratorConfig config;
|
|
DecimalStringRandomGenerator generator;
|
|
for (int i = 0; i < 1000; ++i) {
|
|
auto str = generator.generate(config);
|
|
cout << str << endl;
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|