diff --git a/clickhouse/columns/decimal.cpp b/clickhouse/columns/decimal.cpp index 6586e289..a714991c 100644 --- a/clickhouse/columns/decimal.cpp +++ b/clickhouse/columns/decimal.cpp @@ -1,6 +1,9 @@ #include "decimal.h" #include "clickhouse/exceptions.h" +#include +#include + namespace { using clickhouse::ValidationError; @@ -155,6 +158,42 @@ Int128 ColumnDecimal::At(size_t i) const { } } +std::string ColumnDecimal::StringAt(size_t i) const { + auto scale = GetScale(); + + Int128 val = At(i); + std::string raw_str = Bignum::Int128ToString(val); + if (scale == 0) { + return raw_str; + } + + std::string ret; + ret.reserve(GetPrecision() + 2); // extra space for '-' and '.'; + + auto it = raw_str.cbegin(); + auto end = raw_str.cend(); + + if (it != end && *it == '-') { + ret.push_back(*it++); + } + + int64_t str_len = std::distance(it, end); + int64_t integral_len = str_len - static_cast(scale); + + if (integral_len > 0) { + std::copy(it, it + integral_len, std::back_inserter(ret)); + it += integral_len; + ret.push_back('.'); + } + else { + ret.append("0."); + ret.append(static_cast(-integral_len), '0'); + } + std::copy(it, end, std::back_inserter(ret)); + + return ret; +} + void ColumnDecimal::Reserve(size_t new_cap) { data_->Reserve(new_cap); } diff --git a/clickhouse/columns/decimal.h b/clickhouse/columns/decimal.h index 28425ab5..ded0c1d4 100644 --- a/clickhouse/columns/decimal.h +++ b/clickhouse/columns/decimal.h @@ -20,6 +20,9 @@ class ColumnDecimal : public Column { Int128 At(size_t i) const; inline auto operator[](size_t i) const { return At(i); } + // Returns string representation of the decimal value + std::string StringAt(size_t i) const; + public: /// Increase the capacity of the column for large block insertion. void Reserve(size_t new_cap) override; diff --git a/ut/columns_ut.cpp b/ut/columns_ut.cpp index 232778f2..d79debbe 100644 --- a/ut/columns_ut.cpp +++ b/ut/columns_ut.cpp @@ -124,6 +124,7 @@ TEST(ColumnsCase, DecimalStringValueMapping) { }; std::vector samples = { + {18, 0, "0.0", Int128(0)}, {18, 0, "0.123", Int128(0)}, {18, 0, "123", Int128(123)}, {18, 3, "0.123", Int128(123)}, @@ -144,6 +145,45 @@ TEST(ColumnsCase, DecimalStringValueMapping) { } +TEST(ColumnsCase, DecimalStringAt) { + struct TestSample { + size_t precision; + size_t scale; + std::string str; + std::string expect; + }; + + std::vector samples = { + {18, 0, "0", "0"}, + {18, 0, "123", "123"}, + {18, 0, "-123", "-123"}, + {18, 3, "0", "0.000"}, + {18, 3, "1", "0.001"}, + {18, 3, "12", "0.012"}, + {18, 3, "123", "0.123"}, + {18, 3, "-123", "-0.123"}, + {18, 3, "0.123", "0.123"}, + {18, 3, "1234", "1.234"}, + {18, 3, "-1234", "-1.234"}, + {18, 3, "-1.234", "-1.234"}, + {18, 3, "123.0", "123.000"}, + {18, 3, "123.", "123.000"}, + {18, 3, "123000", "123.000"}, + // Large values that do not fit in 64 bits. + {38, 4, "12345678901234567890.1234", "12345678901234567890.1234"}, + {38, 4, "-12345678901234567890.1234", "-12345678901234567890.1234"}, + {38, 0, "99999999999999999999999999999999999999", "99999999999999999999999999999999999999"}, + }; + + for (auto & [precision, scale, str, expect] : samples) { + auto col = std::make_shared(precision, scale); + EXPECT_NO_THROW(col->Append(str)) << "exception for value \"" << str << "\""; + auto value = col->StringAt(0); + EXPECT_EQ(value, expect); + } + +} + TEST(ColumnsCase, NumericInit) { auto col = std::make_shared(MakeNumbers());