Skip to content

Commit ab80e59

Browse files
committed
fix: review comments
1 parent 46ba945 commit ab80e59

File tree

5 files changed

+55
-76
lines changed

5 files changed

+55
-76
lines changed

LICENSE

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -250,12 +250,6 @@ The file src/iceberg/util/visit_type.h contains code adapted from
250250

251251
https://github.com/apache/arrow/blob/main/cpp/src/arrow/visit_type_inline.h
252252

253-
Copyright: 2016-2025 The Apache Software Foundation.
254-
Home page: https://arrow.apache.org/
255-
License: https://www.apache.org/licenses/LICENSE-2.0
256-
257-
--------------------------------------------------------------------------------
258-
259253
The file src/iceberg/util/decimal.h contains code adapted from
260254

261255
https://github.com/apache/arrow/blob/main/cpp/src/arrow/util/decimal.h

src/iceberg/util/decimal.cc

Lines changed: 38 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,15 @@ namespace {
5151
// Signed left shift with well-defined behaviour on negative numbers or overflow
5252
template <typename SignedInt, typename Shift>
5353
requires std::is_signed_v<SignedInt> && std::is_integral_v<Shift>
54-
constexpr SignedInt SafeLeftShift(SignedInt u, Shift shift) {
54+
constexpr SignedInt SafeLeftShift(SignedInt u, Shift bits) {
5555
using UnsignedInt = std::make_unsigned_t<SignedInt>;
56-
return static_cast<SignedInt>(static_cast<UnsignedInt>(u) << shift);
56+
return static_cast<SignedInt>(static_cast<UnsignedInt>(u) << bits);
5757
}
5858

5959
struct DecimalComponents {
6060
std::string_view while_digits;
6161
std::string_view fractional_digits;
62-
int32_t exponent;
62+
int32_t exponent{0};
6363
char sign{0};
6464
bool has_exponent{false};
6565
};
@@ -100,9 +100,8 @@ bool ParseDecimalComponents(std::string_view str, DecimalComponents* out) {
100100

101101
// Optional dot
102102
if (IsDot(str[pos])) {
103-
++pos;
104103
// Second run of digits after the dot
105-
pos = ParseDigitsRun(str, pos, &out->fractional_digits);
104+
pos = ParseDigitsRun(str, ++pos, &out->fractional_digits);
106105
}
107106
if (out->fractional_digits.empty() && out->while_digits.empty()) {
108107
// Need at least some digits (whole or fractional)
@@ -200,7 +199,7 @@ constexpr std::array<Decimal, Decimal::kMaxScale + 1> kDecimal128PowersOfTen = {
200199
Decimal(542101086242752217LL, 68739955140067328ULL),
201200
Decimal(5421010862427522170LL, 687399551400673280ULL)};
202201

203-
static inline void ShiftAndAdd(std::string_view input, uint128_t& out) {
202+
inline void ShiftAndAdd(std::string_view input, uint128_t& out) {
204203
for (size_t pos = 0; pos < input.size();) {
205204
const size_t group_size = std::min(kInt64DecimalDigits, input.size() - pos);
206205
const uint64_t multiple = kUInt64PowersOfTen[group_size];
@@ -213,12 +212,11 @@ static inline void ShiftAndAdd(std::string_view input, uint128_t& out) {
213212
}
214213
}
215214

216-
static void AdjustIntegerStringWithScale(std::string* str, int32_t scale) {
215+
void AdjustIntegerStringWithScale(int32_t scale, std::string* str) {
217216
if (scale == 0) {
218217
return;
219218
}
220-
assert(str != nullptr);
221-
assert(!str->empty());
219+
ICEBERG_DCHECK(str != nullptr && !str->empty(), "str must not be null or empty");
222220
const bool is_negative = str->front() == '-';
223221
const auto is_negative_offset = static_cast<int32_t>(is_negative);
224222
const auto len = static_cast<int32_t>(str->size());
@@ -291,7 +289,7 @@ Decimal::Decimal(std::string_view str) {
291289
}
292290

293291
Decimal& Decimal::Negate() {
294-
uint128_t u = static_cast<uint128_t>(~data_) + 1;
292+
uint128_t u = ~static_cast<uint128_t>(data_) + 1;
295293
data_ = static_cast<int128_t>(u);
296294
return *this;
297295
}
@@ -341,25 +339,17 @@ Decimal& Decimal::operator&=(const Decimal& other) {
341339
return *this;
342340
}
343341

344-
Decimal& Decimal::operator<<=(uint32_t shift) {
345-
if (shift != 0) {
346-
if (shift < 128) {
347-
data_ = static_cast<int128_t>(static_cast<uint128_t>(data_) << shift);
348-
} else {
349-
data_ = 0;
350-
}
342+
Decimal& Decimal::operator<<=(uint32_t bits) {
343+
if (bits != 0) {
344+
data_ = static_cast<int128_t>(static_cast<uint128_t>(data_) << bits);
351345
}
352346

353347
return *this;
354348
}
355349

356-
Decimal& Decimal::operator>>=(uint32_t shift) {
357-
if (shift != 0) {
358-
if (shift < 128) {
359-
data_ >>= shift;
360-
} else {
361-
data_ = (data_ < 0) ? -1 : 0;
362-
}
350+
Decimal& Decimal::operator>>=(uint32_t bits) {
351+
if (bits != 0) {
352+
data_ >>= bits;
363353
}
364354

365355
return *this;
@@ -372,7 +362,7 @@ Result<std::string> Decimal::ToString(int32_t scale) const {
372362
kMaxScale, scale);
373363
}
374364
std::string str(ToIntegerString());
375-
AdjustIntegerStringWithScale(&str, scale);
365+
AdjustIntegerStringWithScale(scale, &str);
376366
return str;
377367
}
378368

@@ -571,7 +561,7 @@ struct DecimalRealConversion {
571561
template <typename Real>
572562
static Real PowerOfTen(int32_t exp) {
573563
constexpr int32_t N = kPrecomputedPowersOfTen;
574-
assert(exp >= -N && exp <= N);
564+
ICEBERG_DCHECK(exp >= -N && exp <= N, "");
575565
return RealTraits<Real>::powers_of_ten()[N + exp];
576566
}
577567

@@ -590,7 +580,7 @@ struct DecimalRealConversion {
590580
static constexpr int32_t kMaxScale = Decimal::kMaxScale;
591581

592582
static constexpr auto& DecimalPowerOfTen(int32_t exp) {
593-
assert(exp >= 0 && exp <= kMaxPrecision);
583+
ICEBERG_DCHECK(exp >= 0 && exp <= kMaxPrecision, "");
594584
return kDecimal128PowersOfTen[exp];
595585
}
596586

@@ -635,11 +625,12 @@ struct DecimalRealConversion {
635625
}
636626

637627
template <typename Real>
638-
static Result<Decimal> FromPositiveApprox(Real real, int32_t precision, int32_t scale) {
628+
static Result<Decimal> FromPositiveRealApprox(Real real, int32_t precision,
629+
int32_t scale) {
639630
// Approximate algorithm that operates in the FP domain (thus subject
640631
// to precision loss).
641-
const auto x = std::nearbyint(real * PowerOfTen<double>(scale));
642-
const auto max_abs = PowerOfTen<double>(precision);
632+
const auto x = std::nearbyint(real * PowerOfTen<Real>(scale));
633+
const auto max_abs = PowerOfTen<Real>(precision);
643634
if (x <= -max_abs || x >= max_abs) {
644635
return Invalid("Cannot convert {} to Decimal(precision = {}, scale = {}): overflow",
645636
real, precision, scale);
@@ -649,10 +640,10 @@ struct DecimalRealConversion {
649640
const auto high = std::floor(std::ldexp(x, -64));
650641
const auto low = x - std::ldexp(high, 64);
651642

652-
assert(high >= 0);
653-
assert(high < 9.223372036854776e+18); // 2**63
654-
assert(low >= 0);
655-
assert(low < 1.8446744073709552e+19); // 2**64
643+
ICEBERG_DCHECK(high >= 0, "");
644+
ICEBERG_DCHECK(high < 9.223372036854776e+18, ""); // 2**63
645+
ICEBERG_DCHECK(low >= 0, "");
646+
ICEBERG_DCHECK(low < 1.8446744073709552e+19, ""); // 2**64
656647
return Decimal(static_cast<int64_t>(high), static_cast<uint64_t>(low));
657648
}
658649

@@ -665,7 +656,7 @@ struct DecimalRealConversion {
665656
// closest to `real * 10^scale`.
666657
if (scale < 0) {
667658
// Negative scales are not handled below, fall back to approx algorithm
668-
return FromPositiveApprox(real, precision, scale);
659+
return FromPositiveRealApprox(real, precision, scale);
669660
}
670661

671662
// 1. Check that `real` is within acceptable bounds.
@@ -737,7 +728,8 @@ struct DecimalRealConversion {
737728
total_exp += exp;
738729
// The supplementary right shift required so that
739730
// `x * 10^total_exp / 2^total_shift` fits in the decimal.
740-
assert(static_cast<size_t>(total_exp) < sizeof(kCeilLog2PowersOfTen));
731+
ICEBERG_DCHECK(static_cast<size_t>(total_exp) < sizeof(kCeilLog2PowersOfTen),
732+
"");
741733
const int32_t bits =
742734
std::min(right_shift_by, kCeilLog2PowersOfTen[total_exp] - total_shift);
743735
total_shift += bits;
@@ -799,7 +791,7 @@ struct DecimalRealConversion {
799791

800792
// Split decimal into whole and fractional parts to avoid precision loss
801793
auto s = decimal.GetWholeAndFraction(scale);
802-
assert(s);
794+
ICEBERG_DCHECK(s, "Decimal::GetWholeAndFraction failed");
803795

804796
Real whole = ToRealPositiveNoSplit<Real>(s->first, 0);
805797
Real fraction = ToRealPositiveNoSplit<Real>(s->second, scale);
@@ -809,8 +801,8 @@ struct DecimalRealConversion {
809801

810802
template <typename Real>
811803
static Result<Decimal> FromReal(Real value, int32_t precision, int32_t scale) {
812-
assert(precision >= 1 && precision <= kMaxPrecision);
813-
assert(scale >= -kMaxScale && scale <= kMaxScale);
804+
ICEBERG_DCHECK(precision >= 1 && precision <= kMaxPrecision, "");
805+
ICEBERG_DCHECK(scale >= -kMaxScale && scale <= kMaxScale, "");
814806

815807
if (!std::isfinite(value)) {
816808
return InvalidArgument("Cannot convert {} to Decimal", value);
@@ -830,7 +822,7 @@ struct DecimalRealConversion {
830822

831823
template <typename Real>
832824
static Real ToReal(const Decimal& decimal, int32_t scale) {
833-
assert(scale >= -kMaxScale && scale <= kMaxScale);
825+
ICEBERG_DCHECK(scale >= -kMaxScale && scale <= kMaxScale, "");
834826

835827
if (decimal.IsNegative()) {
836828
// Convert the absolute value to avoid precision loss
@@ -866,7 +858,7 @@ static bool RescaleWouldCauseDataLoss(const Decimal& value, int32_t delta_scale,
866858
const Decimal& multiplier, Decimal* result) {
867859
if (delta_scale < 0) {
868860
auto res = value.Divide(multiplier);
869-
assert(res);
861+
ICEBERG_DCHECK(res, "Decimal::Divide failed");
870862
*result = res->first;
871863
return res->second != 0;
872864
}
@@ -886,7 +878,7 @@ Result<Decimal> Decimal::FromReal(double x, int32_t precision, int32_t scale) {
886878
}
887879

888880
Result<std::pair<Decimal, Decimal>> Decimal::GetWholeAndFraction(int32_t scale) const {
889-
assert(scale >= 0 && scale <= kMaxScale);
881+
ICEBERG_DCHECK(scale >= 0 && scale <= kMaxScale, "");
890882

891883
Decimal multiplier(kDecimal128PowersOfTen[scale]);
892884
return Divide(multiplier);
@@ -946,19 +938,6 @@ Result<Decimal> Decimal::FromBigEndian(const uint8_t* bytes, int32_t length) {
946938
}
947939

948940
Result<Decimal> Decimal::Rescale(int32_t orig_scale, int32_t new_scale) const {
949-
if (orig_scale < 0 || orig_scale > kMaxScale) {
950-
return InvalidArgument(
951-
"Decimal::Rescale: original scale must be in the range [0, {}], "
952-
"was {}",
953-
kMaxScale, orig_scale);
954-
}
955-
if (new_scale < 0 || new_scale > kMaxScale) {
956-
return InvalidArgument(
957-
"Decimal::Rescale: new scale must be in the range [0, {}], "
958-
"was {}",
959-
kMaxScale, new_scale);
960-
}
961-
962941
if (orig_scale == new_scale) {
963942
return *this;
964943
}
@@ -967,23 +946,23 @@ Result<Decimal> Decimal::Rescale(int32_t orig_scale, int32_t new_scale) const {
967946
const int32_t abs_delta_scale = std::abs(delta_scale);
968947
Decimal out;
969948

970-
assert(abs_delta_scale <= kMaxScale);
949+
ICEBERG_DCHECK(abs_delta_scale <= kMaxScale, "");
971950

972951
auto& multiplier = kDecimal128PowersOfTen[abs_delta_scale];
973952

974953
const bool rescale_would_cause_data_loss =
975954
RescaleWouldCauseDataLoss(*this, delta_scale, multiplier, &out);
976955

977956
if (rescale_would_cause_data_loss) {
978-
return Invalid("Rescale would cause data loss: {} -> {}", ToIntegerString(),
979-
out.ToIntegerString());
957+
return Invalid("Rescale {} from {} to {} would cause data loss", ToIntegerString(),
958+
orig_scale, new_scale);
980959
}
981960

982961
return out;
983962
}
984963

985964
bool Decimal::FitsInPrecision(int32_t precision) const {
986-
assert(precision >= 1 && precision <= kMaxPrecision);
965+
ICEBERG_DCHECK(precision >= 1 && precision <= kMaxPrecision, "");
987966
return Decimal::Abs(*this) < kDecimal128PowersOfTen[precision];
988967
}
989968

src/iceberg/util/decimal.h

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,15 @@
2525
/// https://github.com/apache/arrow/blob/main/cpp/src/arrow/util/decimal.h
2626

2727
#include <array>
28-
#include <bit>
2928
#include <cstdint>
3029
#include <iosfwd>
3130
#include <string>
3231
#include <string_view>
3332
#include <type_traits>
3433

35-
#include <iceberg/type.h>
36-
3734
#include "iceberg/iceberg_export.h"
3835
#include "iceberg/result.h"
36+
#include "iceberg/util/formattable.h"
3937
#include "iceberg/util/int128.h"
4038
#include "iceberg/util/macros.h"
4139

@@ -44,7 +42,7 @@ namespace iceberg {
4442
/// \brief Represents 128-bit fixed-point decimal numbers.
4543
/// The max decimal precision that can be safely represented is
4644
/// 38 significant digits.
47-
class ICEBERG_EXPORT Decimal {
45+
class ICEBERG_EXPORT Decimal : public util::Formattable {
4846
public:
4947
static constexpr int32_t kBitWidth = 128;
5048
static constexpr int32_t kByteWidth = kBitWidth / 8;
@@ -55,13 +53,13 @@ class ICEBERG_EXPORT Decimal {
5553
constexpr Decimal() noexcept = default;
5654

5755
/// \brief Create a Decimal from a 128-bit integer.
58-
constexpr Decimal(int128_t value) noexcept // NOLINT
56+
constexpr Decimal(int128_t value) noexcept // NOLINT(google-explicit-constructor)
5957
: data_(value) {}
6058

6159
/// \brief Create a Decimal from any integer not wider than 64 bits.
6260
template <typename T>
6361
requires(std::is_integral_v<T> && (sizeof(T) <= sizeof(uint64_t)))
64-
constexpr Decimal(T value) noexcept // NOLINT
62+
constexpr Decimal(T value) noexcept // NOLINT(google-explicit-constructor)
6563
{
6664
data_ = static_cast<int128_t>(value);
6765
}
@@ -150,8 +148,15 @@ class ICEBERG_EXPORT Decimal {
150148
/// \brief Convert the Decimal value to an integer string.
151149
std::string ToIntegerString() const;
152150

151+
/// \brief Returns an integer string representation of the decimal value.
152+
std::string ToString() const override { return ToIntegerString(); }
153+
153154
/// \brief Convert the decimal string to a Decimal value, optionally including precision
154155
/// and scale if they are provided not null.
156+
/// \param str The string representation of the Decimal value.
157+
/// \param[out] precision Optional pointer to store the precision of the parsed value.
158+
/// \param[out] scale Optional pointer to store the scale of the parsed value.
159+
/// \return The Decimal value.
155160
static Result<Decimal> FromString(std::string_view str, int32_t* precision = nullptr,
156161
int32_t* scale = nullptr);
157162

src/iceberg/util/macros.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
#pragma once
2121

22+
#include <cassert>
23+
2224
#define ICEBERG_RETURN_UNEXPECTED(result) \
2325
if (auto&& result_name = result; !result_name) [[unlikely]] { \
2426
return std::unexpected<Error>(result_name.error()); \
@@ -36,3 +38,5 @@
3638
#define ICEBERG_ASSIGN_OR_RAISE(lhs, rexpr) \
3739
ICEBERG_ASSIGN_OR_RAISE_IMPL(ICEBERG_ASSIGN_OR_RAISE_NAME(result_, __COUNTER__), lhs, \
3840
rexpr)
41+
42+
#define ICEBERG_DCHECK(expr, message) assert((expr) && (message))

test/CMakeLists.txt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,7 @@ add_iceberg_test(table_test
7575
table_test.cc
7676
schema_json_test.cc)
7777

78-
add_iceberg_test(expression_test
79-
SOURCES
80-
decimal_test.cc
81-
expression_test.cc
82-
literal_test.cc)
78+
add_iceberg_test(expression_test SOURCES expression_test.cc literal_test.cc)
8379

8480
add_iceberg_test(json_serde_test
8581
SOURCES
@@ -91,6 +87,7 @@ add_iceberg_test(json_serde_test
9187
add_iceberg_test(util_test
9288
SOURCES
9389
config_test.cc
90+
decimal_test.cc
9491
endian_test.cc
9592
formatter_test.cc
9693
string_util_test.cc

0 commit comments

Comments
 (0)