Skip to content

Commit e69f3fa

Browse files
authored
Merge pull request #401 from cppalliance/conversion
Make cross type conversions implicit
2 parents e6e59c0 + 7e5ae9f commit e69f3fa

10 files changed

Lines changed: 487 additions & 60 deletions

File tree

doc/modules/ROOT/pages/examples.adoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ From BOOST_INT128_INT128_C(min): -170141183460469231731687303715884105728
4545
=== Default and Copy Construction ===
4646
Default constructed: 0
4747
Copy constructed: 340282366920938463463374607431768211455
48+
49+
=== Floating-Point Construction ===
50+
uint128_t from 12345.9 (truncated): 12345
51+
int128_t from -12345.9 (truncated toward zero): -12345
52+
uint128_t from 2^100: 1267650600228229401496703205376
53+
54+
=== Floating-Point Edge Cases ===
55+
uint128_t from NaN: 0
56+
int128_t from NaN: 0
57+
uint128_t from -1.0 (clamped to zero): 0
58+
uint128_t from +infinity (saturates to UINT128_MAX): 340282366920938463463374607431768211455
59+
int128_t from 1e40 (saturates to INT128_MAX): 170141183460469231731687303715884105727
60+
int128_t from -1e40 (saturates to INT128_MIN): -170141183460469231731687303715884105728
4861
----
4962
====
5063

doc/modules/ROOT/pages/int128_t.adoc

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ struct int128_t
9292
BOOST_INT128_HOST_DEVICE constexpr int128_t& operator=(const int128_t&) noexcept = default;
9393
BOOST_INT128_HOST_DEVICE constexpr int128_t& operator=(int128_t&&) noexcept = default;
9494
95-
BOOST_INT128_HOST_DEVICE explicit constexpr int128_t(const uint128_t& v) noexcept;
95+
BOOST_INT128_HOST_DEVICE constexpr int128_t(const uint128_t& v) noexcept;
9696
9797
// Construct from integral types
9898
BOOST_INT128_HOST_DEVICE constexpr int128_t(const std::int64_t hi, const std::uint64_t lo) noexcept;
@@ -112,14 +112,21 @@ struct int128_t
112112
BOOST_INT128_HOST_DEVICE constexpr int128_t(const detail::builtin_u128 v) noexcept;
113113
114114
#endif // BOOST_INT128_HAS_INT128
115+
116+
// Construct from floating-point types
117+
template <BOOST_INT128_FLOATING_POINT_CONCEPT Float>
118+
BOOST_INT128_HOST_DEVICE constexpr int128_t(Float f) noexcept;
115119
};
116120
117121
} // namespace int128
118122
} // namespace boost
119123
----
120124

121-
All constructors are only defined for integers and are subject to mixed sign limitations discussed xref:int128_t.adoc#i128_operator_behavior[above].
122-
None are marked `explicit` in order to match the implicit conversion behavior of the built-in integer types.
125+
None of the constructors are marked `explicit` in order to match the implicit conversion behavior of the built-in integer types.
126+
Integer constructors are subject to mixed sign limitations discussed xref:int128_t.adoc#i128_operator_behavior[above].
127+
128+
The floating-point constructor truncates toward zero, matching `static_cast<__int128>(f)`.
129+
Edge cases mirror libgcc's `__fixXfti`: NaN yields zero, values `>= 2^127` saturate to `INT128_MAX`, and values `<= -2^127` saturate to `INT128_MIN`.
123130

