blob: b7f57f47cee1d458f0322b3215cacd252797e5e0 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#pragma once
/// \file iceberg/util/decimal.h
/// \brief 128-bit fixed-point decimal numbers.
/// Adapted from Apache Arrow with only Decimal128 support.
/// https://github.com/apache/arrow/blob/main/cpp/src/arrow/util/decimal.h
#include <array>
#include <compare>
#include <cstdint>
#include <iosfwd>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "iceberg/iceberg_export.h"
#include "iceberg/result.h"
#include "iceberg/util/formattable.h"
#include "iceberg/util/int128.h"
namespace iceberg {
/// \brief Represents 128-bit fixed-point decimal numbers.
/// The max decimal precision that can be safely represented is
/// 38 significant digits.
class ICEBERG_EXPORT Decimal : public util::Formattable {
public:
static constexpr int32_t kBitWidth = 128;
static constexpr int32_t kByteWidth = kBitWidth / 8;
static constexpr int32_t kMaxPrecision = 38;
static constexpr int32_t kMaxScale = 38;
/// \brief Default constructor initializes to zero.
constexpr Decimal() noexcept = default;
/// \brief Create a Decimal from a 128-bit integer.
constexpr Decimal(int128_t value) noexcept // NOLINT implicit conversion
: data_(value) {}
/// \brief Create a Decimal from any integer not wider than 64 bits.
template <typename T>
requires(std::is_integral_v<T> && (sizeof(T) <= sizeof(uint64_t)))
constexpr Decimal(T value) noexcept // NOLINT implicit conversion
: data_(static_cast<int128_t>(value)) {}
/// \brief Parse a Decimal from a string representation.
/// \throw This constructor throws an exception if parsing fails. Use
/// Decimal::FromString() if you want to handle errors more gracefully.
explicit Decimal(std::string_view str);
/// \brief Create a Decimal from two 64-bit integers.
constexpr Decimal(int64_t high, uint64_t low) noexcept {
data_ = (static_cast<int128_t>(high) << 64) | low;
}
/// \brief Negate the current Decimal value (in place)
Decimal& Negate();
/// \brief Absolute value of the current Decimal value (in place)
Decimal& Abs();
/// \brief Absolute value of the current Decimal value
static Decimal Abs(const Decimal& value);
/// \brief Add a number to this one. The result is truncated to 128 bits.
Decimal& operator+=(const Decimal& other);
/// \brief Subtract a number from this one. The result is truncated to 128 bits.
Decimal& operator-=(const Decimal& other);
/// \brief Multiply this number by another. The result is truncated to 128 bits.
Decimal& operator*=(const Decimal& other);
/// \brief Divide this number by another.
///
/// The operation does not modify the current Decimal value.
/// The answer rounds towards zero. Signs work like:
/// 21 / 5 -> 4, 1
/// -21 / 5 -> -4, -1
/// 21 / -5 -> -4, 1
/// -21 / -5 -> 4, -1
/// \param[in] divisor the number to divide by
/// \return the pair of the quotient and the remainder
Result<std::pair<Decimal, Decimal>> Divide(const Decimal& divisor) const;
/// \brief In place division.
Decimal& operator/=(const Decimal& other);
/// \brief Bitwise OR operation.
Decimal& operator|=(const Decimal& other);
/// \brief Bitwise AND operation.
Decimal& operator&=(const Decimal& other);
/// \brief Shift left by the given number of bits (in place).
Decimal& operator<<=(uint32_t shift);
/// \brief Shift left by the given number of bits.
Decimal operator<<(uint32_t shift) const {
Decimal result(*this);
result <<= shift;
return result;
}
/// \brief Shift right by the given number of bits (in place).
Decimal& operator>>=(uint32_t shift);
/// \brief Shift right by the given number of bits.
Decimal operator>>(uint32_t shift) const {
Decimal result(*this);
result >>= shift;
return result;
}
/// \brief Get the underlying 128-bit integer representation of the number.
constexpr int128_t value() const { return data_; }
/// \brief Get the high bits of the two's complement representation of the number.
constexpr int64_t high() const { return static_cast<int64_t>(data_ >> 64); }
/// \brief Get the low bits of the two's complement representation of the number.
constexpr uint64_t low() const { return static_cast<uint64_t>(data_); }
/// \brief Convert the Decimal value to a base 10 decimal string with the given scale.
/// \param scale The scale to use for the string representation.
/// \return The string representation of the Decimal value.
Result<std::string> ToString(int32_t scale) const;
/// \brief Convert the Decimal value to an integer string.
std::string ToIntegerString() const;
/// \brief Returns an integer string representation of the decimal value.
std::string ToString() const override { return ToIntegerString(); }
/// \brief Convert the decimal string to a Decimal value, optionally including precision
/// and scale if they are provided not null.
/// \param str The string representation of the Decimal value.
/// \param[out] precision Optional pointer to store the precision of the parsed value.
/// \param[out] scale Optional pointer to store the scale of the parsed value.
/// \return The Decimal value.
static Result<Decimal> FromString(std::string_view str, int32_t* precision = nullptr,
int32_t* scale = nullptr);
/// \brief Convert from a big-endian byte representation. The length must be
/// between 1 and 16.
/// \return error status if the length is an invalid value
static Result<Decimal> FromBigEndian(const uint8_t* data, int32_t length);
/// \brief Convert Decimal's unscaled value to two’s-complement big-endian binary, using
/// the minimum number of bytes for the value.
/// \return A vector containing the big-endian bytes.
std::vector<uint8_t> ToBigEndian() const;
/// \brief Convert Decimal from one scale to another.
Result<Decimal> Rescale(int32_t orig_scale, int32_t new_scale) const;
/// \brief Whether this number fits in the given precision
///
/// Returns true if the number of significant digits is less or equal to `precision`.
bool FitsInPrecision(int32_t precision) const;
/// \brief Spaceship operator for three-way comparison.
std::strong_ordering operator<=>(const Decimal& other) const {
if (high() != other.high()) {
return high() <=> other.high();
}
return low() <=> other.low();
}
/// \brief Compare two Decimals with different scales.
static std::partial_ordering Compare(const Decimal& lhs, const Decimal& rhs,
int32_t lhs_scale, int32_t rhs_scale);
const uint8_t* native_endian_bytes() const {
return reinterpret_cast<const uint8_t*>(&data_);
}
/// \brief Returns the raw bytes of the value in native-endian byte order.
std::array<uint8_t, kByteWidth> ToBytes() const;
/// \brief Returns 1 if positive or zero, -1 if strictly negative.
int64_t Sign() const { return 1 | (high() >> 63); }
/// \brief Check if the Decimal value is negative.
bool IsNegative() const { return (high() >> 63) < 0; }
explicit operator bool() const { return data_ != 0; }
friend bool operator==(const Decimal& lhs, const Decimal& rhs) {
return lhs.data_ == rhs.data_;
}
friend bool operator!=(const Decimal& lhs, const Decimal& rhs) {
return lhs.data_ != rhs.data_;
}
ICEBERG_EXPORT friend Decimal operator-(const Decimal& operand);
ICEBERG_EXPORT friend Decimal operator~(const Decimal& operand);
ICEBERG_EXPORT friend Decimal operator+(const Decimal& lhs, const Decimal& rhs);
ICEBERG_EXPORT friend Decimal operator-(const Decimal& lhs, const Decimal& rhs);
ICEBERG_EXPORT friend Decimal operator*(const Decimal& lhs, const Decimal& rhs);
ICEBERG_EXPORT friend Decimal operator/(const Decimal& lhs, const Decimal& rhs);
ICEBERG_EXPORT friend Decimal operator%(const Decimal& lhs, const Decimal& rhs);
private:
int128_t data_{0};
};
ICEBERG_EXPORT std::ostream& operator<<(std::ostream& os, const Decimal& decimal);
} // namespace iceberg