Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions doc/modules/ROOT/pages/examples.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ From BOOST_INT128_INT128_C(min): -170141183460469231731687303715884105728
=== Default and Copy Construction ===
Default constructed: 0
Copy constructed: 340282366920938463463374607431768211455

=== Floating-Point Construction ===
uint128_t from 12345.9 (truncated): 12345
int128_t from -12345.9 (truncated toward zero): -12345
uint128_t from 2^100: 1267650600228229401496703205376

=== Floating-Point Edge Cases ===
uint128_t from NaN: 0
int128_t from NaN: 0
uint128_t from -1.0 (clamped to zero): 0
uint128_t from +infinity (saturates to UINT128_MAX): 340282366920938463463374607431768211455
int128_t from 1e40 (saturates to INT128_MAX): 170141183460469231731687303715884105727
int128_t from -1e40 (saturates to INT128_MIN): -170141183460469231731687303715884105728
----
====

Expand Down
36 changes: 22 additions & 14 deletions doc/modules/ROOT/pages/int128_t.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ struct int128_t
BOOST_INT128_HOST_DEVICE constexpr int128_t& operator=(const int128_t&) noexcept = default;
BOOST_INT128_HOST_DEVICE constexpr int128_t& operator=(int128_t&&) noexcept = default;

BOOST_INT128_HOST_DEVICE explicit constexpr int128_t(const uint128_t& v) noexcept;
BOOST_INT128_HOST_DEVICE constexpr int128_t(const uint128_t& v) noexcept;

// Construct from integral types
BOOST_INT128_HOST_DEVICE constexpr int128_t(const std::int64_t hi, const std::uint64_t lo) noexcept;
Expand All @@ -112,14 +112,21 @@ struct int128_t
BOOST_INT128_HOST_DEVICE constexpr int128_t(const detail::builtin_u128 v) noexcept;

#endif // BOOST_INT128_HAS_INT128

// Construct from floating-point types
template <BOOST_INT128_FLOATING_POINT_CONCEPT Float>
BOOST_INT128_HOST_DEVICE constexpr int128_t(Float f) noexcept;
};

} // namespace int128
} // namespace boost
----

All constructors are only defined for integers and are subject to mixed sign limitations discussed xref:int128_t.adoc#i128_operator_behavior[above].
None are marked `explicit` in order to match the implicit conversion behavior of the built-in integer types.
None of the constructors are marked `explicit` in order to match the implicit conversion behavior of the built-in integer types.
Integer constructors are subject to mixed sign limitations discussed xref:int128_t.adoc#i128_operator_behavior[above].

The floating-point constructor truncates toward zero, matching `static_cast<__int128>(f)`.
Edge cases mirror libgcc's `__fixXfti`: NaN yields zero, values `>= 2^127` saturate to `INT128_MAX`, and values `<= -2^127` saturate to `INT128_MIN`.

[#i128_conversions]
== Conversions
Expand All @@ -134,35 +141,36 @@ struct int128_t
...

// Integer conversion operators
BOOST_INT128_HOST_DEVICE constexpr operator bool() const noexcept;
BOOST_INT128_HOST_DEVICE explicit constexpr operator bool() const noexcept;

template <BOOST_INT128_SIGNED_INTEGER_CONCEPT SignedInteger>
BOOST_INT128_HOST_DEVICE explicit constexpr operator SignedInteger() const noexcept;
BOOST_INT128_HOST_DEVICE constexpr operator SignedInteger() const noexcept;

template <BOOST_INT128_UNSIGNED_INTEGER_CONCEPT UnsignedInteger>
BOOST_INT128_HOST_DEVICE explicit constexpr operator UnsignedInteger() const noexcept;
BOOST_INT128_HOST_DEVICE constexpr operator UnsignedInteger() const noexcept;

#ifdef BOOST_INT128_HAS_INT128

BOOST_INT128_HOST_DEVICE explicit constexpr operator detail::builtin_i128() const noexcept;
BOOST_INT128_HOST_DEVICE constexpr operator detail::builtin_i128() const noexcept;

BOOST_INT128_HOST_DEVICE explicit constexpr operator detail::builtin_u128() const noexcept;
BOOST_INT128_HOST_DEVICE constexpr operator detail::builtin_u128() const noexcept;

#endif // BOOST_INT128_HAS_INT128

// Conversion to float
BOOST_INT128_HOST_DEVICE explicit constexpr operator float() const noexcept;
BOOST_INT128_HOST_DEVICE explicit constexpr operator double() const noexcept;
explicit constexpr operator long double() const noexcept; // There are no long doubles on device
// Conversion to floating point
BOOST_INT128_HOST_DEVICE constexpr operator float() const noexcept;
BOOST_INT128_HOST_DEVICE constexpr operator double() const noexcept;
constexpr operator long double() const noexcept; // There are no long doubles on device
};

} // namespace int128
} // namespace boost
----

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