124131
[#i128_conversions]
125132
== Conversions
@@ -134,35 +141,36 @@ struct int128_t
134141
...
135142
136143
// Integer conversion operators
137-
BOOST_INT128_HOST_DEVICE constexpr operator bool() const noexcept;
144+
BOOST_INT128_HOST_DEVICE explicit constexpr operator bool() const noexcept;
138145
139146
template <BOOST_INT128_SIGNED_INTEGER_CONCEPT SignedInteger>
140-
BOOST_INT128_HOST_DEVICE explicit constexpr operator SignedInteger() const noexcept;
147+
BOOST_INT128_HOST_DEVICE constexpr operator SignedInteger() const noexcept;
141148
142149
template <BOOST_INT128_UNSIGNED_INTEGER_CONCEPT UnsignedInteger>
143-
BOOST_INT128_HOST_DEVICE explicit constexpr operator UnsignedInteger() const noexcept;
150+
BOOST_INT128_HOST_DEVICE constexpr operator UnsignedInteger() const noexcept;
144151
145152
#ifdef BOOST_INT128_HAS_INT128
146153
147-
BOOST_INT128_HOST_DEVICE explicit constexpr operator detail::builtin_i128() const noexcept;
154+
BOOST_INT128_HOST_DEVICE constexpr operator detail::builtin_i128() const noexcept;
148155
149-
BOOST_INT128_HOST_DEVICE explicit constexpr operator detail::builtin_u128() const noexcept;
156+
BOOST_INT128_HOST_DEVICE constexpr operator detail::builtin_u128() const noexcept;
150157
151158
#endif // BOOST_INT128_HAS_INT128
152159
153-
// Conversion to float
154-
BOOST_INT128_HOST_DEVICE explicit constexpr operator float() const noexcept;
155-
BOOST_INT128_HOST_DEVICE explicit constexpr operator double() const noexcept;
156-
explicit constexpr operator long double() const noexcept; // There are no long doubles on device
160+
// Conversion to floating point
161+
BOOST_INT128_HOST_DEVICE constexpr operator float() const noexcept;
162+
BOOST_INT128_HOST_DEVICE constexpr operator double() const noexcept;
163+
constexpr operator long double() const noexcept; // There are no long doubles on device
157164
};
158165
159166
} // namespace int128
160167
} // namespace boost
161168
----
162169

170+
All conversion operators except `operator bool()` are implicit to match the behavior of built-in integer types.
171+
`operator bool()` is explicit so that an `int128_t` cannot accidentally bind to a `bool` parameter; contextual conversions (`if (x)`, `!x`, etc.) still work.
163172
Conversions to unsigned integers are subject to mixed sign limitations discussed xref:int128_t.adoc#i128_operator_behavior[above].
164-
Conversion to `bool` is not marked explicit to match the behavior of built-in integer types.
165-
Conversions to floating point types may not be lossless depending on the value of the `int128_t` at time of conversion,
173+
Conversions to floating-point types may not be lossless depending on the value of the `int128_t` at time of conversion,
166174
as the number of digits it represents can exceed the precision of the significand in floating point types.
167175

168176
[#i128_comparison_operators]

doc/modules/ROOT/pages/uint128_t.adoc

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ struct uint128_t
9393
BOOST_INT128_HOST_DEVICE constexpr uint128_t& operator=(const uint128_t&) noexcept = default;
9494
BOOST_INT128_HOST_DEVICE constexpr uint128_t& operator=(uint128_t&&) noexcept = default;
9595
96-
BOOST_INT128_HOST_DEVICE explicit constexpr uint128_t(const int128_t& v) noexcept;
96+
BOOST_INT128_HOST_DEVICE constexpr uint128_t(const int128_t& v) noexcept;
9797
9898
// Construct from integral types
9999
BOOST_INT128_HOST_DEVICE constexpr uint128_t(const std::uint64_t hi, const std::uint64_t lo) noexcept;
@@ -113,14 +113,21 @@ struct uint128_t
113113
BOOST_INT128_HOST_DEVICE constexpr uint128_t(const detail::builtin_u128 v) noexcept;
114114
115115
#endif // BOOST_INT128_HAS_INT128
116+
117+
// Construct from floating-point types
118+
template <BOOST_INT128_FLOATING_POINT_CONCEPT Float>
119+
BOOST_INT128_HOST_DEVICE constexpr uint128_t(Float f) noexcept;
116120
};
117121
118122
} // namespace int128
119123
} // namespace boost
120124
----
121125

