diff options
Diffstat (limited to 'absl/numeric')
-rw-r--r-- | absl/numeric/int128.cc | 22 | ||||
-rw-r--r-- | absl/numeric/int128_test.cc | 14 |
2 files changed, 36 insertions, 0 deletions
diff --git a/absl/numeric/int128.cc b/absl/numeric/int128.cc index 33f528ce9cb7..93b62c52901e 100644 --- a/absl/numeric/int128.cc +++ b/absl/numeric/int128.cc @@ -123,6 +123,28 @@ uint128 MakeUint128FromFloat(T v) { return MakeUint128(0, static_cast<uint64_t>(v)); } + +#if defined(__clang__) && !defined(__SSE3__) +// Workaround for clang bug: https://bugs.llvm.org/show_bug.cgi?id=38289 +// Casting from long double to uint64_t is miscompiled and drops bits. +// It is more work, so only use when we need the workaround. +uint128 MakeUint128FromFloat(long double v) { + // Go 50 bits at a time, that fits in a double + static_assert(std::numeric_limits<double>::digits >= 50, ""); + static_assert(std::numeric_limits<long double>::digits <= 150, ""); + // Undefined behavior if v is not finite or cannot fit into uint128. + assert(std::isfinite(v) && v > -1 && v < std::ldexp(1.0L, 128)); + + v = std::ldexp(v, -100); + uint64_t w0 = static_cast<uint64_t>(static_cast<double>(std::trunc(v))); + v = std::ldexp(v - static_cast<double>(w0), 50); + uint64_t w1 = static_cast<uint64_t>(static_cast<double>(std::trunc(v))); + v = std::ldexp(v - static_cast<double>(w1), 50); + uint64_t w2 = static_cast<uint64_t>(static_cast<double>(std::trunc(v))); + return (static_cast<uint128>(w0) << 100) | (static_cast<uint128>(w1) << 50) | + static_cast<uint128>(w2); +} +#endif // __clang__ && !__SSE3__ } // namespace uint128::uint128(float v) : uint128(MakeUint128FromFloat(v)) {} diff --git a/absl/numeric/int128_test.cc b/absl/numeric/int128_test.cc index 216ec50c54ec..5e1b5ec352b0 100644 --- a/absl/numeric/int128_test.cc +++ b/absl/numeric/int128_test.cc @@ -271,6 +271,20 @@ TEST(Uint128, ConversionTests) { EXPECT_EQ(static_cast<absl::uint128>(round_to_zero), 0); EXPECT_EQ(static_cast<absl::uint128>(round_to_five), 5); EXPECT_EQ(static_cast<absl::uint128>(round_to_nine), 9); + + absl::uint128 highest_precision_in_long_double = + ~absl::uint128{} >> (128 - std::numeric_limits<long double>::digits); + EXPECT_EQ(highest_precision_in_long_double, + static_cast<absl::uint128>( + static_cast<long double>(highest_precision_in_long_double))); + // Apply a mask just to make sure all the bits are the right place. + const absl::uint128 arbitrary_mask = + absl::MakeUint128(0xa29f622677ded751, 0xf8ca66add076f468); + EXPECT_EQ(highest_precision_in_long_double & arbitrary_mask, + static_cast<absl::uint128>(static_cast<long double>( + highest_precision_in_long_double & arbitrary_mask))); + + EXPECT_EQ(static_cast<absl::uint128>(-0.1L), 0); } TEST(Uint128, OperatorAssignReturnRef) { |