summaryrefslogtreecommitdiff
path: root/absl/numeric/int128.cc
diff options
context:
space:
mode:
Diffstat (limited to 'absl/numeric/int128.cc')
-rw-r--r--absl/numeric/int128.cc22
1 files changed, 22 insertions, 0 deletions
diff --git a/absl/numeric/int128.cc b/absl/numeric/int128.cc
index 33f528ce..93b62c52 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)) {}