122-
All constructors are only defined for integers and are subject to mixed sign limitations discussed xref:uint128_t.adoc#u128_operator_behavior[above].
123-
None are marked `explicit` in order to match the implicit conversion behavior of the built-in integer types.
126+
None of the constructors are marked `explicit` in order to match the implicit conversion behavior of the built-in integer types.
127+
Integer constructors are subject to mixed sign limitations discussed xref:uint128_t.adoc#u128_operator_behavior[above].
128+
129+
The floating-point constructor truncates toward zero, matching `static_cast<unsigned __int128>(f)`.
130+
Edge cases mirror libgcc's `__fixunsXfti`: NaN and negative values yield zero, and values `>= 2^128` (including positive infinity) saturate to `UINT128_MAX`.
124131

125132
[#u128_conversions]
126133
== Conversions
@@ -135,35 +142,36 @@ struct uint128_t
135142
...
136143
137144
// Integer conversion operators
138-
BOOST_INT128_HOST_DEVICE constexpr operator bool() const noexcept;
145+
BOOST_INT128_HOST_DEVICE explicit constexpr operator bool() const noexcept;
139146
140147
template <BOOST_INT128_SIGNED_INTEGER_CONCEPT SignedInteger>
141-
BOOST_INT128_HOST_DEVICE explicit constexpr operator SignedInteger() const noexcept;
148+
BOOST_INT128_HOST_DEVICE constexpr operator SignedInteger() const noexcept;
142149
143150
template <BOOST_INT128_UNSIGNED_INTEGER_CONCEPT UnsignedInteger>
144-
BOOST_INT128_HOST_DEVICE explicit constexpr operator UnsignedInteger() const noexcept;
151+
BOOST_INT128_HOST_DEVICE constexpr operator UnsignedInteger() const noexcept;
145152
146153
#ifdef BOOST_INT128_HAS_INT128
147154
148-
BOOST_INT128_HOST_DEVICE explicit constexpr operator detail::builtin_i128() const noexcept;
155+
BOOST_INT128_HOST_DEVICE constexpr operator detail::builtin_i128() const noexcept;
149156
150-
BOOST_INT128_HOST_DEVICE explicit constexpr operator detail::builtin_u128() const noexcept;
157+
BOOST_INT128_HOST_DEVICE constexpr operator detail::builtin_u128() const noexcept;
151158
152159
#endif // BOOST_INT128_HAS_INT128
153160
154-
// Conversion to float
155-
BOOST_INT128_HOST_DEVICE explicit constexpr operator float() const noexcept;
156-
BOOST_INT128_HOST_DEVICE explicit constexpr operator double() const noexcept;
157-
explicit constexpr operator long double() const noexcept; // There are no long doubles on device
161+
// Conversion to floating point
162+
BOOST_INT128_HOST_DEVICE constexpr operator float() const noexcept;
163+
BOOST_INT128_HOST_DEVICE constexpr operator double() const noexcept;
164+
constexpr operator long double() const noexcept; // There are no long doubles on device
158165
};
159166
160167
} // namespace int128
161168
} // namespace boost
162169
----
163170

171+
All conversion operators except `operator bool()` are implicit to match the behavior of built-in integer types.
172+
`operator bool()` is explicit so that a `uint128_t` cannot accidentally bind to a `bool` parameter; contextual conversions (`if (x)`, `!x`, etc.) still work.
164173
Conversions to signed integers are subject to mixed sign limitations discussed xref:uint128_t.adoc#u128_operator_behavior[above].
165-
Conversion to `bool` is not marked explicit to match the behavior of built-in integer types.
166-
Conversions to floating point types may not be lossless depending on the value of the `uint128_t` at time of conversion,
174+
Conversions to floating-point types may not be lossless depending on the value of the `uint128_t` at time of conversion,
167175
as the number of digits it represents can exceed the precision of the significand in floating point types.
168176

