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
2 changes: 1 addition & 1 deletion include/boost/int128/detail/constants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace detail {
BOOST_INT128_INLINE_CONSTEXPR std::uint64_t low_word_mask {(std::numeric_limits<std::uint64_t>::max)()};

template <typename T>
BOOST_INT128_INLINE_CONSTEXPR T offset_value_v = static_cast<T>((std::numeric_limits<std::uint64_t>::max)());
BOOST_INT128_INLINE_CONSTEXPR T offset_value_v = static_cast<T>(18446744073709551616.0); // UINT64_MAX as double

} // namespace detail
} // namespace int128
Expand Down
51 changes: 51 additions & 0 deletions include/boost/int128/detail/float_conversion.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#ifndef BOOST_INT128_DETAIL_FLOAT_CONVERSION_HPP
#define BOOST_INT128_DETAIL_FLOAT_CONVERSION_HPP

#include <boost/int128/detail/config.hpp>
#include <boost/int128/detail/constants.hpp>

#ifndef BOOST_INT128_BUILD_MODULE

#include <cstdint>

#endif

namespace boost {
namespace int128 {
namespace detail {

// The most correct way to do this would be std::ldexp(static_cast<T>(high), 64) + static_cast<T>(low);
// Since std::ldexp is not constexpr until C++23 we can work around this by multiplying the high word
// by 0xFFFFFFFF in order to generally replicate what ldexp is doing in the constexpr context.
// We also avoid pulling in <quadmath.h> for the __float128 case where we would need ldexpq
template <typename T>
BOOST_INT128_HOST_DEVICE constexpr T unsigned_words_to_float(const std::uint64_t high, const std::uint64_t low) noexcept
{
return static_cast<T>(high) * offset_value_v<T> + static_cast<T>(low);
}

template <typename T>
BOOST_INT128_HOST_DEVICE constexpr T signed_words_to_float(const std::int64_t high, const std::uint64_t low) noexcept
{
if (high < 0)
{
// Two's complement magnitude of the full 128-bit value.
// INT128_MIN needs no special case since it yields high = 2^63, low = 0
const auto abs_low {~low + UINT64_C(1)};
const auto abs_high {~static_cast<std::uint64_t>(high) + (abs_low == UINT64_C(0) ? UINT64_C(1) : UINT64_C(0))};

return -unsigned_words_to_float<T>(abs_high, abs_low);
}

return unsigned_words_to_float<T>(static_cast<std::uint64_t>(high), low);
}

} // namespace detail
} // namespace int128
} // namespace boost

#endif // BOOST_INT128_DETAIL_FLOAT_CONVERSION_HPP
44 changes: 35 additions & 9 deletions include/boost/int128/detail/int128_imp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <boost/int128/detail/clz.hpp>
#include <boost/int128/detail/common_mul.hpp>
#include <boost/int128/detail/common_div.hpp>
#include <boost/int128/detail/float_conversion.hpp>

#ifndef BOOST_INT128_BUILD_MODULE

Expand Down Expand Up @@ -117,8 +118,9 @@ int128_t
#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
// Uses the builtin 128-bit conversion where one exists, and otherwise converts
// the unsigned magnitude as high * 2^64 + low before applying the sign.
// See detail/float_conversion.hpp for why the sign handling is required
BOOST_INT128_HOST_DEVICE constexpr operator float() const noexcept;
BOOST_INT128_HOST_DEVICE constexpr operator double() const noexcept;