[#i128_comparison_operators]
Expand Down
36 changes: 22 additions & 14 deletions doc/modules/ROOT/pages/uint128_t.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ struct uint128_t
BOOST_INT128_HOST_DEVICE constexpr uint128_t& operator=(const uint128_t&) noexcept = default;
BOOST_INT128_HOST_DEVICE constexpr uint128_t& operator=(uint128_t&&) noexcept = default;

BOOST_INT128_HOST_DEVICE explicit constexpr uint128_t(const int128_t& v) noexcept;
BOOST_INT128_HOST_DEVICE constexpr uint128_t(const int128_t& v) noexcept;

// Construct from integral types
BOOST_INT128_HOST_DEVICE constexpr uint128_t(const std::uint64_t hi, const std::uint64_t lo) noexcept;
Expand All @@ -113,14 +113,21 @@ struct uint128_t
BOOST_INT128_HOST_DEVICE constexpr uint128_t(const detail::builtin_u128 v) noexcept;

#endif // BOOST_INT128_HAS_INT128

// Construct from floating-point types
template <BOOST_INT128_FLOATING_POINT_CONCEPT Float>
BOOST_INT128_HOST_DEVICE constexpr uint128_t(Float f) noexcept;
};

} // namespace int128
} // namespace boost
----

All constructors are only defined for integers and are subject to mixed sign limitations discussed xref:uint128_t.adoc#u128_operator_behavior[above].
None are marked `explicit` in order to match the implicit conversion behavior of the built-in integer types.
None of the constructors are marked `explicit` in order to match the implicit conversion behavior of the built-in integer types.
Integer constructors are subject to mixed sign limitations discussed xref:uint128_t.adoc#u128_operator_behavior[above].

The floating-point constructor truncates toward zero, matching `static_cast<unsigned __int128>(f)`.
Edge cases mirror libgcc's `__fixunsXfti`: NaN and negative values yield zero, and values `>= 2^128` (including positive infinity) saturate to `UINT128_MAX`.

[#u128_conversions]
== Conversions
Expand All @@ -135,35 +142,36 @@ struct uint128_t
...

// Integer conversion operators
BOOST_INT128_HOST_DEVICE constexpr operator bool() const noexcept;
BOOST_INT128_HOST_DEVICE explicit constexpr operator bool() const noexcept;

template <BOOST_INT128_SIGNED_INTEGER_CONCEPT SignedInteger>
BOOST_INT128_HOST_DEVICE explicit constexpr operator SignedInteger() const noexcept;
BOOST_INT128_HOST_DEVICE constexpr operator SignedInteger() const noexcept;

template <BOOST_INT128_UNSIGNED_INTEGER_CONCEPT UnsignedInteger>
BOOST_INT128_HOST_DEVICE explicit constexpr operator UnsignedInteger() const noexcept;
BOOST_INT128_HOST_DEVICE constexpr operator UnsignedInteger() const noexcept;

#ifdef BOOST_INT128_HAS_INT128

BOOST_INT128_HOST_DEVICE explicit constexpr operator detail::builtin_i128() const noexcept;
BOOST_INT128_HOST_DEVICE constexpr operator detail::builtin_i128() const noexcept;

BOOST_INT128_HOST_DEVICE explicit constexpr operator detail::builtin_u128() const noexcept;
BOOST_INT128_HOST_DEVICE constexpr operator detail::builtin_u128() const noexcept;

#endif // BOOST_INT128_HAS_INT128

// Conversion to float
BOOST_INT128_HOST_DEVICE explicit constexpr operator float() const noexcept;
BOOST_INT128_HOST_DEVICE explicit constexpr operator double() const noexcept;
explicit constexpr operator long double() const noexcept; // There are no long doubles on device
// Conversion to floating point
BOOST_INT128_HOST_DEVICE constexpr operator float() const noexcept;
BOOST_INT128_HOST_DEVICE constexpr operator double() const noexcept;
constexpr operator long double() const noexcept; // There are no long doubles on device
};

} // namespace int128
} // namespace boost
----

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