169177
[#u128_comparison_operators]

examples/construction.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,5 +90,45 @@ int main()
9090
const uint128_t copied {from_macro};
9191
std::cout << "Copy constructed: " << copied << std::endl;
9292

93+
std::cout << "\n=== Floating-Point Construction ===" << std::endl;
94+
95+
// Floating-point construction truncates toward zero, matching the behavior of
96+
// a static_cast from a floating-point type to a built-in integer.
97+
constexpr uint128_t from_double {12345.9};
98+
std::cout << "uint128_t from 12345.9 (truncated): " << from_double << std::endl;
99+
100+
constexpr int128_t from_negative_double {-12345.9};
101+
std::cout << "int128_t from -12345.9 (truncated toward zero): " << from_negative_double << std::endl;
102+
103+
// Values that exceed the 64-bit range are routed through the full 128-bit decomposition.
104+
const double two_to_the_100 {1.2676506002282294e30}; // 2^100
105+
const uint128_t large_from_double {two_to_the_100};
106+
std::cout << "uint128_t from 2^100: " << large_from_double << std::endl;
107+
108+
std::cout << "\n=== Floating-Point Edge Cases ===" << std::endl;
109+
110+
// NaN yields zero for both signed and unsigned (mirrors libgcc's __fix(uns)Xfti).
111+
const double nan_value {std::numeric_limits<double>::quiet_NaN()};
112+
const uint128_t unsigned_from_nan {nan_value};
113+
const int128_t signed_from_nan {nan_value};
114+
std::cout << "uint128_t from NaN: " << unsigned_from_nan << std::endl;
115+
std::cout << "int128_t from NaN: " << signed_from_nan << std::endl;
116+
117+
// Negative values are clamped to zero when constructing uint128_t.
118+
const uint128_t unsigned_from_negative {-1.0};
119+
std::cout << "uint128_t from -1.0 (clamped to zero): " << unsigned_from_negative << std::endl;
120+
121+
// Positive overflow saturates: anything >= 2^128 (including +infinity) becomes UINT128_MAX.
122+
const double infinity {std::numeric_limits<double>::infinity()};
123+
const uint128_t saturated_unsigned {infinity};
124+
std::cout << "uint128_t from +infinity (saturates to UINT128_MAX): " << saturated_unsigned << std::endl;
125+
126+
// For int128_t, values >= 2^127 saturate to INT128_MAX and values <= -2^127 saturate to INT128_MIN.
127+
const double huge {1e40}; // Well beyond 2^127 (~ 1.7e38)
128+
const int128_t saturated_positive {huge};
129+
const int128_t saturated_negative {-huge};
130+
std::cout << "int128_t from 1e40 (saturates to INT128_MAX): " << saturated_positive << std::endl;
131+
std::cout << "int128_t from -1e40 (saturates to INT128_MIN): " << saturated_negative << std::endl;
132+
93133
return 0;
94134
}

include/boost/int128/detail/conversions.hpp

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,6 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t::uint128_t(const int128_t& v) noexc
3838

3939
#endif // BOOST_INT128_ENDIAN_LITTLE_BYTE
4040

41-
//=====================================
42-
// Conversion Operators
43-
//=====================================
44-
45-
BOOST_INT128_HOST_DEVICE constexpr int128_t::operator uint128_t() const noexcept
46-
{
47-
return uint128_t{static_cast<std::uint64_t>(this->high), static_cast<std::uint64_t>(this->low)};
48-
}
49-
50-
BOOST_INT128_HOST_DEVICE constexpr uint128_t::operator int128_t() const noexcept
51-
{
52-
return int128_t{static_cast<std::int64_t>(this->high), static_cast<std::uint64_t>(this->low)};
53-
}
54-
5541
//=====================================
5642
// Comparison Operators
5743
//=====================================

include/boost/int128/detail/int128_imp.hpp

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@ int128_t
5656
constexpr int128_t& operator=(int128_t&&) noexcept = default;
5757

5858
// Requires a conversion file to be implemented
59-
BOOST_INT128_HOST_DEVICE explicit constexpr int128_t(const uint128_t& v) noexcept;
60-
BOOST_INT128_HOST_DEVICE explicit constexpr operator uint128_t() const noexcept;
59+
BOOST_INT128_HOST_DEVICE constexpr int128_t(const uint128_t& v) noexcept;
6160

