sparrow 0.9.0
Loading...
Searching...
No Matches
/home/runner/work/sparrow/sparrow/include/sparrow/utils/decimal.hpp

Fixed-point decimal number representation with arbitrary precision.

Fixed-point decimal number representation with arbitrary precision. The decimal class provides a fixed-point representation of decimal numbers using an integer value and a scale factor. The actual decimal value is computed as: value * 10^(-scale).

This representation is particularly useful for financial calculations and data science applications where exact decimal representation is required to avoid floating-point precision errors.

Template Parameters
TThe underlying integer type for storing the decimal value
Precondition
T must satisfy decimal_integer_type concept
Postcondition
Maintains exact decimal representation without floating-point errors
Thread-safe for read operations, requires external synchronization for writes
Supports conversion to floating-point types with appropriate precision loss warnings
// Represent 123.45 as decimal with scale 2
decimal<int64_t> d1(12345, 2); // 12345 * 10^(-2) = 123.45
// Represent 1000 as decimal with negative scale
decimal<int64_t> d2(1, -3); // 1 * 10^(-(-3)) = 1000
// Convert to floating-point
double value = static_cast<double>(d1); // 123.45
// Convert to string representation
std::string str = static_cast<std::string>(d1); // "123.45"
#pragma once
#include <cmath>
#include <iostream>
#include <sstream>
#if defined(__cpp_lib_format)
# include <format>
#endif
namespace sparrow
{
template <typename T>
concept decimal_integer_type = std::is_same_v<T, std::int32_t> || std::is_same_v<T, std::int64_t>
|| std::is_same_v<T, int128_t> || std::is_same_v<T, int256_t>;
template <decimal_integer_type T>
class decimal
{
public:
using integer_type = T;
constexpr decimal()
constexpr decimal()
constexpr decimal(T value, int scale);
constexpr bool operator==(const decimal& other) const;
constexpr explicit operator float() const
constexpr explicit operator double() const
constexpr explicit operator long double() const
[[nodiscard]] explicit operator std::string() const
[[nodiscard]] constexpr T storage() const;
[[nodiscard]] constexpr int scale() const;
private:
template <class FLOAT_TYPE>
[[nodiscard]] constexpr FLOAT_TYPE convert_to_floating_point() const
T m_value;
int m_scale;
};
template <typename T>
template <typename T>
concept decimal_type = is_decimal_v<T> && decimal_integer_type<typename T::integer_type>;
template <decimal_integer_type T>
constexpr decimal<T>::decimal()
: m_value(0)
, m_scale()
{
}
template <decimal_integer_type T>
constexpr decimal<T>::decimal()
: m_value()
, m_scale()
{
}
template <decimal_integer_type T>
constexpr decimal<T>::decimal(T value, int scale)
: m_value(value)
, m_scale(scale)
{
}
template <decimal_integer_type T>
constexpr bool decimal<T>::operator==(const decimal& other) const
{
return m_value == other.m_value && m_scale == other.m_scale;
}
template <decimal_integer_type T>
constexpr decimal<T>::operator float() const
{
return convert_to_floating_point<float>();
}
template <decimal_integer_type T>
constexpr decimal<T>::operator double() const
{
return convert_to_floating_point<double>();
}
template <decimal_integer_type T>
constexpr decimal<T>::operator long double() const
{
return convert_to_floating_point<long double>();
}
template <decimal_integer_type T>
decimal<T>::operator std::string() const
{
std::stringstream ss;
ss << m_value;
std::string result = ss.str();
if (m_scale == 0)
{
return result;
}
if (result[0] == '0')
{
return "0";
}
// remove - (we handle it later)
if (result[0] == '-')
{
result = result.substr(1);
}
if (m_scale > 0)
{
if (result.length() <= static_cast<std::size_t>(m_scale))
{
result.insert(
0,
std::string(static_cast<std::size_t>(m_scale) + 1 - result.length(), '0')
); // Pad with leading zeros
}
std::size_t int_part_len = result.length() - static_cast<std::size_t>(m_scale);
std::string int_part = result.substr(0, int_part_len);
std::string frac_part = result.substr(int_part_len);
result = int_part + "." + frac_part;
}
else
{
result += std::string(static_cast<std::size_t>(-m_scale), '0'); // Append zeros
}
// handle negative values
if (m_value < 0)
{
result.insert(0, 1, '-');
}
return result;
}
template <decimal_integer_type T>
constexpr T decimal<T>::storage() const
{
return m_value;
}
template <decimal_integer_type T>
constexpr int decimal<T>::scale() const
{
return m_scale;
}
template <decimal_integer_type T>
template <class FLOAT_TYPE>
constexpr FLOAT_TYPE decimal<T>::convert_to_floating_point() const
{
using to_type = FLOAT_TYPE;
if constexpr (std::is_same_v<T, int256_t>)
{
// danger zone
auto val = static_cast<int128_t>(m_value);
return static_cast<to_type>(val) / static_cast<to_type>(std::pow(10, m_scale));
}
else
{
return static_cast<to_type>(m_value) / static_cast<to_type>(std::pow(10, m_scale));
}
}
} // namespace sparrow
#if defined(__cpp_lib_format)
template <typename T>
struct std::formatter<sparrow::decimal<T>>
{
constexpr auto parse(std::format_parse_context& ctx)
{
return ctx.begin();
}
auto format(const sparrow::decimal<T>& d, std::format_context& ctx) const
{
return std::format_to(ctx.out(), "Decimal({}, {})", d.storage(), d.scale());
}
};
template <typename T>
std::ostream& operator<<(std::ostream& os, const sparrow::decimal<T>& value)
{
os << std::format("{}", value);
return os;
}
#endif
constexpr int scale() const
Gets the decimal scale.
Definition decimal.hpp:362
constexpr bool operator==(const decimal &other) const
Equality comparison operator.
Definition decimal.hpp:282
constexpr T storage() const
Gets the raw storage value.
Definition decimal.hpp:356
constexpr decimal()
Default constructor for non-placeholder integer types.
Definition decimal.hpp:259
constexpr bool is_type_instance_of_v
Variable template for convenient access to is_type_instance_of.
Definition mp_utils.hpp:102
primesum::int128_t int128_t
Definition large_int.hpp:84
constexpr bool is_int_placeholder_v
Definition large_int.hpp:82
constexpr bool is_decimal_v
Type trait to check if a type is a decimal instantiation.
Definition decimal.hpp:245
std::ostream & operator<<(std::ostream &os, const sparrow::nullval_t &)