[#u128_comparison_operators]
Expand Down
40 changes: 40 additions & 0 deletions examples/construction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,45 @@ int main()
const uint128_t copied {from_macro};
std::cout << "Copy constructed: " << copied << std::endl;

std::cout << "\n=== Floating-Point Construction ===" << std::endl;

// Floating-point construction truncates toward zero, matching the behavior of
// a static_cast from a floating-point type to a built-in integer.
constexpr uint128_t from_double {12345.9};
std::cout << "uint128_t from 12345.9 (truncated): " << from_double << std::endl;

constexpr int128_t from_negative_double {-12345.9};
std::cout << "int128_t from -12345.9 (truncated toward zero): " << from_negative_double << std::endl;

// Values that exceed the 64-bit range are routed through the full 128-bit decomposition.
const double two_to_the_100 {1.2676506002282294e30}; // 2^100
const uint128_t large_from_double {two_to_the_100};
std::cout << "uint128_t from 2^100: " << large_from_double << std::endl;

std::cout << "\n=== Floating-Point Edge Cases ===" << std::endl;

// NaN yields zero for both signed and unsigned (mirrors libgcc's __fix(uns)Xfti).
const double nan_value {std::numeric_limits<double>::quiet_NaN()};
const uint128_t unsigned_from_nan {nan_value};
const int128_t signed_from_nan {nan_value};
std::cout << "uint128_t from NaN: " << unsigned_from_nan << std::endl;
std::cout << "int128_t from NaN: " << signed_from_nan << std::endl;

// Negative values are clamped to zero when constructing uint128_t.
const uint128_t unsigned_from_negative {-1.0};
std::cout << "uint128_t from -1.0 (clamped to zero): " << unsigned_from_negative << std::endl;

// Positive overflow saturates: anything >= 2^128 (including +infinity) becomes UINT128_MAX.
const double infinity {std::numeric_limits<double>::infinity()};
const uint128_t saturated_unsigned {infinity};
std::cout << "uint128_t from +infinity (saturates to UINT128_MAX): " << saturated_unsigned << std::endl;

// For int128_t, values >= 2^127 saturate to INT128_MAX and values <= -2^127 saturate to INT128_MIN.
const double huge {1e40}; // Well beyond 2^127 (~ 1.7e38)
const int128_t saturated_positive {huge};
const int128_t saturated_negative {-huge};
std::cout << "int128_t from 1e40 (saturates to INT128_MAX): " << saturated_positive << std::endl;
std::cout << "int128_t from -1e40 (saturates to INT128_MIN): " << saturated_negative << std::endl;

return 0;
}
14 changes: 0 additions & 14 deletions include/boost/int128/detail/conversions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,6 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t::uint128_t(const int128_t& v) noexc

#endif // BOOST_INT128_ENDIAN_LITTLE_BYTE

//=====================================
// Conversion Operators
//=====================================

BOOST_INT128_HOST_DEVICE constexpr int128_t::operator uint128_t() const noexcept
{
return uint128_t{static_cast<std::uint64_t>(this->high), static_cast<std::uint64_t>(this->low)};
}

BOOST_INT128_HOST_DEVICE constexpr uint128_t::operator int128_t() const noexcept
{
return int128_t{static_cast<std::int64_t>(this->high), static_cast<std::uint64_t>(this->low)};
}

//=====================================
// Comparison Operators
//=====================================
Expand Down
77 changes: 68 additions & 9 deletions include/boost/int128/detail/int128_imp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ int128_t
constexpr int128_t& operator=(int128_t&&) noexcept = default;

// Requires a conversion file to be implemented
BOOST_INT128_HOST_DEVICE explicit constexpr int128_t(const uint128_t& v) noexcept;
BOOST_INT128_HOST_DEVICE explicit constexpr operator uint128_t() const noexcept;
BOOST_INT128_HOST_DEVICE constexpr int128_t(const uint128_t& v) noexcept;

// Construct from integral types
#if BOOST_INT128_ENDIAN_LITTLE_BYTE
Expand Down Expand Up @@ -96,32 +95,36 @@ int128_t

#endif // BOOST_INT128_ENDIAN_LITTLE_BYTE