Expand Down Expand Up @@ -285,26 +287,50 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t abs(int128_t val
// Float Conversion Operators
//=====================================

// The most correct way to do this would be std::ldexp(static_cast<T>(high), 64) + static_cast<T>(low);
// Since std::ldexp is not constexpr until C++23 we can work around this by multiplying the high word
// by 0xFFFFFFFF in order to generally replicate what ldexp is doing in the constexpr context.
// We also avoid pulling in <quadmath.h> for the __float128 case where we would need ldexpq
// When the builtin 128-bit type exists we convert through it since the compiler
// runtime (__floattisf and friends) is correctly rounded. The portable fallback
// converts the unsigned magnitude and applies the sign; see detail/float_conversion.hpp
// for why the raw words can not be composed directly for negative values

BOOST_INT128_HOST_DEVICE constexpr int128_t::operator float() const noexcept
{
return static_cast<float>(high) * detail::offset_value_v<float> + static_cast<float>(low);
#if defined(BOOST_INT128_HAS_INT128) && !defined(BOOST_INT128_HAS_GPU_SUPPORT)

return static_cast<float>(static_cast<detail::builtin_i128>(*this));

#else

return detail::signed_words_to_float<float>(high, low);

#endif
}

BOOST_INT128_HOST_DEVICE constexpr int128_t::operator double() const noexcept
{
return static_cast<double>(high) * detail::offset_value_v<double> + static_cast<double>(low);
#if defined(BOOST_INT128_HAS_INT128) && !defined(BOOST_INT128_HAS_GPU_SUPPORT)

return static_cast<double>(static_cast<detail::builtin_i128>(*this));

#else

return detail::signed_words_to_float<double>(high, low);

#endif
}

#if !defined(BOOST_INT128_HAS_GPU_SUPPORT)

constexpr int128_t::operator long double() const noexcept
{
return static_cast<long double>(high) * detail::offset_value_v<long double> + static_cast<long double>(low);
#if defined(BOOST_INT128_HAS_INT128)

return static_cast<long double>(static_cast<detail::builtin_i128>(*this));

#else

return detail::signed_words_to_float<long double>(high, low);

#endif
}

#endif
Expand Down
45 changes: 35 additions & 10 deletions include/boost/int128/detail/uint128_imp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <boost/int128/detail/clz.hpp>
#include <boost/int128/detail/common_mul.hpp>
#include <boost/int128/detail/common_div.hpp>
#include <boost/int128/detail/float_conversion.hpp>

#ifndef BOOST_INT128_BUILD_MODULE

Expand Down Expand Up @@ -128,8 +129,9 @@ uint128_t
#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
// Uses the builtin 128-bit conversion where one exists, and otherwise composes
// the words as high * 2^64 + low with an exact 2^64 constant, which is the value
// ldexp(static_cast<T>(high), 64) + static_cast<T>(low) computes
BOOST_INT128_HOST_DEVICE constexpr operator float() const noexcept;
BOOST_INT128_HOST_DEVICE constexpr operator double() const noexcept;

Expand Down Expand Up @@ -287,29 +289,52 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t abs(const uint1
// Float Conversion Operators
//=====================================

// The most correct way to do this would be std::ldexp(static_cast<T>(high), 64) + static_cast<T>(low);
// Since std::ldexp is not constexpr until C++23 we can work around this by multiplying the high word
// by 0xFFFFFFFF in order to generally replicate what ldexp is doing in the constexpr context.
// We also avoid pulling in <quadmath.h> for the __float128 case where we would need ldexpq
// When the builtin 128-bit type exists we convert through it since the compiler
// runtime (__floatuntisf and friends) is correctly rounded. The portable fallback
// composes the words as high * 2^64 + low; see detail/float_conversion.hpp

BOOST_INT128_HOST_DEVICE constexpr uint128_t::operator float() const noexcept
{
return static_cast<float>(high) * detail::offset_value_v<float> + static_cast<float>(low);
#if defined(BOOST_INT128_HAS_INT128) && !defined(BOOST_INT128_HAS_GPU_SUPPORT)

return static_cast<float>(static_cast<detail::builtin_u128>(*this));

#else

return detail::unsigned_words_to_float<float>(high, low);

#endif
}

BOOST_INT128_HOST_DEVICE constexpr uint128_t::operator double() const noexcept
{
return static_cast<double>(high) * detail::offset_value_v<double> + static_cast<double>(low);
#if defined(BOOST_INT128_HAS_INT128) && !defined(BOOST_INT128_HAS_GPU_SUPPORT)

return static_cast<double>(static_cast<detail::builtin_u128>(*this));

#else

return detail::unsigned_words_to_float<double>(high, low);

#endif
}

#if !defined(BOOST_INT128_HAS_GPU_SUPPORT)

constexpr uint128_t::operator long double() const noexcept
{
return static_cast<long double>(high) * detail::offset_value_v<long double> + static_cast<long double>(low);
#if defined(BOOST_INT128_HAS_INT128)

return static_cast<long double>(static_cast<detail::builtin_u128>(*this));

#else

return detail::unsigned_words_to_float<long double>(high, low);

#endif
}

#endif // __NVCC__
#endif // BOOST_INT128_HAS_GPU_SUPPORT

//=====================================
// Float Construction
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ run github_issue_210.cpp ;
run github_issue_221.cpp ;
run github_issue_272.cpp ;
run github_issue_377.cpp ;
run github_issue_432.cpp ;

# Compilation of individual headers
compile compile_tests/int128_master_header_compile.cpp ;
Expand Down
Loading
Loading