Nix  2.93.0-dev
Lix: A modern, delicious implementation of the Nix package manager; unstable internal interfaces
Loading...
Searching...
No Matches
checked-arithmetic.hh
1#pragma once
6#include <compare>
7#include <concepts> // IWYU pragma: keep
8#include <exception>
9#include <ostream>
10#include <limits>
11#include <optional>
12#include <type_traits>
13
14namespace nix::checked {
15
16class DivideByZero : std::exception
17{};
18
22template<std::integral T>
23struct Checked
24{
25 using Inner = T;
26
27 // TODO: this must be a "trivial default constructor", which means it
28 // cannot set the value to NOT DO UB on uninit.
29 T value;
30
31 Checked() = default;
32 explicit Checked(T const value) : value{value} {}
33 Checked(Checked<T> const & other) = default;
34 Checked(Checked<T> && other) = default;
35 Checked<T> & operator=(Checked<T> const & other) = default;
36
37 std::strong_ordering operator<=>(Checked<T> const & other) const = default;
38 std::strong_ordering operator<=>(T const & other) const
39 {
40 return value <=> other;
41 }
42
43 explicit operator T() const
44 {
45 return value;
46 }
47
48 enum class OverflowKind {
49 NoOverflow,
50 Overflow,
51 DivByZero,
52 };
53
54 class Result
55 {
56 T value;
57 OverflowKind overflowed_;
58
59 public:
60 Result(T value, bool overflowed) : value{value}, overflowed_{overflowed ? OverflowKind::Overflow : OverflowKind::NoOverflow} {}
61 Result(T value, OverflowKind overflowed) : value{value}, overflowed_{overflowed} {}
62
63 bool operator==(Result other) const
64 {
65 return value == other.value && overflowed_ == other.overflowed_;
66 }
67
68 std::optional<T> valueChecked() const
69 {
70 if (overflowed_ != OverflowKind::NoOverflow) {
71 return std::nullopt;
72 } else {
73 return value;
74 }
75 }
76
82 T valueWrapping() const
83 {
84 if (overflowed_ == OverflowKind::DivByZero) {
85 throw DivideByZero{};
86 }
87 return value;
88 }
89
90 bool overflowed() const
91 {
92 return overflowed_ == OverflowKind::Overflow;
93 }
94
95 bool divideByZero() const
96 {
97 return overflowed_ == OverflowKind::DivByZero;
98 }
99 };
100
101 Result operator+(Checked<T> const other) const
102 {
103 return (*this) + other.value;
104 }
105 Result operator+(T const other) const
106 {
107 T result;
108 bool overflowed = __builtin_add_overflow(value, other, &result);
109 return Result{result, overflowed};
110 }
111
112 Result operator-(Checked<T> const other) const
113 {
114 return (*this) - other.value;
115 }
116 Result operator-(T const other) const
117 {
118 T result;
119 bool overflowed = __builtin_sub_overflow(value, other, &result);
120 return Result{result, overflowed};
121 }
122
123 Result operator*(Checked<T> const other) const
124 {
125 return (*this) * other.value;
126 }
127 Result operator*(T const other) const
128 {
129 T result;
130 bool overflowed = __builtin_mul_overflow(value, other, &result);
131 return Result{result, overflowed};
132 }
133
134 Result operator/(Checked<T> const other) const
135 {
136 return (*this) / other.value;
137 }
144 Result operator/(T const other) const
145 {
146 constexpr T const minV = std::numeric_limits<T>::min();
147
148 // It's only possible to overflow with signed division since doing so
149 // requires crossing the two's complement limits by MIN / -1 (since
150 // two's complement has one more in range in the negative direction
151 // than in the positive one).
152 if (std::is_signed<T>() && (value == minV && other == -1)) {
153 return Result{minV, true};
154 } else if (other == 0) {
155 return Result{0, OverflowKind::DivByZero};
156 } else {
157 T result = value / other;
158 return Result{result, false};
159 }
160 }
161};
162
163template<std::integral T>
164std::ostream & operator<<(std::ostream & ios, Checked<T> v)
165{
166 ios << v.value;
167 return ios;
168}
169
170}
Definition checked-arithmetic.hh:55
T valueWrapping() const
Definition checked-arithmetic.hh:82
Definition checked-arithmetic.hh:17
Definition checked-arithmetic.hh:24
Result operator/(T const other) const
Definition checked-arithmetic.hh:144