From 1293fc094beda96093bf206d4014652239e3a09b Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 12 May 2026 10:21:38 -0400 Subject: [PATCH 01/12] Cross type conversions should be implicit --- include/boost/int128/detail/int128_imp.hpp | 4 ++-- include/boost/int128/detail/uint128_imp.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/boost/int128/detail/int128_imp.hpp b/include/boost/int128/detail/int128_imp.hpp index ca3677fc..17256159 100644 --- a/include/boost/int128/detail/int128_imp.hpp +++ b/include/boost/int128/detail/int128_imp.hpp @@ -56,8 +56,8 @@ 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; + BOOST_INT128_HOST_DEVICE constexpr operator uint128_t() const noexcept; // Construct from integral types #if BOOST_INT128_ENDIAN_LITTLE_BYTE diff --git a/include/boost/int128/detail/uint128_imp.hpp b/include/boost/int128/detail/uint128_imp.hpp index 84d15a62..306e67a2 100644 --- a/include/boost/int128/detail/uint128_imp.hpp +++ b/include/boost/int128/detail/uint128_imp.hpp @@ -57,8 +57,8 @@ uint128_t constexpr uint128_t& operator=(uint128_t&&) noexcept = default; // Requires a conversion file to be implemented - BOOST_INT128_HOST_DEVICE explicit constexpr uint128_t(const int128_t& v) noexcept; - BOOST_INT128_HOST_DEVICE explicit constexpr operator int128_t() const noexcept; + BOOST_INT128_HOST_DEVICE constexpr uint128_t(const int128_t& v) noexcept; + BOOST_INT128_HOST_DEVICE constexpr operator int128_t() const noexcept; // Construct from integral types #if BOOST_INT128_ENDIAN_LITTLE_BYTE From 7cf50308eea430fea0f0bdaf9521bc46693b761d Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 12 May 2026 10:26:51 -0400 Subject: [PATCH 02/12] Add testing --- test/Jamfile | 1 + test/test_cross_type_assign.cpp | 112 ++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 test/test_cross_type_assign.cpp diff --git a/test/Jamfile b/test/Jamfile index 7c169306..357c0b7c 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -70,6 +70,7 @@ run test_stream.cpp ; run test_mixed_type_sign_compare.cpp ; run test_mixed_type_sign_conversion.cpp ; +run test_cross_type_assign.cpp ; run test_builtin_parity.cpp ; run test_consteval_funcs.cpp ; diff --git a/test/test_cross_type_assign.cpp b/test/test_cross_type_assign.cpp new file mode 100644 index 00000000..344dff08 --- /dev/null +++ b/test/test_cross_type_assign.cpp @@ -0,0 +1,112 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include +#include + +using namespace boost::int128; + +void test_implicit_conversion_traits() +{ + static_assert(std::is_convertible::value, "int128_t -> uint128_t should be implicit"); + static_assert(std::is_convertible::value, "uint128_t -> int128_t should be implicit"); + static_assert(std::is_assignable::value, "uint128_t should be assignable to int128_t"); + static_assert(std::is_assignable::value, "int128_t should be assignable to uint128_t"); +} + +void test_uint_to_int_construction() +{ + const uint128_t u {1U, 42U}; + + // Copy construction with braces + const int128_t a {u}; + BOOST_TEST_EQ(a.low, u.low); + BOOST_TEST_EQ(static_cast(a.high), u.high); + + // Copy-initialization (implicit conversion) + const int128_t b = u; + BOOST_TEST_EQ(b.low, u.low); + BOOST_TEST_EQ(static_cast(b.high), u.high); + + // Move construction + uint128_t u_movable {1U, 42U}; + const int128_t c {std::move(u_movable)}; + BOOST_TEST_EQ(c.low, 42U); + BOOST_TEST_EQ(c.high, 1); +} + +void test_int_to_uint_construction() +{ + const int128_t i {-1, 0xFFFFFFFFFFFFFFFFULL}; + + const uint128_t a {i}; + BOOST_TEST_EQ(a.low, i.low); + BOOST_TEST_EQ(a.high, static_cast(i.high)); + + const uint128_t b = i; + BOOST_TEST_EQ(b.low, i.low); + BOOST_TEST_EQ(b.high, static_cast(i.high)); + + int128_t i_movable {-1, 0xFFFFFFFFFFFFFFFFULL}; + const uint128_t c {std::move(i_movable)}; + BOOST_TEST_EQ(c.high, 0xFFFFFFFFFFFFFFFFULL); + BOOST_TEST_EQ(c.low, 0xFFFFFFFFFFFFFFFFULL); +} + +void test_uint_to_int_assignment() +{ + const uint128_t u {7U, 99U}; + + // Copy assignment via implicit conversion + int128_t a {}; + a = u; + BOOST_TEST_EQ(a.low, 99U); + BOOST_TEST_EQ(a.high, 7); + + // Move assignment via implicit conversion + int128_t b {}; + b = uint128_t{7U, 99U}; + BOOST_TEST_EQ(b.low, 99U); + BOOST_TEST_EQ(b.high, 7); +} + +void test_int_to_uint_assignment() +{ + const int128_t i {-2, 0x1234U}; + + uint128_t a {}; + a = i; + BOOST_TEST_EQ(a.low, 0x1234U); + BOOST_TEST_EQ(a.high, static_cast(-2)); + + uint128_t b {}; + b = int128_t{-2, 0x1234U}; + BOOST_TEST_EQ(b.low, 0x1234U); + BOOST_TEST_EQ(b.high, static_cast(-2)); +} + +void test_constexpr_cross_type() +{ + constexpr uint128_t u {1U, 42U}; + constexpr int128_t a {u}; + static_assert(a.low == 42U, "constexpr cross-type construction"); + + constexpr int128_t i {-1, 7U}; + constexpr uint128_t b {i}; + static_assert(b.low == 7U, "constexpr cross-type construction"); +} + +int main() +{ + test_implicit_conversion_traits(); + test_uint_to_int_construction(); + test_int_to_uint_construction(); + test_uint_to_int_assignment(); + test_int_to_uint_assignment(); + test_constexpr_cross_type(); + + return boost::report_errors(); +} From 24271039df4b297ab470b6583a2079b98d61d503 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 12 May 2026 10:27:01 -0400 Subject: [PATCH 03/12] Remove unneeded conversion --- include/boost/int128/detail/conversions.hpp | 14 -------------- include/boost/int128/detail/int128_imp.hpp | 1 - include/boost/int128/detail/uint128_imp.hpp | 1 - 3 files changed, 16 deletions(-) diff --git a/include/boost/int128/detail/conversions.hpp b/include/boost/int128/detail/conversions.hpp index 8b0e640a..aca90214 100644 --- a/include/boost/int128/detail/conversions.hpp +++ b/include/boost/int128/detail/conversions.hpp @@ -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(this->high), static_cast(this->low)}; -} - -BOOST_INT128_HOST_DEVICE constexpr uint128_t::operator int128_t() const noexcept -{ - return int128_t{static_cast(this->high), static_cast(this->low)}; -} - //===================================== // Comparison Operators //===================================== diff --git a/include/boost/int128/detail/int128_imp.hpp b/include/boost/int128/detail/int128_imp.hpp index 17256159..f9c890fe 100644 --- a/include/boost/int128/detail/int128_imp.hpp +++ b/include/boost/int128/detail/int128_imp.hpp @@ -57,7 +57,6 @@ int128_t // Requires a conversion file to be implemented BOOST_INT128_HOST_DEVICE constexpr int128_t(const uint128_t& v) noexcept; - BOOST_INT128_HOST_DEVICE constexpr operator uint128_t() const noexcept; // Construct from integral types #if BOOST_INT128_ENDIAN_LITTLE_BYTE diff --git a/include/boost/int128/detail/uint128_imp.hpp b/include/boost/int128/detail/uint128_imp.hpp index 306e67a2..7c82fd63 100644 --- a/include/boost/int128/detail/uint128_imp.hpp +++ b/include/boost/int128/detail/uint128_imp.hpp @@ -58,7 +58,6 @@ uint128_t // Requires a conversion file to be implemented BOOST_INT128_HOST_DEVICE constexpr uint128_t(const int128_t& v) noexcept; - BOOST_INT128_HOST_DEVICE constexpr operator int128_t() const noexcept; // Construct from integral types #if BOOST_INT128_ENDIAN_LITTLE_BYTE From 217718bf8a2693d50b60b64a5428c7e51f803c20 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 12 May 2026 10:59:45 -0400 Subject: [PATCH 04/12] Make the other conversions implict --- include/boost/int128/detail/int128_imp.hpp | 14 +++++++------- include/boost/int128/detail/uint128_imp.hpp | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/include/boost/int128/detail/int128_imp.hpp b/include/boost/int128/detail/int128_imp.hpp index f9c890fe..738a3ecb 100644 --- a/include/boost/int128/detail/int128_imp.hpp +++ b/include/boost/int128/detail/int128_imp.hpp @@ -99,28 +99,28 @@ int128_t BOOST_INT128_HOST_DEVICE explicit constexpr operator bool() const noexcept { return low || high; } template - BOOST_INT128_HOST_DEVICE explicit constexpr operator SignedInteger() const noexcept { return static_cast(low); } + BOOST_INT128_HOST_DEVICE constexpr operator SignedInteger() const noexcept { return static_cast(low); } template - BOOST_INT128_HOST_DEVICE explicit constexpr operator UnsignedInteger() const noexcept { return static_cast(low); } + BOOST_INT128_HOST_DEVICE constexpr operator UnsignedInteger() const noexcept { return static_cast(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(static_cast(high) << static_cast(64)) | static_cast(low); } + BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR operator detail::builtin_i128() const noexcept { return static_cast(static_cast(high) << static_cast(64)) | static_cast(low); } - BOOST_INT128_HOST_DEVICE explicit BOOST_INT128_BUILTIN_CONSTEXPR operator detail::builtin_u128() const noexcept { return (static_cast(high) << static_cast(64)) | static_cast(low); } + BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR operator detail::builtin_u128() const noexcept { return (static_cast(high) << static_cast(64)) | static_cast(low); } #endif // BOOST_INT128_HAS_INT128 // Conversion to float // This is basically the same as ldexp(static_cast(high), 64) + static_cast(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 diff --git a/include/boost/int128/detail/uint128_imp.hpp b/include/boost/int128/detail/uint128_imp.hpp index 7c82fd63..32c875d4 100644 --- a/include/boost/int128/detail/uint128_imp.hpp +++ b/include/boost/int128/detail/uint128_imp.hpp @@ -110,28 +110,28 @@ uint128_t BOOST_INT128_HOST_DEVICE explicit constexpr operator bool() const noexcept {return low || high; } template - BOOST_INT128_HOST_DEVICE explicit constexpr operator SignedInteger() const noexcept { return static_cast(low); } + BOOST_INT128_HOST_DEVICE constexpr operator SignedInteger() const noexcept { return static_cast(low); } template - BOOST_INT128_HOST_DEVICE explicit constexpr operator UnsignedInteger() const noexcept { return static_cast(low); } + BOOST_INT128_HOST_DEVICE constexpr operator UnsignedInteger() const noexcept { return static_cast(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(static_cast(high) << static_cast(64)) | static_cast(low); } + BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR operator detail::builtin_i128() const noexcept { return static_cast(static_cast(high) << static_cast(64)) | static_cast(low); } - BOOST_INT128_HOST_DEVICE explicit BOOST_INT128_BUILTIN_CONSTEXPR operator detail::builtin_u128() const noexcept { return (static_cast(high) << static_cast(64)) | static_cast(low); } + BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR operator detail::builtin_u128() const noexcept { return (static_cast(high) << static_cast(64)) | static_cast(low); } #endif // BOOST_INT128_HAS_INT128 // Conversion to float // This is basically the same as ldexp(static_cast(high), 64) + static_cast(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 doubles do 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 From 98737e12be5e1877ed447510e0488fc893795ea5 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 12 May 2026 10:59:55 -0400 Subject: [PATCH 05/12] Add testing of now implicit conversions --- test/test_cross_type_assign.cpp | 52 +++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/test_cross_type_assign.cpp b/test/test_cross_type_assign.cpp index 344dff08..7f015c66 100644 --- a/test/test_cross_type_assign.cpp +++ b/test/test_cross_type_assign.cpp @@ -15,6 +15,57 @@ void test_implicit_conversion_traits() static_assert(std::is_convertible::value, "uint128_t -> int128_t should be implicit"); static_assert(std::is_assignable::value, "uint128_t should be assignable to int128_t"); static_assert(std::is_assignable::value, "int128_t should be assignable to uint128_t"); + + // Implicit conversions to builtin integer types (matches __int128 behavior) + static_assert(std::is_convertible::value, "int128_t -> int should be implicit"); + static_assert(std::is_convertible::value, "int128_t -> unsigned int should be implicit"); + static_assert(std::is_convertible::value, "int128_t -> int64_t should be implicit"); + static_assert(std::is_convertible::value, "int128_t -> uint64_t should be implicit"); + static_assert(std::is_convertible::value, "uint128_t -> int should be implicit"); + static_assert(std::is_convertible::value, "uint128_t -> unsigned int should be implicit"); + + // Implicit conversions to floating-point types + static_assert(std::is_convertible::value, "int128_t -> float should be implicit"); + static_assert(std::is_convertible::value, "int128_t -> double should be implicit"); + static_assert(std::is_convertible::value, "uint128_t -> float should be implicit"); + static_assert(std::is_convertible::value, "uint128_t -> double should be implicit"); + +#if defined(BOOST_INT128_HAS_INT128) + // Implicit conversions to builtin __int128 + static_assert(std::is_convertible::value, "int128_t -> __int128 should be implicit"); + static_assert(std::is_convertible::value, "int128_t -> unsigned __int128 should be implicit"); + static_assert(std::is_convertible::value, "uint128_t -> __int128 should be implicit"); + static_assert(std::is_convertible::value, "uint128_t -> unsigned __int128 should be implicit"); +#endif +} + +void test_implicit_conversions_runtime() +{ + const int128_t i {0, 42U}; + + const int as_int = i; + BOOST_TEST_EQ(as_int, 42); + + const std::uint64_t as_u64 = i; + BOOST_TEST_EQ(as_u64, 42U); + + const double as_double = i; + BOOST_TEST_EQ(as_double, 42.0); + + const uint128_t u {0U, 100U}; + const unsigned int as_uint = u; + BOOST_TEST_EQ(as_uint, 100U); + + const float as_float = u; + BOOST_TEST_EQ(as_float, 100.0f); + +#if defined(BOOST_INT128_HAS_INT128) + const detail::builtin_i128 as_native_i = int128_t{1, 2U}; + BOOST_TEST(as_native_i == ((static_cast(1) << 64) | 2)); + + const detail::builtin_u128 as_native_u = uint128_t{3U, 4U}; + BOOST_TEST(as_native_u == ((static_cast(3) << 64) | 4)); +#endif } void test_uint_to_int_construction() @@ -107,6 +158,7 @@ int main() test_uint_to_int_assignment(); test_int_to_uint_assignment(); test_constexpr_cross_type(); + test_implicit_conversions_runtime(); return boost::report_errors(); } From bfdefab4123685d05fa7b7dcf92a0d6265e2e2a6 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 12 May 2026 11:24:11 -0400 Subject: [PATCH 06/12] Add floating point trait --- include/boost/int128/detail/traits.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/boost/int128/detail/traits.hpp b/include/boost/int128/detail/traits.hpp index e6b6efd1..576b6ae0 100644 --- a/include/boost/int128/detail/traits.hpp +++ b/include/boost/int128/detail/traits.hpp @@ -71,10 +71,12 @@ using evaluation_type_t = std::conditional_t, bool> = true #define BOOST_INT128_DEFAULTED_UNSIGNED_INTEGER_CONCEPT typename UnsignedInteger, std::enable_if_t, bool> = true #define BOOST_INT128_DEFAULTED_INTEGER_CONCEPT typename Integer, std::enable_if_t, bool> = true +#define BOOST_INT128_DEFAULTED_FLOATING_POINT_CONCEPT typename Float, std::enable_if_t::value, bool> = true #define BOOST_INT128_SIGNED_INTEGER_CONCEPT typename SignedInteger, std::enable_if_t, bool> #define BOOST_INT128_UNSIGNED_INTEGER_CONCEPT typename UnsignedInteger, std::enable_if_t, bool> #define BOOST_INT128_INTEGER_CONCEPT typename Integer, std::enable_if_t, bool> +#define BOOST_INT128_FLOATING_POINT_CONCEPT typename Float, std::enable_if_t::value, bool> #if defined(BOOST_INT128_HAS_INT128) || defined(BOOST_INT128_HAS_MSVC_INT128) From 48c534456654acfad0c0ac9aab15e5fc3abdfb2d Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 12 May 2026 11:25:30 -0400 Subject: [PATCH 07/12] Add floating point constructors --- include/boost/int128/detail/int128_imp.hpp | 60 +++++++++++++++++++++ include/boost/int128/detail/uint128_imp.hpp | 36 +++++++++++++ 2 files changed, 96 insertions(+) diff --git a/include/boost/int128/detail/int128_imp.hpp b/include/boost/int128/detail/int128_imp.hpp index 738a3ecb..200d434f 100644 --- a/include/boost/int128/detail/int128_imp.hpp +++ b/include/boost/int128/detail/int128_imp.hpp @@ -95,6 +95,10 @@ int128_t #endif // BOOST_INT128_ENDIAN_LITTLE_BYTE + // Construct from floating-point types + template + 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; } @@ -305,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_HOST_DEVICE constexpr int128_t::int128_t(Float f) noexcept +{ + constexpr Float two_32 {static_cast(UINT64_C(1) << 32)}; + constexpr Float two_64 {two_32 * two_32}; + constexpr Float two_127 {two_64 * static_cast(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::max)(); + low = UINT64_MAX; + return; + } + + if (f <= -two_127) + { + high = (std::numeric_limits::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(abs_f / two_64)}; + const Float remainder {abs_f - static_cast(h) * two_64}; + std::uint64_t l {static_cast(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(h); + low = l; +} + //===================================== // Unary Operators //===================================== diff --git a/include/boost/int128/detail/uint128_imp.hpp b/include/boost/int128/detail/uint128_imp.hpp index 32c875d4..9e703fa4 100644 --- a/include/boost/int128/detail/uint128_imp.hpp +++ b/include/boost/int128/detail/uint128_imp.hpp @@ -106,6 +106,10 @@ uint128_t #endif // BOOST_INT128_ENDIAN_LITTLE_BYTE + // Construct from floating-point types + template + BOOST_INT128_HOST_DEVICE constexpr uint128_t(Float f) noexcept; + // Integer conversion operators BOOST_INT128_HOST_DEVICE explicit constexpr operator bool() const noexcept {return low || high; } @@ -307,6 +311,38 @@ constexpr uint128_t::operator long double() const noexcept #endif // __NVCC__ +//===================================== +// Float Construction +//===================================== + +// Inverse of operator(Float): decompose f into (high, low) by dividing by 2^64. +// NaN/negative -> 0 +// overflow -> UINT128_MAX. +template +BOOST_INT128_HOST_DEVICE constexpr uint128_t::uint128_t(Float f) noexcept +{ + constexpr Float two_32 {static_cast(UINT64_C(1) << 32)}; + constexpr Float two_64 {two_32 * two_32}; + constexpr Float two_128 {two_64 * two_64}; + + // !(f >= 0) catches both NaN and negative values without using + if (!(f >= Float{0})) + { + return; + } + + if (f >= two_128) + { + high = UINT64_MAX; + low = UINT64_MAX; + return; + } + + high = static_cast(f / two_64); + const Float remainder {f - static_cast(high) * two_64}; + low = static_cast(remainder); +} + //===================================== // Unary Operators //===================================== From c7a9cc6aea04c0f8249a930be1ce4b8e3fff08d9 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 12 May 2026 11:25:40 -0400 Subject: [PATCH 08/12] Add testing of floating point constructors --- test/test_cross_type_assign.cpp | 106 ++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/test/test_cross_type_assign.cpp b/test/test_cross_type_assign.cpp index 7f015c66..1e0ce7b7 100644 --- a/test/test_cross_type_assign.cpp +++ b/test/test_cross_type_assign.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include using namespace boost::int128; @@ -150,6 +152,102 @@ void test_constexpr_cross_type() static_assert(b.low == 7U, "constexpr cross-type construction"); } +template +void test_uint_from_float() +{ + // Basic positive values + BOOST_TEST_EQ(uint128_t{Float{0}}.low, 0U); + BOOST_TEST_EQ(uint128_t{Float{0}}.high, 0U); + BOOST_TEST_EQ(uint128_t{Float{42}}.low, 42U); + BOOST_TEST_EQ(uint128_t{Float{42}}.high, 0U); + + // Truncation toward zero + BOOST_TEST_EQ(uint128_t{Float{3.7}}.low, 3U); + BOOST_TEST_EQ(uint128_t{Float{0.99}}.low, 0U); + + // NaN -> 0 + const Float nan {std::numeric_limits::quiet_NaN()}; + BOOST_TEST_EQ(uint128_t{nan}.low, 0U); + BOOST_TEST_EQ(uint128_t{nan}.high, 0U); + + // Negative -> 0 (matches libgcc) + BOOST_TEST_EQ(uint128_t{Float{-1}}.low, 0U); + BOOST_TEST_EQ(uint128_t{Float{-1}}.high, 0U); + + // Saturation on overflow: infinity (or any value >= 2^128) -> UINT128_MAX. + // For float, 2^128 itself is +infinity since the 8-bit exponent saturates. + const Float two_64 {static_cast(UINT64_C(1) << 32) * static_cast(UINT64_C(1) << 32)}; + const uint128_t saturated {std::numeric_limits::infinity()}; + BOOST_TEST_EQ(saturated.low, UINT64_MAX); + BOOST_TEST_EQ(saturated.high, UINT64_MAX); + + // 2^127 should fit (representable in float, double, long double) + const Float two_127 {two_64 * static_cast(UINT64_C(1) << 63)}; + const uint128_t large {two_127}; + BOOST_TEST_EQ(large.low, 0U); + BOOST_TEST_EQ(large.high, UINT64_C(1) << 63); + + // Round-trip for an exactly representable mid-range value + const Float round_trip_src {two_64}; // 2^64 + const uint128_t round_trip {round_trip_src}; + BOOST_TEST_EQ(round_trip.low, 0U); + BOOST_TEST_EQ(round_trip.high, 1U); +} + +template +void test_int_from_float() +{ + // Basic positive and negative + BOOST_TEST_EQ(int128_t{Float{0}}.low, 0U); + BOOST_TEST_EQ(int128_t{Float{0}}.high, 0); + BOOST_TEST_EQ(int128_t{Float{42}}.low, 42U); + BOOST_TEST_EQ(int128_t{Float{-42}}.low, static_cast(-42)); + BOOST_TEST_EQ(int128_t{Float{-42}}.high, -1); + + // Truncation toward zero + BOOST_TEST_EQ(int128_t{Float{3.7}}.low, 3U); + BOOST_TEST_EQ(int128_t{Float{-3.7}}.low, static_cast(-3)); + + // NaN -> 0 + const Float nan {std::numeric_limits::quiet_NaN()}; + BOOST_TEST_EQ(int128_t{nan}.low, 0U); + BOOST_TEST_EQ(int128_t{nan}.high, 0); + + // Positive saturation: f >= 2^127 -> INT128_MAX + const Float two_64 {static_cast(UINT64_C(1) << 32) * static_cast(UINT64_C(1) << 32)}; + const Float two_127 {two_64 * static_cast(UINT64_C(1) << 63)}; + const int128_t pos_sat {two_127}; + BOOST_TEST_EQ(pos_sat.high, (std::numeric_limits::max)()); + BOOST_TEST_EQ(pos_sat.low, UINT64_MAX); + + // Negative saturation: f <= -2^127 -> INT128_MIN + const int128_t neg_sat {-two_127}; + BOOST_TEST_EQ(neg_sat.high, (std::numeric_limits::min)()); + BOOST_TEST_EQ(neg_sat.low, 0U); + + // Just below the positive boundary should not saturate + const int128_t near_max {two_127 * Float{0.5}}; // 2^126 + BOOST_TEST_EQ(near_max.high, UINT64_C(1) << 62); + BOOST_TEST_EQ(near_max.low, 0U); + + // Round-trip a negative power of two through the two's-complement path + const int128_t neg_round_trip {-two_64}; // -2^64 + BOOST_TEST_EQ(neg_round_trip.low, 0U); + BOOST_TEST_EQ(neg_round_trip.high, -1); +} + +void test_constexpr_float_construction() +{ + constexpr uint128_t u {42.5}; + static_assert(u.low == 42U, "constexpr uint from double"); + + constexpr int128_t i {-7.9}; + static_assert(i.high == -1, "constexpr int from double sign"); + + constexpr int128_t zero_from_nan {std::numeric_limits::quiet_NaN()}; + static_assert(zero_from_nan.low == 0U && zero_from_nan.high == 0, "NaN -> 0 in constexpr"); +} + int main() { test_implicit_conversion_traits(); @@ -160,5 +258,13 @@ int main() test_constexpr_cross_type(); test_implicit_conversions_runtime(); + test_uint_from_float(); + test_uint_from_float(); + test_uint_from_float(); + test_int_from_float(); + test_int_from_float(); + test_int_from_float(); + test_constexpr_float_construction(); + return boost::report_errors(); } From 5931888b94cf29fa2014ffefa85b16fbfffac0af Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 12 May 2026 11:35:54 -0400 Subject: [PATCH 09/12] Update doc pages --- doc/modules/ROOT/pages/int128_t.adoc | 36 ++++++++++++++++----------- doc/modules/ROOT/pages/uint128_t.adoc | 36 ++++++++++++++++----------- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/doc/modules/ROOT/pages/int128_t.adoc b/doc/modules/ROOT/pages/int128_t.adoc index 67d93f55..70f7aa56 100644 --- a/doc/modules/ROOT/pages/int128_t.adoc +++ b/doc/modules/ROOT/pages/int128_t.adoc @@ -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; @@ -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_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 @@ -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_HOST_DEVICE explicit constexpr operator SignedInteger() const noexcept; + BOOST_INT128_HOST_DEVICE constexpr operator SignedInteger() const noexcept; template - 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] diff --git a/doc/modules/ROOT/pages/uint128_t.adoc b/doc/modules/ROOT/pages/uint128_t.adoc index 15cb6978..bd5e68c4 100644 --- a/doc/modules/ROOT/pages/uint128_t.adoc +++ b/doc/modules/ROOT/pages/uint128_t.adoc @@ -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; @@ -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_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(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 @@ -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_HOST_DEVICE explicit constexpr operator SignedInteger() const noexcept; + BOOST_INT128_HOST_DEVICE constexpr operator SignedInteger() const noexcept; template - 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] From 6f03424e6bdf1c29eb6d0d515bc5b90167f450f0 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 12 May 2026 11:59:38 -0400 Subject: [PATCH 10/12] Update examples and output --- doc/modules/ROOT/pages/examples.adoc | 13 +++++++++ examples/construction.cpp | 40 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/doc/modules/ROOT/pages/examples.adoc b/doc/modules/ROOT/pages/examples.adoc index 6a8137ce..8378c30c 100644 --- a/doc/modules/ROOT/pages/examples.adoc +++ b/doc/modules/ROOT/pages/examples.adoc @@ -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 ---- ==== diff --git a/examples/construction.cpp b/examples/construction.cpp index 644e2b6a..1ddfe4dc 100644 --- a/examples/construction.cpp +++ b/examples/construction.cpp @@ -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::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::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; } \ No newline at end of file From e6635cfea228803dac59c8db4f6877510340dcf6 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 12 May 2026 13:41:25 -0400 Subject: [PATCH 11/12] Fix warnings --- test/test_cross_type_assign.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/test/test_cross_type_assign.cpp b/test/test_cross_type_assign.cpp index 1e0ce7b7..24830b7c 100644 --- a/test/test_cross_type_assign.cpp +++ b/test/test_cross_type_assign.cpp @@ -52,14 +52,14 @@ void test_implicit_conversions_runtime() BOOST_TEST_EQ(as_u64, 42U); const double as_double = i; - BOOST_TEST_EQ(as_double, 42.0); + BOOST_TEST_EQ(static_cast(as_double), 42); const uint128_t u {0U, 100U}; const unsigned int as_uint = u; BOOST_TEST_EQ(as_uint, 100U); const float as_float = u; - BOOST_TEST_EQ(as_float, 100.0f); + BOOST_TEST_EQ(static_cast(as_float), 100); #if defined(BOOST_INT128_HAS_INT128) const detail::builtin_i128 as_native_i = int128_t{1, 2U}; @@ -161,9 +161,10 @@ void test_uint_from_float() BOOST_TEST_EQ(uint128_t{Float{42}}.low, 42U); BOOST_TEST_EQ(uint128_t{Float{42}}.high, 0U); - // Truncation toward zero - BOOST_TEST_EQ(uint128_t{Float{3.7}}.low, 3U); - BOOST_TEST_EQ(uint128_t{Float{0.99}}.low, 0U); + // Truncation toward zero. Use Float{N}/Float{D} rather than a double literal + // so the test compiles cleanly for float and long double without precision warnings. + BOOST_TEST_EQ((uint128_t{Float{37} / Float{10}}.low), 3U); // ~3.7 -> 3 + BOOST_TEST_EQ((uint128_t{Float{99} / Float{100}}.low), 0U); // ~0.99 -> 0 // NaN -> 0 const Float nan {std::numeric_limits::quiet_NaN()}; @@ -204,9 +205,9 @@ void test_int_from_float() BOOST_TEST_EQ(int128_t{Float{-42}}.low, static_cast(-42)); BOOST_TEST_EQ(int128_t{Float{-42}}.high, -1); - // Truncation toward zero - BOOST_TEST_EQ(int128_t{Float{3.7}}.low, 3U); - BOOST_TEST_EQ(int128_t{Float{-3.7}}.low, static_cast(-3)); + // Truncation toward zero (see note in test_uint_from_float on the literal style). + BOOST_TEST_EQ((int128_t{Float{37} / Float{10}}.low), 3U); // ~3.7 -> 3 + BOOST_TEST_EQ((int128_t{Float{-37} / Float{10}}.low), static_cast(-3)); // ~-3.7 -> -3 // NaN -> 0 const Float nan {std::numeric_limits::quiet_NaN()}; @@ -225,8 +226,8 @@ void test_int_from_float() BOOST_TEST_EQ(neg_sat.high, (std::numeric_limits::min)()); BOOST_TEST_EQ(neg_sat.low, 0U); - // Just below the positive boundary should not saturate - const int128_t near_max {two_127 * Float{0.5}}; // 2^126 + // Just below the positive boundary should not saturate. + const int128_t near_max {two_127 / Float{2}}; // 2^126 BOOST_TEST_EQ(near_max.high, UINT64_C(1) << 62); BOOST_TEST_EQ(near_max.low, 0U); From 7e5ae9fd66c34774fd9768ea6432bb108e421c9d Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 12 May 2026 14:00:25 -0400 Subject: [PATCH 12/12] Fix non-constexpr errors --- include/boost/int128/detail/uint128_imp.hpp | 9 ++++++--- test/test_cross_type_assign.cpp | 5 +++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/include/boost/int128/detail/uint128_imp.hpp b/include/boost/int128/detail/uint128_imp.hpp index 9e703fa4..1788433d 100644 --- a/include/boost/int128/detail/uint128_imp.hpp +++ b/include/boost/int128/detail/uint128_imp.hpp @@ -323,7 +323,6 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t::uint128_t(Float f) noexcept { constexpr Float two_32 {static_cast(UINT64_C(1) << 32)}; constexpr Float two_64 {two_32 * two_32}; - constexpr Float two_128 {two_64 * two_64}; // !(f >= 0) catches both NaN and negative values without using if (!(f >= Float{0})) @@ -331,14 +330,18 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t::uint128_t(Float f) noexcept return; } - if (f >= two_128) + // Overflow test: f >= 2^128 iff f / 2^64 >= 2^64. Comparing scaled values + // avoids materializing 2^128 as a Float, which overflows to +infinity for + // `float` and is therefore not constant-evaluable on older compilers. + const Float scaled {f / two_64}; + if (scaled >= two_64) { high = UINT64_MAX; low = UINT64_MAX; return; } - high = static_cast(f / two_64); + high = static_cast(scaled); const Float remainder {f - static_cast(high) * two_64}; low = static_cast(remainder); } diff --git a/test/test_cross_type_assign.cpp b/test/test_cross_type_assign.cpp index 24830b7c..1bd0919a 100644 --- a/test/test_cross_type_assign.cpp +++ b/test/test_cross_type_assign.cpp @@ -245,8 +245,9 @@ void test_constexpr_float_construction() constexpr int128_t i {-7.9}; static_assert(i.high == -1, "constexpr int from double sign"); - constexpr int128_t zero_from_nan {std::numeric_limits::quiet_NaN()}; - static_assert(zero_from_nan.low == 0U && zero_from_nan.high == 0, "NaN -> 0 in constexpr"); + // NaN -> 0 is exercised at runtime in test_uint_from_float / test_int_from_float. + // It cannot be constant-evaluated on GCC 9, which rejects NaN comparisons in + // constexpr contexts. } int main()