Skip to content

Commit 363df86

Browse files
committed
uint128: constify and constexperify
1 parent 2831b8b commit 363df86

1 file changed

Lines changed: 66 additions & 79 deletions

File tree

include/proxsuite/proxqp/utils/uint128_msvc.hpp

Lines changed: 66 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,14 @@
66

77
#include <cstdint>
88
#include <immintrin.h>
9+
#include <stdexcept>
910

1011
class uint128_t
1112
{
1213
public:
13-
uint64_t low;
14-
uint64_t high;
14+
uint64_t low = 0;
15+
uint64_t high = 0;
1516

16-
// --- Constructors ---
17-
constexpr uint128_t()
18-
: low(0)
19-
, high(0)
20-
{
21-
}
2217
constexpr uint128_t(uint64_t l)
2318
: low(l)
2419
, high(0)
@@ -31,18 +26,20 @@ class uint128_t
3126
}
3227

3328
// --- Type Conversions ---
34-
explicit operator bool() const { return low || high; }
35-
explicit operator uint64_t() const { return low; }
36-
explicit operator int64_t() const { return static_cast<int64_t>(low); }
29+
constexpr explicit operator bool() const { return low || high; }
30+
constexpr explicit operator uint64_t() const { return low; }
31+
constexpr explicit operator int64_t() const
32+
{
33+
return static_cast<int64_t>(low);
34+
}
3735

3836
// --- Arithmetic Operators ---
3937

40-
// Addition
4138
uint128_t operator+(const uint128_t& rhs) const
4239
{
43-
uint128_t result;
44-
unsigned char carry = _addcarry_u64(0, low, rhs.low, &result.low);
45-
_addcarry_u64(carry, high, rhs.high, &result.high);
40+
uint128_t result = 0;
41+
const unsigned char carry = _addcarry_u64(0, low, rhs.low, &result.low);
42+
std::ignore = _addcarry_u64(carry, high, rhs.high, &result.high);
4643
return result;
4744
}
4845

@@ -52,11 +49,10 @@ class uint128_t
5249
return *this;
5350
}
5451

55-
// Subtraction
5652
uint128_t operator-(const uint128_t& rhs) const
5753
{
58-
uint128_t result;
59-
unsigned char borrow = _subborrow_u64(0, low, rhs.low, &result.low);
54+
uint128_t result = 0;
55+
const unsigned char borrow = _subborrow_u64(0, low, rhs.low, &result.low);
6056
_subborrow_u64(borrow, high, rhs.high, &result.high);
6157
return result;
6258
}
@@ -67,16 +63,11 @@ class uint128_t
6763
return *this;
6864
}
6965

70-
// Multiplication
7166
uint128_t operator*(const uint128_t& rhs) const
7267
{
73-
uint64_t product_high;
74-
uint64_t product_low = _umul128(low, rhs.low, &product_high);
75-
76-
// The total high part is the high part of (low * rhs.low)
77-
// plus the cross terms (low * rhs.high) and (high * rhs.low)
68+
uint64_t product_high = 0;
69+
const uint64_t product_low = _umul128(low, rhs.low, &product_high);
7870
product_high += (low * rhs.high) + (high * rhs.low);
79-
8071
return uint128_t(product_low, product_high);
8172
}
8273

@@ -86,50 +77,33 @@ class uint128_t
8677
return *this;
8778
}
8879

