dimval 0.2.0
Modern C++23 header-only library of dimensional values (units, measures, ranges)
Loading...
Searching...
No Matches
range.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <dimval/core.hpp>
7#include <dimval/measure.hpp>
8#include <dimval/traits.hpp>
9#include <dimval/unit.hpp>
10
11#include <expected>
12#include <optional>
13#include <string>
14#include <utility>
15
16namespace dimval {
17
18namespace detail {
19
20template <typename T>
21[[nodiscard]] constexpr bool contains_value(T lo, T hi, const RangeInclusion inc, T x) noexcept {
22 const bool lo_ok = (inc.lower == Bound::Inclusive) ? (x >= lo) : (x > lo);
23 const bool hi_ok = (inc.upper == Bound::Inclusive) ? (x <= hi) : (x < hi);
24 return lo_ok && hi_ok;
25}
26
27template <typename T>
28[[nodiscard]] constexpr std::expected<std::tuple<T, T, RangeInclusion>, RangeError>
29validate_range(T lo, T hi, RangeInclusion inc) noexcept {
30 if (hi < lo) {
31 return std::unexpected{RangeError{RangeErrorCode::MaxLessThanMin,
32 "range upper bound is less than lower bound"}};
33 }
34 if (lo == hi && (inc.lower == Bound::Exclusive || inc.upper == Bound::Exclusive)) {
35 return std::unexpected{RangeError{RangeErrorCode::EmptyOpenRange,
36 "single-point range cannot have an exclusive bound"}};
37 }
38 return std::tuple<T, T, RangeInclusion>{lo, hi, inc};
39}
40
41} // namespace detail
42
44template <UnitLike U, NumericValue T = double>
46public:
47 using unit_t = U;
48 using value_t = T;
50
51 constexpr UnitRangeValue() = default;
52
55 constexpr UnitRangeValue(bound_t lo,
56 bound_t hi,
57 const RangeInclusion inc = RangeInclusion::closed()) noexcept
58 : min_(std::move(lo)), max_(std::move(hi)), inclusion_(inc) {}
59
60 [[nodiscard]] static std::expected<UnitRangeValue, RangeError>
61 make(const bound_t& lo,
62 const bound_t& hi,
63 RangeInclusion inc = RangeInclusion::closed()) noexcept {
64 auto v = detail::validate_range<T>(lo.v, hi.v, inc);
65 if (!v) {
66 return std::unexpected{v.error()};
67 }
68 return UnitRangeValue{lo, hi, inc};
69 }
70
71 [[nodiscard]] static constexpr UnitRangeValue closed(bound_t lo, bound_t hi) noexcept {
72 return UnitRangeValue{std::move(lo), std::move(hi), RangeInclusion::closed()};
73 }
74 [[nodiscard]] static constexpr UnitRangeValue open(bound_t lo, bound_t hi) noexcept {
75 return UnitRangeValue{std::move(lo), std::move(hi), RangeInclusion::open()};
76 }
77 [[nodiscard]] static constexpr UnitRangeValue left_open(bound_t lo, bound_t hi) noexcept {
78 return UnitRangeValue{std::move(lo), std::move(hi), RangeInclusion::left_open()};
79 }
80 [[nodiscard]] static constexpr UnitRangeValue right_open(bound_t lo, bound_t hi) noexcept {
81 return UnitRangeValue{std::move(lo), std::move(hi), RangeInclusion::right_open()};
82 }
83
84 [[nodiscard]] constexpr bound_t min() const noexcept {
85 return min_;
86 }
87 [[nodiscard]] constexpr bound_t max() const noexcept {
88 return max_;
89 }
90 [[nodiscard]] constexpr RangeInclusion inclusion() const noexcept {
91 return inclusion_;
92 }
93
94 [[nodiscard]] constexpr bool contains(const bound_t& x) const noexcept {
95 return detail::contains_value<T>(min_.v, max_.v, inclusion_, x.v);
96 }
97
98 [[nodiscard]] constexpr bool contains(const UnitRangeValue& other) const noexcept {
99 // other.min_ inside this AND other.max_ inside this, accounting for
100 // each side's inclusivity vs the outer range's inclusivity.
101 const bool lo_ok = bound_inside_lower(other.min_.v, other.inclusion_.lower);
102 const bool hi_ok = bound_inside_upper(other.max_.v, other.inclusion_.upper);
103 return lo_ok && hi_ok;
104 }
105
106 [[nodiscard]] constexpr bool overlaps(const UnitRangeValue& other) const noexcept {
107 // Disjoint cases first.
108 if (max_.v < other.min_.v || other.max_.v < min_.v) {
109 return false;
110 }
111 if (max_.v == other.min_.v) {
112 return inclusion_.upper == Bound::Inclusive &&
113 other.inclusion_.lower == Bound::Inclusive;
114 }
115 if (other.max_.v == min_.v) {
116 return inclusion_.lower == Bound::Inclusive &&
117 other.inclusion_.upper == Bound::Inclusive;
118 }
119 return true;
120 }
121
122 [[nodiscard]] constexpr std::optional<UnitRangeValue>
123 intersect(const UnitRangeValue& other) const noexcept {
124 if (!overlaps(other)) {
125 return std::nullopt;
126 }
127 bound_t lo;
128 Bound lo_inc{};
129 if (min_.v > other.min_.v) {
130 lo = min_;
131 lo_inc = inclusion_.lower;
132 } else if (min_.v < other.min_.v) {
133 lo = other.min_;
134 lo_inc = other.inclusion_.lower;
135 } else {
136 lo = min_;
137 lo_inc =
138 (inclusion_.lower == Bound::Exclusive || other.inclusion_.lower == Bound::Exclusive)
139 ? Bound::Exclusive
140 : Bound::Inclusive;
141 }
142 bound_t hi;
143 Bound hi_inc{};
144 if (max_.v < other.max_.v) {
145 hi = max_;
146 hi_inc = inclusion_.upper;
147 } else if (max_.v > other.max_.v) {
148 hi = other.max_;
149 hi_inc = other.inclusion_.upper;
150 } else {
151 hi = max_;
152 hi_inc =
153 (inclusion_.upper == Bound::Exclusive || other.inclusion_.upper == Bound::Exclusive)
154 ? Bound::Exclusive
155 : Bound::Inclusive;
156 }
157 return UnitRangeValue{lo, hi, RangeInclusion{lo_inc, hi_inc}};
158 }
159
160 [[nodiscard]] std::string to_string() const;
161 [[nodiscard]] std::string to_formatted_string() const;
162
163 [[nodiscard]] friend constexpr bool operator==(const UnitRangeValue&,
164 const UnitRangeValue&) = default;
165
166private:
167 [[nodiscard]] constexpr bool bound_inside_lower(T other_v,
168 const Bound other_lower) const noexcept {
169 if (inclusion_.lower == Bound::Inclusive) {
170 return other_v >= min_.v;
171 }
172 if (other_lower == Bound::Exclusive) {
173 return other_v >= min_.v;
174 }
175 return other_v > min_.v;
176 }
177
178 [[nodiscard]] constexpr bool bound_inside_upper(T other_v,
179 const Bound other_upper) const noexcept {
180 if (inclusion_.upper == Bound::Inclusive) {
181 return other_v <= max_.v;
182 }
183 if (other_upper == Bound::Exclusive) {
184 return other_v <= max_.v;
185 }
186 return other_v < max_.v;
187 }
188
189 bound_t min_{};
190 bound_t max_{};
191 RangeInclusion inclusion_{RangeInclusion::closed()};
192};
193
195template <MeasureLike M, NumericValue T = double>
197public:
198 using measure_t = M;
199 using unit_t = M::base_unit_t;
200 using value_t = T;
202
203 constexpr MeasureRangeValue() = default;
204 constexpr MeasureRangeValue(bound_t lo,
205 bound_t hi,
206 const RangeInclusion inc = RangeInclusion::closed()) noexcept
207 : min_(std::move(lo)), max_(std::move(hi)), inclusion_(inc) {}
208
209 [[nodiscard]] static std::expected<MeasureRangeValue, RangeError>
210 make(const bound_t& lo,
211 const bound_t& hi,
212 RangeInclusion inc = RangeInclusion::closed()) noexcept {
213 auto v = detail::validate_range<T>(lo.v, hi.v, inc);
214 if (!v) {
215 return std::unexpected{v.error()};
216 }
217 return MeasureRangeValue{lo, hi, inc};
218 }
219
220 [[nodiscard]] static constexpr MeasureRangeValue closed(bound_t lo, bound_t hi) noexcept {
221 return MeasureRangeValue{std::move(lo), std::move(hi), RangeInclusion::closed()};
222 }
223 [[nodiscard]] static constexpr MeasureRangeValue open(bound_t lo, bound_t hi) noexcept {
224 return MeasureRangeValue{std::move(lo), std::move(hi), RangeInclusion::open()};
225 }
226 [[nodiscard]] static constexpr MeasureRangeValue left_open(bound_t lo, bound_t hi) noexcept {
227 return MeasureRangeValue{std::move(lo), std::move(hi), RangeInclusion::left_open()};
228 }
229 [[nodiscard]] static constexpr MeasureRangeValue right_open(bound_t lo, bound_t hi) noexcept {
230 return MeasureRangeValue{std::move(lo), std::move(hi), RangeInclusion::right_open()};
231 }
232
233 [[nodiscard]] constexpr bound_t min() const noexcept {
234 return min_;
235 }
236 [[nodiscard]] constexpr bound_t max() const noexcept {
237 return max_;
238 }
239 [[nodiscard]] constexpr RangeInclusion inclusion() const noexcept {
240 return inclusion_;
241 }
242
243 [[nodiscard]] constexpr bool contains(const bound_t& x) const noexcept {
244 return detail::contains_value<T>(min_.v, max_.v, inclusion_, x.v);
245 }
246
247 [[nodiscard]] constexpr bool contains(const MeasureRangeValue& other) const noexcept {
248 return as_unit_range().contains(other.as_unit_range());
249 }
250
251 [[nodiscard]] constexpr bool overlaps(const MeasureRangeValue& other) const noexcept {
252 return as_unit_range().overlaps(other.as_unit_range());
253 }
254
255 [[nodiscard]] constexpr std::optional<MeasureRangeValue>
256 intersect(const MeasureRangeValue& other) const noexcept {
257 if (auto r = as_unit_range().intersect(other.as_unit_range())) {
258 return MeasureRangeValue{
259 MeasureValue<M, T>{r->min().v}, MeasureValue<M, T>{r->max().v}, r->inclusion()};
260 }
261 return std::nullopt;
262 }
263
264 [[nodiscard]] std::string to_string() const;
265 [[nodiscard]] std::string to_formatted_string() const;
266
267 [[nodiscard]] friend constexpr bool operator==(const MeasureRangeValue&,
268 const MeasureRangeValue&) = default;
269
270private:
271 [[nodiscard]] constexpr UnitRangeValue<unit_t, T> as_unit_range() const noexcept {
272 return UnitRangeValue<unit_t, T>{min_.as_unit_value(), max_.as_unit_value(), inclusion_};
273 }
274
275 bound_t min_{};
276 bound_t max_{};
277 RangeInclusion inclusion_{RangeInclusion::closed()};
278};
279
280} // namespace dimval
Closed/open interval over a MeasureValue<M,T>.
Definition range.hpp:196
Closed/open interval over a UnitValue<U,T>.
Definition range.hpp:45
constexpr UnitRangeValue(bound_t lo, bound_t hi, const RangeInclusion inc=RangeInclusion::closed()) noexcept
Unchecked constructor — caller must guarantee min <= max and that an exclusive single-point range is ...
Definition range.hpp:55
Common concepts, error types, and small utilities used across dimval.
Bound
Inclusivity of a single range bound.
Definition core.hpp:50
MeasureValue<M,T>: a UnitValue tagged with an additional semantic measure.
A value carrying both a measure tag and a unit tag.
Definition measure.hpp:31
constexpr UnitValue< unit_t, T > as_unit_value() const noexcept
Drop the measure tag and expose the underlying UnitValue.
Definition measure.hpp:88
Inclusivity of both range bounds together.
Definition core.hpp:56
A value paired at the type level with a unit tag.
Definition unit.hpp:36
Concepts and helper accessors that drive the static side of dimval.
UnitValue<U,T>: a strongly-typed value carrying a compile-time unit tag.