Skip to content

Commit 20c0f4c

Browse files
committed
[#24]:svarga:feature, add fixed decimal type
1 parent 80b4dcd commit 20c0f4c

1 file changed

Lines changed: 208 additions & 0 deletions

File tree

include/decimal/fixed.hpp

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
#pragma once
2+
3+
#include <compare>
4+
#include <concepts>
5+
#include <cstdint>
6+
#include <limits>
7+
#include <stdexcept>
8+
#include <string>
9+
#include <tuple>
10+
#include <type_traits>
11+
#include "bid.hpp"
12+
13+
namespace math::fixed::impl {
14+
using category = math::bid::category;
15+
template<class T> concept signed_integral_t = std::integral<T> && std::is_signed_v<T>;
16+
template<class T> concept unsigned_integral_t = std::integral<T> && !std::is_signed_v<T>;
17+
constexpr bool is_special(category kind) { return kind == category::nan || kind == category::pinf || kind == category::ninf; }
18+
constexpr bool is_finite(category kind) { return kind == category::zero || kind == category::positive || kind == category::negative; }
19+
20+
template<class T> [[nodiscard]] constexpr auto abs_t(T value) {
21+
if constexpr (std::is_signed_v<T>) return value < 0 ? static_cast<std::make_unsigned_t<T>>(-value) : static_cast<std::make_unsigned_t<T>>(value);
22+
else return value;
23+
}
24+
25+
template<unsigned_integral_t element_t, int exponent> struct traits_t {
26+
static constexpr int exponent_v = exponent;
27+
static constexpr unsigned scale_v = exponent < 0 ? static_cast<unsigned>(-exponent) : 0;
28+
using wide_t = std::conditional_t<(sizeof(element_t) <= sizeof(std::uint32_t)), std::uint64_t, unsigned __int128>;
29+
30+
static constexpr wide_t pow10() {
31+
wide_t value = 1;
32+
for (unsigned i = 0; i < scale_v; ++i) value *= 10;
33+
return value;
34+
}
35+
static constexpr wide_t scale_factor_v = pow10();
36+
37+
static constexpr element_t checked_cast(wide_t value) {
38+
if (value > static_cast<wide_t>(std::numeric_limits<element_t>::max()))
39+
throw std::overflow_error("fixed::decimal_t: overflow");
40+
return static_cast<element_t>(value);
41+
}
42+
static constexpr element_t checked_add(element_t lhs, element_t rhs) {
43+
const wide_t value = static_cast<wide_t>(lhs) + static_cast<wide_t>(rhs);
44+
return checked_cast(value);
45+
}
46+
};
47+
} // namespace math::fixed::impl
48+
49+
namespace math::fixed {
50+
using category = math::bid::category;
51+
52+
template <class element_t, int exponent> struct decimal_t {
53+
static_assert(impl::unsigned_integral_t<element_t>, "element_t must be unsigned integral");
54+
using element_type_t = element_t;
55+
using significand_t = element_t;
56+
using exponent_type_t = int;
57+
using category_type_t = category;
58+
using traits_t = impl::traits_t<element_t, exponent>;
59+
static constexpr int exponent_v = exponent;
60+
static constexpr auto scale_factor_v = traits_t::scale_factor_v;
61+
62+
constexpr decimal_t() = default;
63+
constexpr decimal_t(category kind, element_t significand): kind(kind), significand(significand) { normalize(); }
64+
constexpr decimal_t(std::unsigned_integral auto value): kind(value == 0 ? category::zero : category::positive), significand(static_cast<element_t>(value)) { normalize(); }
65+
constexpr decimal_t(std::signed_integral auto value): kind(value < 0 ? category::negative : (value == 0 ? category::zero : category::positive)), significand(static_cast<element_t>(impl::abs_t(value))) { normalize(); }
66+
67+
constexpr void normalize() {
68+
if (impl::is_special(kind)) return significand = 0, void();
69+
if (significand == 0) return kind = category::zero, void();
70+
if (kind == category::zero) kind = category::positive;
71+
}
72+
73+
[[nodiscard]] constexpr bool is_zero() const { return kind == category::zero; }
74+
[[nodiscard]] constexpr bool is_finite() const { return impl::is_finite(kind); }
75+
[[nodiscard]] constexpr bool is_special() const { return impl::is_special(kind); }
76+
[[nodiscard]] constexpr auto as_tuple() const { return std::tuple{kind, significand, exponent}; }
77+
[[nodiscard]] constexpr auto decompose() const { return as_tuple(); }
78+
[[nodiscard]] inline std::string coefficient() const { return std::to_string(significand); }
79+
80+
[[nodiscard]] inline std::string str() const {
81+
switch(kind) {
82+
case category::nan: return "nan";
83+
case category::pinf: return "+inf";
84+
case category::ninf: return "-inf";
85+
case category::zero: return "0e" + std::to_string(exponent);
86+
default: return std::string{kind == category::negative ? "-" : ""} + coefficient() + "e" + std::to_string(exponent);
87+
}
88+
}
89+
90+
[[nodiscard]] explicit operator std::string() const { return str(); }
91+
92+
[[nodiscard]] constexpr long double to_long_double() const {
93+
switch(kind) {
94+
case category::nan: return std::numeric_limits<long double>::quiet_NaN();
95+
case category::pinf: return std::numeric_limits<long double>::infinity();
96+
case category::ninf: return -std::numeric_limits<long double>::infinity();
97+
default: {
98+
long double value = static_cast<long double>(significand);
99+
if constexpr (exponent >= 0) {
100+
for (int i = 0; i < exponent; ++i) value *= 10.0L;
101+
} else {
102+
for (int i = 0; i < -exponent; ++i) value /= 10.0L;
103+
}
104+
return kind == category::negative ? -value : value;
105+
}
106+
}
107+
}
108+
109+
[[nodiscard]] explicit constexpr operator float() const { return static_cast<float>(to_long_double()); }
110+
[[nodiscard]] explicit constexpr operator double() const { return static_cast<double>(to_long_double()); }
111+
[[nodiscard]] explicit constexpr operator long double() const { return to_long_double(); }
112+
113+
constexpr decimal_t& operator+=(decimal_t const& rhs) {
114+
if (!is_finite() || !rhs.is_finite()) throw std::domain_error("fixed::decimal_t: non-finite addition");
115+
if (kind == rhs.kind) significand = traits_t::checked_add(significand, rhs.significand);
116+
else {
117+
if (significand == rhs.significand) return kind = category::zero, significand = 0, *this;
118+
if (significand > rhs.significand) significand -= rhs.significand;
119+
else significand = static_cast<element_t>(rhs.significand - significand), kind = rhs.kind;
120+
}
121+
normalize();
122+
return *this;
123+
}
124+
125+
constexpr decimal_t& operator-=(decimal_t const& rhs) {
126+
auto temp = rhs;
127+
if (temp.kind == category::positive) temp.kind = category::negative;
128+
else if (temp.kind == category::negative) temp.kind = category::positive;
129+
return *this += temp;
130+
}
131+
132+
constexpr decimal_t& operator*=(decimal_t const& rhs) {
133+
if (!is_finite() || !rhs.is_finite()) throw std::domain_error("fixed::decimal_t: non-finite multiplication");
134+
if (is_zero() || rhs.is_zero()) return kind = category::zero, significand = 0, *this;
135+
using wide_t = typename traits_t::wide_t;
136+
wide_t value = static_cast<wide_t>(significand) * static_cast<wide_t>(rhs.significand);
137+
if constexpr (exponent < 0) value /= traits_t::scale_factor_v;
138+
else if constexpr (exponent > 0) value *= traits_t::scale_factor_v;
139+
significand = traits_t::checked_cast(value);
140+
kind = kind == rhs.kind ? category::positive : category::negative;
141+
normalize();
142+
return *this;
143+
}
144+
145+
constexpr decimal_t& operator/=(decimal_t const& rhs) {
146+
if (!is_finite() || !rhs.is_finite()) throw std::domain_error("fixed::decimal_t: non-finite division");
147+
if (rhs.is_zero()) throw std::domain_error("fixed::decimal_t: division by zero");
148+
if (is_zero()) return *this;
149+
using wide_t = typename traits_t::wide_t;
150+
wide_t numerator = static_cast<wide_t>(significand);
151+
wide_t denominator = static_cast<wide_t>(rhs.significand);
152+
if constexpr (exponent < 0) numerator *= traits_t::scale_factor_v;
153+
else if constexpr (exponent > 0) denominator *= traits_t::scale_factor_v;
154+
significand = traits_t::checked_cast(numerator / denominator);
155+
kind = significand == 0 ? category::zero : (kind == rhs.kind ? category::positive : category::negative);
156+
normalize();
157+
return *this;
158+
}
159+
160+
[[nodiscard]] friend constexpr decimal_t operator+(decimal_t lhs, decimal_t const& rhs) { lhs += rhs; return lhs; }
161+
[[nodiscard]] friend constexpr decimal_t operator-(decimal_t lhs, decimal_t const& rhs) { lhs -= rhs; return lhs; }
162+
[[nodiscard]] friend constexpr decimal_t operator*(decimal_t lhs, decimal_t const& rhs) { lhs *= rhs; return lhs; }
163+
[[nodiscard]] friend constexpr decimal_t operator/(decimal_t lhs, decimal_t const& rhs) { lhs /= rhs; return lhs; }
164+
[[nodiscard]] constexpr decimal_t operator+() const { return *this; }
165+
166+
[[nodiscard]] constexpr decimal_t operator-() const {
167+
auto out = *this;
168+
if (out.kind == category::positive) out.kind = category::negative;
169+
else if (out.kind == category::negative) out.kind = category::positive;
170+
return out;
171+
}
172+
173+
[[nodiscard]] friend constexpr bool operator==(decimal_t const& lhs, decimal_t const& rhs) {
174+
if (lhs.kind == category::nan || rhs.kind == category::nan) return false;
175+
if (lhs.kind != rhs.kind) return lhs.is_zero() && rhs.is_zero();
176+
return lhs.significand == rhs.significand;
177+
}
178+
179+
[[nodiscard]] friend constexpr std::partial_ordering operator<=>(decimal_t const& lhs, decimal_t const& rhs) {
180+
if (lhs.kind == category::nan || rhs.kind == category::nan) return std::partial_ordering::unordered;
181+
if (lhs.kind == category::pinf) return rhs.kind == category::pinf ? std::partial_ordering::equivalent : std::partial_ordering::greater;
182+
if (lhs.kind == category::ninf) return rhs.kind == category::ninf ? std::partial_ordering::equivalent : std::partial_ordering::less;
183+
if (rhs.kind == category::pinf) return std::partial_ordering::less;
184+
if (rhs.kind == category::ninf) return std::partial_ordering::greater;
185+
if (lhs.kind == category::negative && rhs.kind != category::negative) return std::partial_ordering::less;
186+
if (lhs.kind != category::negative && rhs.kind == category::negative) return std::partial_ordering::greater;
187+
if (lhs.significand == rhs.significand) return std::partial_ordering::equivalent;
188+
if (lhs.kind == category::negative) return lhs.significand < rhs.significand ? std::partial_ordering::greater : std::partial_ordering::less;
189+
else return lhs.significand < rhs.significand ? std::partial_ordering::less : std::partial_ordering::greater;
190+
}
191+
192+
category kind = category::zero;
193+
element_t significand = 0;
194+
};
195+
196+
template<class element_t, int exponent>
197+
[[nodiscard]] inline auto decompose(decimal_t<element_t, exponent> const& x) {
198+
return x.decompose();
199+
}
200+
201+
template<class element_t, int exponent>
202+
[[nodiscard]] inline auto checksum(decimal_t<element_t, exponent> const& x) -> std::uint64_t {
203+
std::uint64_t h = static_cast<std::uint64_t>(static_cast<unsigned char>(x.kind)) * 0x9e3779b97f4a7c15ULL;
204+
h = (h << 5) ^ (h >> 2) ^ static_cast<std::uint64_t>(x.significand);
205+
h ^= static_cast<std::uint64_t>(static_cast<std::int32_t>(exponent)) * 0xbf58476d1ce4e5b9ULL;
206+
return h;
207+
}
208+
} // namespace math::fixed

0 commit comments

Comments
 (0)