|
| 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