6261
// Construct from integral types
6362
#if BOOST_INT128_ENDIAN_LITTLE_BYTE
@@ -96,32 +95,36 @@ int128_t
9695

9796
#endif // BOOST_INT128_ENDIAN_LITTLE_BYTE
9897

98+
// Construct from floating-point types
99+
template <BOOST_INT128_DEFAULTED_FLOATING_POINT_CONCEPT>
100+
BOOST_INT128_HOST_DEVICE constexpr int128_t(Float f) noexcept;
101+
99102
// Integer Conversion operators
100103
BOOST_INT128_HOST_DEVICE explicit constexpr operator bool() const noexcept { return low || high; }
101104

102105
template <BOOST_INT128_DEFAULTED_SIGNED_INTEGER_CONCEPT>
103-
BOOST_INT128_HOST_DEVICE explicit constexpr operator SignedInteger() const noexcept { return static_cast<SignedInteger>(low); }
106+
BOOST_INT128_HOST_DEVICE constexpr operator SignedInteger() const noexcept { return static_cast<SignedInteger>(low); }
104107

105108
template <BOOST_INT128_DEFAULTED_UNSIGNED_INTEGER_CONCEPT>
106-
BOOST_INT128_HOST_DEVICE explicit constexpr operator UnsignedInteger() const noexcept { return static_cast<UnsignedInteger>(low); }
109+
BOOST_INT128_HOST_DEVICE constexpr operator UnsignedInteger() const noexcept { return static_cast<UnsignedInteger>(low); }
107110

108111
#if defined(BOOST_INT128_HAS_INT128) || defined(BOOST_INT128_HAS_MSVC_INT128)
109112

110-
BOOST_INT128_HOST_DEVICE explicit BOOST_INT128_BUILTIN_CONSTEXPR operator detail::builtin_i128() const noexcept { return static_cast<detail::builtin_i128>(static_cast<detail::builtin_u128>(high) << static_cast<detail::builtin_u128>(64)) | static_cast<detail::builtin_i128>(low); }
113+
BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR operator detail::builtin_i128() const noexcept { return static_cast<detail::builtin_i128>(static_cast<detail::builtin_u128>(high) << static_cast<detail::builtin_u128>(64)) | static_cast<detail::builtin_i128>(low); }
111114

112-
BOOST_INT128_HOST_DEVICE explicit BOOST_INT128_BUILTIN_CONSTEXPR operator detail::builtin_u128() const noexcept { return (static_cast<detail::builtin_u128>(high) << static_cast<detail::builtin_u128>(64)) | static_cast<detail::builtin_u128>(low); }
115+
BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR operator detail::builtin_u128() const noexcept { return (static_cast<detail::builtin_u128>(high) << static_cast<detail::builtin_u128>(64)) | static_cast<detail::builtin_u128>(low); }
113116

114117
#endif // BOOST_INT128_HAS_INT128
115118

116119
// Conversion to float
117120
// This is basically the same as ldexp(static_cast<T>(high), 64) + static_cast<T>(low),
118121
// but can be constexpr at C++11 instead of C++26
119-
BOOST_INT128_HOST_DEVICE explicit constexpr operator float() const noexcept;
120-
BOOST_INT128_HOST_DEVICE explicit constexpr operator double() const noexcept;
122+
BOOST_INT128_HOST_DEVICE constexpr operator float() const noexcept;
123+
BOOST_INT128_HOST_DEVICE constexpr operator double() const noexcept;
121124

122125
// Long double does not exist on device
123126
#if !(defined(__CUDACC__) && defined(BOOST_INT128_ENABLE_CUDA))
124-
explicit constexpr operator long double() const noexcept;
127+
constexpr operator long double() const noexcept;
125128
#endif
126129

127130
// Compound Or
@@ -306,6 +309,62 @@ constexpr int128_t::operator long double() const noexcept
306309

307310
#endif
308311

