Skip to content

Commit 99240ba

Browse files
refactor: Trying to resolve issue#17
1 parent ef46808 commit 99240ba

3 files changed

Lines changed: 198 additions & 19 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,9 @@ jobs:
9696
xmake-version: latest
9797
package-cache: true
9898

99-
- name: Build core library
100-
run: |
101-
xmake f -m release -y -vv
102-
xmake b -y -vv -j2 mcpplibs-primitives
103-
10499
- name: Build and test
105100
run: |
101+
xmake f -m release --ccache=n -y -vv
106102
xmake b -y -vv -j2 primitives_test
107103
xmake run primitives_test
108104

src/operations/operators.cppm

Lines changed: 125 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
module;
22

3+
#include <cmath>
34
#include <compare>
45
#include <concepts>
6+
#include <cstdint>
57
#include <expected>
8+
#include <limits>
9+
#include <optional>
610
#include <type_traits>
711
#include <utility>
812

@@ -13,11 +17,9 @@ import mcpplibs.primitives.operations.dispatcher;
1317
import mcpplibs.primitives.operations.impl;
1418
import mcpplibs.primitives.primitive.impl;
1519
import mcpplibs.primitives.primitive.traits;
16-
import mcpplibs.primitives.conversion.traits;
17-
import mcpplibs.primitives.conversion.underlying;
1820
import mcpplibs.primitives.policy.handler;
1921
import mcpplibs.primitives.policy.impl;
20-
import mcpplibs.primitives.underlying.traits;
22+
import mcpplibs.primitives.underlying;
2123

2224