89-
// Division (Note: Full 128-bit division is complex to implement purely with
90-
// intrinsics if the divisor is > 64 bits. This is a simplified version
91-
// handling common cases). For production-grade full 128/128 division, usage
92-
// of a library like Boost is strongly advised. However, if divisor fits in 64
93-
// bits, we can use _udiv128.
80+
// Division by a 64-bit divisor uses _udiv128. For a full 128-bit divisor,
81+
// falls back to binary long division.
9482
uint128_t operator/(const uint128_t& rhs) const
9583
{
84+
if (!rhs) {
85+
throw std::domain_error("uint128 division by zero");
86+
}
9687
if (rhs.high == 0) {
97-
// Optimization for 64-bit divisor
98-
uint64_t remainder;
99-
uint64_t quotient_high = 0; // High part of result
100-
uint64_t quotient_low;
101-
102-
// If our high part is distinct, we divide the high part first
10388
if (high > 0) {
104-
// This is slightly tricky with _udiv128 directly as it does 128/64
105-
// -> 64. Standard long division algorithm is safer here for the general
106-
// implementation. For simplicity in this snippet, we will fallback to a
107-
// naive loop or simple approximation OR promote strictly the 64-bit
108-
// divisor case which is most common:
109-
110-
quotient_high = high / rhs.low;
111-
uint64_t r_high = high % rhs.low;
112-
113-
quotient_low = _udiv128(r_high, low, rhs.low, &remainder);
89+
const uint64_t quotient_high = high / rhs.low;
90+
const uint64_t r_high = high % rhs.low;
91+
uint64_t remainder = 0;
92+
const uint64_t quotient_low =
93+
_udiv128(r_high, low, rhs.low, &remainder);
11494
return uint128_t(quotient_low, quotient_high);
115-
} else {
116-
return uint128_t(low / rhs.low, 0);
11795
}
96+
return uint128_t(low / rhs.low, 0);
11897
}
119-
// Fallback for full 128-bit divisor: Very slow basic binary long division
98+
// Binary long division for 128-bit divisor
12099
if (rhs > *this)
121100
return uint128_t(0);
122101
if (rhs == *this)
123102
return uint128_t(1);
124103

125104
uint128_t temp = *this;
126105
uint128_t quot = 0;
127-
uint128_t one = 1;
128-
129-
// This is slow O(N) division, acceptable for simple utility, bad for heavy
130-
// math
131106
while (temp >= rhs) {
132-
// Find shift
133107
uint128_t shift_rhs = rhs;
134108
uint128_t shift_count = 1;
135109
while ((shift_rhs.high & 0x8000000000000000) == 0 &&
@@ -143,7 +117,6 @@ class uint128_t
143117
return quot;
144118
}
145119

146-
// Modulus
147120
uint128_t operator%(const uint128_t& rhs) const
148121
{
149122
return *this - (*this / rhs) * rhs;
@@ -156,10 +129,9 @@ class uint128_t
156129
}
157130

158131
// --- Bitwise Operators ---
159-
uint128_t operator<<(int shift) const
132+
constexpr uint128_t operator<<(int shift) const
160133
{
161-
shift &= 127; // Mask the shift amount to imitate native hardware behavior
162-
// (modulo 128)
134+
shift &= 127; // wrap modulo 128, matching hardware behavior
163135
if (shift == 0)
164136
return *this;
165137
if (shift >= 64) {
@@ -168,10 +140,9 @@ class uint128_t
168140
return uint128_t((low << shift), (high << shift) | (low >> (64 - shift)));
169141
}
170142

171-
uint128_t operator>>(int shift) const
143+
constexpr uint128_t operator>>(int shift) const
172144
{
173-
shift &= 127; // Mask the shift amount to imitate native hardware behavior
174-
// (modulo 128)
145+
shift &= 127; // wrap modulo 128, matching hardware behavior
175146
if (shift == 0)
176147
return *this;
177148
if (shift >= 64) {
@@ -181,56 +152,72 @@ class uint128_t
181152
}
182153

183154
// --- Shift by uint128_t Overloads ---
184-
uint128_t operator>>(const uint128_t& shift) const
155+
constexpr uint128_t operator>>(const uint128_t& shift) const
185156
{
186-
// If shift amount is >= 128, the result behavior mimics hardware (modulo
187-
// 128)
188157
return *this >> static_cast<int>(shift.low);
189158
}
190159

191-
uint128_t operator<<(const uint128_t& shift) const
160+
constexpr uint128_t operator<<(const uint128_t& shift) const
192161
{
193-
// If shift amount is >= 128, the result behavior mimics hardware (modulo
194-
// 128)
195162
return *this << static_cast<int>(shift.low);
196163
}
197164

198-
uint128_t& operator<<=(int shift)
165+
constexpr uint128_t& operator<<=(int shift)
199166
{
200167
*this = *this << shift;
201168
return *this;
202169
}
203-
uint128_t& operator>>=(int shift)
170+
constexpr uint128_t& operator>>=(int shift)
204171
{
205172
*this = *this >> shift;
206173
return *this;
207174
}
208175

209-
uint128_t operator|(const uint128_t& rhs) const
176+
constexpr uint128_t& operator<<=(const uint128_t& shift)
177+
{
178+
*this = *this << shift;
179+
return *this;
180+
}
181+
constexpr uint128_t& operator>>=(const uint128_t& shift)
182+
{
183+
*this = *this >> shift;
184+
return *this;
185+
}
186+
187+
constexpr uint128_t operator|(const uint128_t& rhs) const
210188
{
211189
return uint128_t(low | rhs.low, high | rhs.high);
212190
}
213-
uint128_t operator&(const uint128_t& rhs) const
191+
constexpr uint128_t operator&(const uint128_t& rhs) const
214192
{
215193
return uint128_t(low & rhs.low, high & rhs.high);
216194
}
217-
uint128_t operator^(const uint128_t& rhs) const
195+
constexpr uint128_t operator^(const uint128_t& rhs) const
218196
{
219197
return uint128_t(low ^ rhs.low, high ^ rhs.high);
220198
}
221-
uint128_t operator~() const { return uint128_t(~low, ~high); }
199+
constexpr uint128_t operator~() const { return uint128_t(~low, ~high); }
222200

223201
// --- Comparison Operators ---
224-
bool operator==(const uint128_t& rhs) const
202+
constexpr bool operator==(const uint128_t& rhs) const
225203
{
226204
return low == rhs.low && high == rhs.high;
227205
}
228-
bool operator!=(const uint128_t& rhs) const { return !(*this == rhs); }
229-
bool operator<(const uint128_t& rhs) const
206+
constexpr bool operator!=(const uint128_t& rhs) const
207+
{
208+
return !(*this == rhs);
209+
}
210+
constexpr bool operator<(const uint128_t& rhs) const
230211
{
231212
return high < rhs.high || (high == rhs.high && low < rhs.low);
232213
}
233-
bool operator>(const uint128_t& rhs) const { return rhs < *this; }
234-
bool operator<=(const uint128_t& rhs) const { return !(*this > rhs); }
235-
bool operator>=(const uint128_t& rhs) const { return !(*this < rhs); }
214+
constexpr bool operator>(const uint128_t& rhs) const { return rhs < *this; }
215+
constexpr bool operator<=(const uint128_t& rhs) const
216+
{
217+
return !(*this > rhs);
218+
}
219+
constexpr bool operator>=(const uint128_t& rhs) const
220+
{
221+
return !(*this < rhs);
222+
}
236223
};

0 commit comments

Comments
 (0)