From 6ff8e4bc84a50e529878986c4dd0014f57da1f59 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 24 Nov 2025 15:25:17 -0500 Subject: [PATCH] Add `GetString` to `ColumnDecimal`. It converts the internal integer to a decimal string relative to the scale and precision. --- clickhouse/columns/decimal.cpp | 39 +++++++++++++ clickhouse/columns/decimal.h | 3 + ut/client_ut.cpp | 101 +++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) diff --git a/clickhouse/columns/decimal.cpp b/clickhouse/columns/decimal.cpp index 2d214ecf..d09e71b9 100644 --- a/clickhouse/columns/decimal.cpp +++ b/clickhouse/columns/decimal.cpp @@ -1,3 +1,4 @@ +#include #include "decimal.h" namespace @@ -246,4 +247,42 @@ size_t ColumnDecimal::GetPrecision() const return type_->As()->GetPrecision(); } +std::string ColumnDecimal::GetString(size_t index) const { + auto val = At(index); + + // Convert the Int128 to a string. + std::stringstream ss; + ss << val; + std::string str = ss.str(); + + // Start a destination string. + std::stringstream res; + auto scale = GetScale(); + + // Output a dash for negative values + if (val < 0) { + res << '-'; + str.erase(0, 1); + } + + if (scale == 0) { + // No decimal point, just output the entire value. + res << str; + } else if (str.length() <= scale) { + // Append the entire value prepended with zeros after the decimal. + res << "0." << std::string(scale-str.length(), '0') << str; + } else { + // There are digits before the decimal. + auto decAt = str.length() - scale; + res << str.substr(0, decAt); + + // Append any digits after the decimal. + if (decAt < str.length()) { + res << '.' << str.substr(decAt); + } + } + + return res.str(); +} + } diff --git a/clickhouse/columns/decimal.h b/clickhouse/columns/decimal.h index aa499a12..16ddd8aa 100644 --- a/clickhouse/columns/decimal.h +++ b/clickhouse/columns/decimal.h @@ -33,6 +33,9 @@ class ColumnDecimal : public Column { void Swap(Column& other) override; ItemView GetItem(size_t index) const override; + /// Returns a string representation of the Decimal value at \p index. + std::string GetString(size_t index) const; + size_t GetScale() const; size_t GetPrecision() const; diff --git a/ut/client_ut.cpp b/ut/client_ut.cpp index e6106d25..b43c2fb8 100644 --- a/ut/client_ut.cpp +++ b/ut/client_ut.cpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include using namespace clickhouse; @@ -841,6 +843,9 @@ TEST_P(ClientCase, Decimal) { auto decimal = [&b](size_t column, size_t row) { return b[column]->As()->At(row); }; + auto dec_string = [&b](size_t column, size_t row) { + return b[column]->As()->GetString(row); + }; EXPECT_EQ(1u, b[0]->As()->At(0)); EXPECT_EQ("123456789", int128_to_string(decimal(1, 0))); @@ -849,6 +854,12 @@ TEST_P(ClientCase, Decimal) { EXPECT_EQ("123456789", int128_to_string(decimal(4, 0))); EXPECT_EQ("123456789012345678", int128_to_string(decimal(5, 0))); EXPECT_EQ("1234567890123456789", int128_to_string(decimal(6, 0))); + EXPECT_EQ("12345.6789", dec_string(1, 0)); + EXPECT_EQ("123456789.012345678", dec_string(2, 0)); + EXPECT_EQ("0.1234567890123456789", dec_string(3, 0)); + EXPECT_EQ("12345.6789", dec_string(4, 0)); + EXPECT_EQ("123456789.012345678", dec_string(5, 0)); + EXPECT_EQ("0.1234567890123456789", dec_string(6, 0)); EXPECT_EQ(2u, b[0]->As()->At(1)); EXPECT_EQ("999999999", int128_to_string(decimal(1, 1))); @@ -857,6 +868,12 @@ TEST_P(ClientCase, Decimal) { EXPECT_EQ("999999999", int128_to_string(decimal(4, 1))); EXPECT_EQ("999999999999999999", int128_to_string(decimal(5, 1))); EXPECT_EQ("999999999999999999", int128_to_string(decimal(6, 1))); + EXPECT_EQ("99999.9999", dec_string(1, 1)); + EXPECT_EQ("999999999.999999999", dec_string(2, 1)); + EXPECT_EQ("0.0999999999999999999", dec_string(3, 1)); + EXPECT_EQ("99999.9999", dec_string(4, 1)); + EXPECT_EQ("999999999.999999999", dec_string(5, 1)); + EXPECT_EQ("0.0999999999999999999", dec_string(6, 1)); EXPECT_EQ(3u, b[0]->As()->At(2)); EXPECT_EQ("-999999999", int128_to_string(decimal(1, 2))); @@ -865,6 +882,12 @@ TEST_P(ClientCase, Decimal) { EXPECT_EQ("-999999999", int128_to_string(decimal(4, 2))); EXPECT_EQ("-999999999999999999", int128_to_string(decimal(5, 2))); EXPECT_EQ("-999999999999999999", int128_to_string(decimal(6, 2))); + EXPECT_EQ("-99999.9999", dec_string(1, 2)); + EXPECT_EQ("-999999999.999999999", dec_string(2, 2)); + EXPECT_EQ("-0.0999999999999999999", dec_string(3, 2)); + EXPECT_EQ("-99999.9999", dec_string(4, 2)); + EXPECT_EQ("-999999999.999999999", dec_string(5, 2)); + EXPECT_EQ("-0.0999999999999999999", dec_string(6, 2)); EXPECT_EQ(4u, b[0]->As()->At(3)); EXPECT_EQ("123456789", int128_to_string(decimal(1, 3))); @@ -873,6 +896,12 @@ TEST_P(ClientCase, Decimal) { EXPECT_EQ("123456789", int128_to_string(decimal(4, 3))); EXPECT_EQ("123456789012345678", int128_to_string(decimal(5, 3))); EXPECT_EQ("12345678901234567890123456789012345678", int128_to_string(decimal(6, 3))); + EXPECT_EQ("12345.6789", dec_string(1, 3)); + EXPECT_EQ("123456789.012345678", dec_string(2, 3)); + EXPECT_EQ("1234567890123456789.0123456789012345678", dec_string(3, 3)); + EXPECT_EQ("12345.6789", dec_string(4, 3)); + EXPECT_EQ("123456789.012345678", dec_string(5, 3)); + EXPECT_EQ("1234567890123456789.0123456789012345678", dec_string(6, 3)); EXPECT_EQ(5u, b[0]->As()->At(4)); EXPECT_EQ("-123456789", int128_to_string(decimal(1, 4))); @@ -881,6 +910,12 @@ TEST_P(ClientCase, Decimal) { EXPECT_EQ("-123456789", int128_to_string(decimal(4, 4))); EXPECT_EQ("-123456789012345678", int128_to_string(decimal(5, 4))); EXPECT_EQ("-12345678901234567890123456789012345678", int128_to_string(decimal(6, 4))); + EXPECT_EQ("-12345.6789", dec_string(1, 4)); + EXPECT_EQ("-123456789.012345678", dec_string(2, 4)); + EXPECT_EQ("-1234567890123456789.0123456789012345678", dec_string(3, 4)); + EXPECT_EQ("-12345.6789", dec_string(4, 4)); + EXPECT_EQ("-123456789.012345678", dec_string(5, 4)); + EXPECT_EQ("-1234567890123456789.0123456789012345678", dec_string(6, 4)); EXPECT_EQ(6u, b[0]->As()->At(5)); EXPECT_EQ("123456780", int128_to_string(decimal(1, 5))); @@ -889,9 +924,75 @@ TEST_P(ClientCase, Decimal) { EXPECT_EQ("123456789", int128_to_string(decimal(4, 5))); EXPECT_EQ("123456789012345678", int128_to_string(decimal(5, 5))); EXPECT_EQ("12345678901234567890123456789012345678", int128_to_string(decimal(6, 5))); + EXPECT_EQ("12345.6780", dec_string(1, 5)); + EXPECT_EQ("123456789.012345678", dec_string(2, 5)); + EXPECT_EQ("1234567890123456789.0123456789012345678", dec_string(3, 5)); + EXPECT_EQ("12345.6789", dec_string(4, 5)); + EXPECT_EQ("123456789.012345678", dec_string(5, 5)); + EXPECT_EQ("1234567890123456789.0123456789012345678", dec_string(6, 5)); }); } +TEST_P(ClientCase, DecimalString) { + struct testCase { + int64_t input; + std::vector exp; + }; + + auto columns = std::vector> { + std::make_shared(8, 0), + std::make_shared(8, 2), + std::make_shared(8, 4), + std::make_shared(8, 6), + std::make_shared(8, 8), + }; + + for (auto const& tc : std::list { + testCase{ + 1, + {"1", "0.01", "0.0001", "0.000001", "0.00000001"}, + }, + testCase{ + 64, + {"64", "0.64", "0.0064", "0.000064", "0.00000064"}, + }, + testCase{ + 128, + {"128", "1.28", "0.0128", "0.000128", "0.00000128"}, + }, + testCase{ + 1024, + {"1024", "10.24", "0.1024", "0.001024", "0.00001024"}, + }, + testCase{ + 87654, + {"87654", "876.54", "8.7654", "0.087654", "0.00087654"}, + }, + testCase{ + 918273, + {"918273", "9182.73", "91.8273", "0.918273", "0.00918273"}, + }, + testCase{ + 23456789, + {"23456789", "234567.89", "2345.6789", "23.456789", "0.23456789"}, + }, + testCase{ + 1234567890, + {"1234567890", "12345678.90", "123456.7890", "1234.567890", "12.34567890"}, + }, + }) { + for (size_t i = 0; i < tc.exp.size(); i++) { + auto col = columns[i]; + col->Append(tc.input); + EXPECT_EQ(tc.exp[i], col->GetString(0)); + col->Clear(); + col->Append(-tc.input); + EXPECT_EQ("-"+tc.exp[i], col->GetString(0)); + col->Clear(); + } + } +} + // Test special chars in names TEST_P(ClientCase, ColEscapeNameTest) { client_->Execute(R"sql(DROP TEMPORARY TABLE IF EXISTS "test_clickhouse_cpp_col_escape_""name_test";)sql");