2325
namespace mcpplibs::primitives::operations::details {
@@ -60,14 +62,128 @@ constexpr auto decode_three_way_code(CommonRep const &code) -> Ordering {
6062
return Ordering::equivalent;
6163
}
6264

63-
constexpr auto to_policy_error_kind(const conversion::risk::kind kind)
65+
enum class assign_risk : unsigned char {
66+
overflow,
67+
underflow,
68+
domain_error,
69+
precision_loss,
70+
};
71+
72+
template <typename DestRep, typename SrcRep>
73+
concept statically_castable = requires(SrcRep value) {
74+
static_cast<std::remove_cvref_t<DestRep>>(value);
75+
};
76+
77+
template <std_integer DestRep, std_integer SrcRep>
78+
constexpr auto numeric_risk(SrcRep value) -> std::optional<assign_risk> {
79+
using dest_type = std::remove_cvref_t<DestRep>;
80+
using src_type = std::remove_cvref_t<SrcRep>;
81+
82+
if constexpr (std::is_signed_v<src_type>) {
83+
auto const signed_value = static_cast<std::intmax_t>(value);
84+
if constexpr (std::is_signed_v<dest_type>) {
85+
if (signed_value <
86+
static_cast<std::intmax_t>(std::numeric_limits<dest_type>::min())) {
87+
return assign_risk::underflow;
88+
}
89+
if (signed_value >
90+
static_cast<std::intmax_t>(std::numeric_limits<dest_type>::max())) {
91+
return assign_risk::overflow;
92+
}
93+
} else {
94+
if (signed_value < 0) {
95+
return assign_risk::underflow;
96+
}
97+
if (static_cast<std::uintmax_t>(signed_value) >
98+
static_cast<std::uintmax_t>(std::numeric_limits<dest_type>::max())) {
99+
return assign_risk::overflow;
100+
}
101+
}
102+
} else {
103+
auto const unsigned_value = static_cast<std::uintmax_t>(value);
104+
if (unsigned_value >
105+
static_cast<std::uintmax_t>(std::numeric_limits<dest_type>::max())) {
106+
return assign_risk::overflow;
107+
}
108+
}
109+
110+
return std::nullopt;
111+
}
112+
113+
template <std_integer DestRep, std_floating SrcRep>
114+
constexpr auto numeric_risk(SrcRep value) -> std::optional<assign_risk> {
115+
using dest_type = std::remove_cvref_t<DestRep>;
116+
using src_type = std::remove_cvref_t<SrcRep>;
117+
118+
if (std::isnan(value)) {
119+
return assign_risk::domain_error;
120+
}
121+
if (std::isinf(value)) {
122+
return value < static_cast<src_type>(0) ? assign_risk::underflow
123+
: assign_risk::overflow;
124+
}
125+
126+
auto const normalized = static_cast<long double>(value);
127+
auto const min_value =
128+
static_cast<long double>(std::numeric_limits<dest_type>::lowest());
129+
auto const max_value =
130+
static_cast<long double>(std::numeric_limits<dest_type>::max());
131+
132+
if (normalized < min_value) {
133+
return assign_risk::underflow;
134+
}
135+
if (normalized > max_value) {
136+
return assign_risk::overflow;
137+
}
138+
return std::nullopt;
139+
}
140+
141+
template <typename DestRep, typename SrcRep>
142+
constexpr auto numeric_risk(SrcRep value) -> std::optional<assign_risk> {
143+
using dest_type = std::remove_cvref_t<DestRep>;
144+
using src_type = std::remove_cvref_t<SrcRep>;
145+
146+
if constexpr (std_integer<dest_type> && std_integer<src_type>) {
147+
return numeric_risk<dest_type, src_type>(value);
148+
} else if constexpr (std_integer<dest_type> && std_floating<src_type>) {
149+
return numeric_risk<dest_type, src_type>(value);
150+
} else {
151+
return std::nullopt;
152+
}
153+
}
154+
155+
template <typename DestRep, typename SrcRep>
156+
requires statically_castable<DestRep, SrcRep>
157+
constexpr auto saturating_rep_cast(SrcRep value) noexcept
158+
-> std::remove_cvref_t<DestRep> {
159+
using dest_type = std::remove_cvref_t<DestRep>;
160+
using src_type = std::remove_cvref_t<SrcRep>;
161+
162+
if constexpr (std_integer<dest_type> && std_numeric<src_type>) {
163+
if (auto const kind = numeric_risk<dest_type>(value); kind.has_value()) {
164+
if (*kind == assign_risk::overflow) {
165+
return std::numeric_limits<dest_type>::max();
166+
}
167+
if (*kind == assign_risk::underflow) {
168+
return std::numeric_limits<dest_type>::lowest();
169+
}
170+
if (*kind == assign_risk::domain_error) {
171+
return dest_type{};
172+
}
173+
}
174+
}
175+
176+
return static_cast<dest_type>(value);
177+
}
178+
179+
constexpr auto to_policy_error_kind(assign_risk kind)
64180
-> policy::error::kind {
65181
switch (kind) {
66-
case conversion::risk::kind::overflow:
182+
case assign_risk::overflow:
67183
return policy::error::kind::overflow;
68-
case conversion::risk::kind::underflow:
184+
case assign_risk::underflow:
69185
return policy::error::kind::underflow;
70-
case conversion::risk::kind::domain_error:
186+
case assign_risk::domain_error:
71187
return policy::error::kind::domain_error;
72188
default:
73189
return policy::error::kind::unspecified;
@@ -547,7 +663,7 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs)
547663
if constexpr (std::same_as<lhs_value_policy, policy::value::checked> &&
548664
std_integer<lhs_rep> && std_numeric<common_rep>) {
549665
if (auto const kind =
550-
conversion::numeric_risk<lhs_rep>(assigned_common_rep);
666+
details::numeric_risk<lhs_rep>(assigned_common_rep);
551667
kind.has_value()) {
552668
return std::unexpected(
553669
details::to_error_payload<ErrorPayload>(
@@ -556,7 +672,7 @@ constexpr auto apply_assign(Lhs &lhs, Rhs const &rhs)
556672
}
557673

558674
auto const assigned_rep =
559-
conversion::saturating_cast<lhs_rep>(assigned_common_rep);
675+
details::saturating_rep_cast<lhs_rep>(assigned_common_rep);
560676
lhs.store(underlying::traits<lhs_value_type>::from_rep(assigned_rep));
561677
return out;
562678
}

src/primitive/impl.cppm

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
module;
2+
#include <cmath>
23
#include <cstdint>
4+
#include <limits>
35
#include <tuple>
46
#include <type_traits>
57

68
export module mcpplibs.primitives.primitive.impl;
79

8-
import mcpplibs.primitives.conversion.underlying;
9-
import mcpplibs.primitives.underlying.traits;
10+
import mcpplibs.primitives.underlying;
1011
import mcpplibs.primitives.policy.traits;
1112
import mcpplibs.primitives.policy.handler;
1213
import mcpplibs.primitives.policy.impl;
@@ -55,6 +56,73 @@ private:
5556
cross_underlying_constructible_v<U> &&
5657
policy_allows_underlying_bridge_v<value_type, U>;
5758

59+
template <typename TargetRep, typename SourceRep>
60+
static constexpr auto saturating_rep_cast_(SourceRep value) noexcept
61+
-> std::remove_cvref_t<TargetRep> {
62+
using target_rep_type = std::remove_cvref_t<TargetRep>;
63+
using source_rep_type = std::remove_cvref_t<SourceRep>;
64+
65+
if constexpr (std_integer<target_rep_type> && std_integer<source_rep_type>) {
66+
if constexpr (std::is_signed_v<source_rep_type>) {
67+
auto const signed_value = static_cast<std::intmax_t>(value);
68+
if constexpr (std::is_signed_v<target_rep_type>) {
69+
if (signed_value < static_cast<std::intmax_t>(
70+
std::numeric_limits<target_rep_type>::min())) {
71+
return std::numeric_limits<target_rep_type>::lowest();
72+
}
73+
if (signed_value > static_cast<std::intmax_t>(
74+
std::numeric_limits<target_rep_type>::max())) {
75+
return std::numeric_limits<target_rep_type>::max();
76+
}
77+
return static_cast<target_rep_type>(value);
78+
} else {
79+
if (signed_value < 0) {
80+
return std::numeric_limits<target_rep_type>::lowest();
81+
}
82+
if (static_cast<std::uintmax_t>(signed_value) >
83+
static_cast<std::uintmax_t>(
84+
std::numeric_limits<target_rep_type>::max())) {
85+
return std::numeric_limits<target_rep_type>::max();
86+
}
87+
return static_cast<target_rep_type>(value);
88+
}
89+
} else {
90+
auto const unsigned_value = static_cast<std::uintmax_t>(value);
91+
if (unsigned_value >
92+
static_cast<std::uintmax_t>(
93+
std::numeric_limits<target_rep_type>::max())) {
94+
return std::numeric_limits<target_rep_type>::max();
95+
}
96+
return static_cast<target_rep_type>(value);
97+
}
98+
} else if constexpr (std_integer<target_rep_type> &&
99+
std_floating<source_rep_type>) {
100+
if (std::isnan(value)) {
101+
return target_rep_type{};
102+
}
103+
if (std::isinf(value)) {
104+
return value < static_cast<source_rep_type>(0)
105+
? std::numeric_limits<target_rep_type>::lowest()
106+
: std::numeric_limits<target_rep_type>::max();
107+
}
108+
109+
auto const normalized = static_cast<long double>(value);
110+
auto const min_value = static_cast<long double>(
111+
std::numeric_limits<target_rep_type>::lowest());
112+
auto const max_value = static_cast<long double>(
113+
std::numeric_limits<target_rep_type>::max());
114+
if (normalized < min_value) {
115+
return std::numeric_limits<target_rep_type>::lowest();
116+
}
117+
if (normalized > max_value) {
118+
return std::numeric_limits<target_rep_type>::max();
119+
}
120+
return static_cast<target_rep_type>(value);
121+
} else {
122+
return static_cast<target_rep_type>(value);
123+
}
124+
}
125+
58126
template <underlying_type Target, underlying_type Source>
59127
static constexpr auto convert_underlying_(Source source) noexcept -> Target {
60128
using source_value_type = std::remove_cv_t<Source>;
@@ -63,9 +131,8 @@ private:
63131
underlying::traits<std::remove_cv_t<Target>>::rep_type;
64132

65133
auto const source_rep = underlying::traits<source_value_type>::to_rep(source);
66-
auto const target_rep =
67-
conversion::saturating_cast<target_rep_type>(
68-
static_cast<source_rep_type>(source_rep));
134+
auto const target_rep = saturating_rep_cast_<target_rep_type>(
135+
static_cast<source_rep_type>(source_rep));
69136
return underlying::traits<std::remove_cv_t<Target>>::from_rep(target_rep);
70137
}
71138

0 commit comments

Comments
 (0)