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.
#pragma once
#include <cmath>
#include <iostream>
#include <sstream>
#if defined(__cpp_lib_format)
# include <format>
#endif
{
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:
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>
: m_value(0)
, m_scale()
{
}
template <decimal_integer_type T>
: m_value()
, m_scale()
{
}
template <decimal_integer_type T>
: m_value(value)
, m_scale(scale)
{
}
template <decimal_integer_type T>
{
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";
}
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')
);
}
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');
}
if (m_value < 0)
{
result.insert(0, 1, '-');
}
return result;
}
template <decimal_integer_type T>
{
return m_value;
}
template <decimal_integer_type T>
{
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>)
{
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));
}
}
}
#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>
{
os << std::format("{}", value);
return os;
}
#endif
constexpr int scale() const
Gets the decimal scale.
constexpr bool operator==(const decimal &other) const
Equality comparison operator.
constexpr T storage() const
Gets the raw storage value.
constexpr decimal()
Default constructor for non-placeholder integer types.
constexpr bool is_type_instance_of_v
Variable template for convenient access to is_type_instance_of.
primesum::int128_t int128_t
constexpr bool is_int_placeholder_v
constexpr bool is_decimal_v
Type trait to check if a type is a decimal instantiation.
std::ostream & operator<<(std::ostream &os, const sparrow::nullval_t &)