diff --git a/src/iceberg/test/truncate_util_test.cc b/src/iceberg/test/truncate_util_test.cc index 10093e440..a965b1b67 100644 --- a/src/iceberg/test/truncate_util_test.cc +++ b/src/iceberg/test/truncate_util_test.cc @@ -19,6 +19,8 @@ #include "iceberg/util/truncate_util.h" +#include + #include #include "iceberg/expression/literal.h" @@ -51,6 +53,19 @@ TEST(TruncateUtilTest, TruncateLiteral) { Literal::Binary(std::vector(expected.begin(), expected.end()))); } +TEST(TruncateUtilTest, TruncateLiteralAvoidsSignedIntegerOverflow) { + EXPECT_EQ(TruncateUtils::TruncateLiteral( + Literal::Int(std::numeric_limits::max() - 1), + std::numeric_limits::max()), + Literal::Int(0)); + EXPECT_EQ(TruncateUtils::TruncateLiteral( + Literal::Int(std::numeric_limits::min()), 10), + Literal::Int(std::numeric_limits::max() - 1)); + EXPECT_EQ(TruncateUtils::TruncateLiteral( + Literal::Long(std::numeric_limits::min()), 10), + Literal::Long(std::numeric_limits::max() - 1)); +} + TEST(TruncateUtilTest, TruncateLiteralRejectsInvalidWidth) { std::vector data{1, 2, 3}; diff --git a/src/iceberg/util/truncate_util.h b/src/iceberg/util/truncate_util.h index 7fee86eba..16e651fff 100644 --- a/src/iceberg/util/truncate_util.h +++ b/src/iceberg/util/truncate_util.h @@ -22,11 +22,13 @@ #include #include #include +#include #include #include "iceberg/iceberg_export.h" #include "iceberg/result.h" #include "iceberg/type_fwd.h" +#include "iceberg/util/int128.h" namespace iceberg { @@ -84,7 +86,15 @@ class ICEBERG_EXPORT TruncateUtils { template requires std::is_same_v || std::is_same_v static inline T TruncateInteger(T v, int32_t W) { - return v - (((v % W) + W) % W); + using WideInt = std::conditional_t, int64_t, int128_t>; + using UnsignedT = std::make_unsigned_t; + + const auto value = static_cast(v); + const auto width = static_cast(W); + const auto remainder = ((value % width) + width) % width; + const auto truncated = value - remainder; + // Preserve modulo conversion when the mathematical result is below the signed range. + return static_cast(static_cast(truncated)); } /// \brief Truncate a Decimal to a specified width.