// Construct from floating-point types
template <BOOST_INT128_DEFAULTED_FLOATING_POINT_CONCEPT>
BOOST_INT128_HOST_DEVICE constexpr int128_t(Float f) noexcept;

// Integer Conversion operators
BOOST_INT128_HOST_DEVICE explicit constexpr operator bool() const noexcept { return low || high; }

template <BOOST_INT128_DEFAULTED_SIGNED_INTEGER_CONCEPT>
BOOST_INT128_HOST_DEVICE explicit constexpr operator SignedInteger() const noexcept { return static_cast<SignedInteger>(low); }
BOOST_INT128_HOST_DEVICE constexpr operator SignedInteger() const noexcept { return static_cast<SignedInteger>(low); }

template <BOOST_INT128_DEFAULTED_UNSIGNED_INTEGER_CONCEPT>
BOOST_INT128_HOST_DEVICE explicit constexpr operator UnsignedInteger() const noexcept { return static_cast<UnsignedInteger>(low); }
BOOST_INT128_HOST_DEVICE constexpr operator UnsignedInteger() const noexcept { return static_cast<UnsignedInteger>(low); }

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

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); }
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); }

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); }
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); }

#endif // BOOST_INT128_HAS_INT128

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

// Long double does not exist on device
#if !(defined(__CUDACC__) && defined(BOOST_INT128_ENABLE_CUDA))
explicit constexpr operator long double() const noexcept;
constexpr operator long double() const noexcept;
#endif

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

#endif

//=====================================
// Float Construction
//=====================================

// Inverse of operator(Float).
// NaN -> 0;
// f >= 2^127 -> INT128_MAX;
// f < -2^127 -> INT128_MIN.
template <BOOST_INT128_FLOATING_POINT_CONCEPT>
BOOST_INT128_HOST_DEVICE constexpr int128_t::int128_t(Float f) noexcept
{
constexpr Float two_32 {static_cast<Float>(UINT64_C(1) << 32)};
constexpr Float two_64 {two_32 * two_32};
constexpr Float two_127 {two_64 * static_cast<Float>(UINT64_C(1) << 63)};

// NaN: leave default-initialized (zero). NaN compares false to everything,
// so neither >= 0 nor <= 0 holds.
if (!(f >= Float{0}) && !(f <= Float{0}))
{
return;
}

if (f >= two_127)
{
high = (std::numeric_limits<std::int64_t>::max)();
low = UINT64_MAX;
return;
}

if (f <= -two_127)
{
high = (std::numeric_limits<std::int64_t>::min)();
low = UINT64_C(0);
return;
}

const bool negative {f < Float{0}};
const Float abs_f {negative ? -f : f};

std::uint64_t h {static_cast<std::uint64_t>(abs_f / two_64)};
const Float remainder {abs_f - static_cast<Float>(h) * two_64};
std::uint64_t l {static_cast<std::uint64_t>(remainder)};

if (negative)
{
// Two's complement negation of (h, l): new_l = -l (with wraparound),
// new_h = ~h if a borrow occurred (l != 0), else ~h + 1.
const bool low_was_zero {l == UINT64_C(0)};
l = UINT64_C(0) - l;
h = ~h + (low_was_zero ? UINT64_C(1) : UINT64_C(0));
}

high = static_cast<std::int64_t>(h);
low = l;
}

//=====================================
// Unary Operators
//=====================================
Expand Down
2 changes: 2 additions & 0 deletions include/boost/int128/detail/traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ using evaluation_type_t = std::conditional_t<sizeof(T) <= sizeof(std::uint32_t),
#define BOOST_INT128_DEFAULTED_SIGNED_INTEGER_CONCEPT typename SignedInteger, std::enable_if_t<detail::is_signed_integer_v<SignedInteger>, bool> = true
#define BOOST_INT128_DEFAULTED_UNSIGNED_INTEGER_CONCEPT typename UnsignedInteger, std::enable_if_t<detail::is_unsigned_integer_v<UnsignedInteger>, bool> = true
#define BOOST_INT128_DEFAULTED_INTEGER_CONCEPT typename Integer, std::enable_if_t<detail::is_any_integer_v<Integer>, bool> = true
#define BOOST_INT128_DEFAULTED_FLOATING_POINT_CONCEPT typename Float, std::enable_if_t<std::is_floating_point<Float>::value, bool> = true

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

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

Expand Down
Loading
Loading