312+
//=====================================
313+
// Float Construction
314+
//=====================================
315+
316+
// Inverse of operator(Float).
317+
// NaN -> 0;
318+
// f >= 2^127 -> INT128_MAX;
319+
// f < -2^127 -> INT128_MIN.
320+
template <BOOST_INT128_FLOATING_POINT_CONCEPT>
321+
BOOST_INT128_HOST_DEVICE constexpr int128_t::int128_t(Float f) noexcept
322+
{
323+
constexpr Float two_32 {static_cast<Float>(UINT64_C(1) << 32)};
324+
constexpr Float two_64 {two_32 * two_32};
325+
constexpr Float two_127 {two_64 * static_cast<Float>(UINT64_C(1) << 63)};
326+
327+
// NaN: leave default-initialized (zero). NaN compares false to everything,
328+
// so neither >= 0 nor <= 0 holds.
329+
if (!(f >= Float{0}) && !(f <= Float{0}))
330+
{
331+
return;
332+
}
333+
334+
if (f >= two_127)
335+
{
336+
high = (std::numeric_limits<std::int64_t>::max)();
337+
low = UINT64_MAX;
338+
return;
339+
}
340+
341+
if (f <= -two_127)
342+
{
343+
high = (std::numeric_limits<std::int64_t>::min)();
344+
low = UINT64_C(0);
345+
return;
346+
}
347+
348+
const bool negative {f < Float{0}};
349+
const Float abs_f {negative ? -f : f};
350+
351+
std::uint64_t h {static_cast<std::uint64_t>(abs_f / two_64)};
352+
const Float remainder {abs_f - static_cast<Float>(h) * two_64};
353+
std::uint64_t l {static_cast<std::uint64_t>(remainder)};
354+
355+
if (negative)
356+
{
357+
// Two's complement negation of (h, l): new_l = -l (with wraparound),
358+
// new_h = ~h if a borrow occurred (l != 0), else ~h + 1.
359+
const bool low_was_zero {l == UINT64_C(0)};
360+
l = UINT64_C(0) - l;
361+
h = ~h + (low_was_zero ? UINT64_C(1) : UINT64_C(0));
362+
}
363+
364+
high = static_cast<std::int64_t>(h);
365+
low = l;
366+
}
367+
309368
//=====================================
310369
// Unary Operators
311370
//=====================================

include/boost/int128/detail/traits.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,12 @@ using evaluation_type_t = std::conditional_t<sizeof(T) <= sizeof(std::uint32_t),
7171
#define BOOST_INT128_DEFAULTED_SIGNED_INTEGER_CONCEPT typename SignedInteger, std::enable_if_t<detail::is_signed_integer_v<SignedInteger>, bool> = true
7272
#define BOOST_INT128_DEFAULTED_UNSIGNED_INTEGER_CONCEPT typename UnsignedInteger, std::enable_if_t<detail::is_unsigned_integer_v<UnsignedInteger>, bool> = true
7373
#define BOOST_INT128_DEFAULTED_INTEGER_CONCEPT typename Integer, std::enable_if_t<detail::is_any_integer_v<Integer>, bool> = true
74+
#define BOOST_INT128_DEFAULTED_FLOATING_POINT_CONCEPT typename Float, std::enable_if_t<std::is_floating_point<Float>::value, bool> = true
7475

7576
#define BOOST_INT128_SIGNED_INTEGER_CONCEPT typename SignedInteger, std::enable_if_t<detail::is_signed_integer_v<SignedInteger>, bool>
7677
#define BOOST_INT128_UNSIGNED_INTEGER_CONCEPT typename UnsignedInteger, std::enable_if_t<detail::is_unsigned_integer_v<UnsignedInteger>, bool>
7778
#define BOOST_INT128_INTEGER_CONCEPT typename Integer, std::enable_if_t<detail::is_any_integer_v<Integer>, bool>
79+
#define BOOST_INT128_FLOATING_POINT_CONCEPT typename Float, std::enable_if_t<std::is_floating_point<Float>::value, bool>
7880

7981
#if defined(BOOST_INT128_HAS_INT128) || defined(BOOST_INT128_HAS_MSVC_INT128)
8082

0 commit comments

Comments
 (0)