[llvm] [Support][APInt] Fix sign extension, exponent and mantissa in APInt::roundToDouble (PR #192451)
Andi Drebes via llvm-commits
llvm-commits at lists.llvm.org
Thu Apr 16 21:58:43 PDT 2026
https://github.com/andidr updated https://github.com/llvm/llvm-project/pull/192451
>From f1e40a2e2eabdf7030189ca5312f81c789e62410 Mon Sep 17 00:00:00 2001
From: Andi Drebes <andi at drebesium.org>
Date: Thu, 16 Apr 2026 15:05:32 +0200
Subject: [PATCH] [Support][APInt] Fix sign extension, exponent and mantissa in
APInt::roundToDouble
Conversion of an `APInt` to double via `APInt::roundToDouble` misses
several edge cases that result in crashes due to an assertion or in
erroneous values due to incorrect values for the exponent and mantissa
of the generated double.
The assertion is triggered when attempting to convert a multi-word
`APInt` without active bits or with active bits only in the first
word, e.g.,
`APInt(65, 0, true).roundToDouble(true)` or
`APInt(65, 1, true).roundToDouble(true)`
The issue is caused by passing the bit width as the argument to
`SignExtend64`, exceeding the maximum expected bit width of 64.
Incorrect values for the mantissa or exponent are calculated for any
multi-word, unsigned value, e.g.,
`APInt(65, "18446744073709551616" /* 2^64 */, 10).roundToDouble(false)`
This is due to `APInt::roundToDouble` expecting the exponent to
correspond to the highest of the fractional part of the double rather
than to the highest active bit and due to the missing code clearing
the highest bit after generation of the mantissa.
This patch solves the issues by by delegating the conversion to
`APFloat::convertFromAPInt` with double semantics and rounding towards
zero. Tests are added for the edge cases and some regular cases.
---
llvm/lib/Support/APInt.cpp | 62 ++++++--------------------------
llvm/unittests/ADT/APIntTest.cpp | 39 ++++++++++++++++++++
2 files changed, 50 insertions(+), 51 deletions(-)
diff --git a/llvm/lib/Support/APInt.cpp b/llvm/lib/Support/APInt.cpp
index 6aa5fc615a302..ef64cfb11310f 100644
--- a/llvm/lib/Support/APInt.cpp
+++ b/llvm/lib/Support/APInt.cpp
@@ -11,6 +11,7 @@
//
//===----------------------------------------------------------------------===//
+#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/FoldingSet.h"
@@ -890,7 +891,8 @@ APInt llvm::APIntOps::RoundDoubleToAPInt(double Double, unsigned width) {
return isNeg ? -Tmp : Tmp;
}
-/// This function converts this APInt to a double.
+/// Converts this APInt to a double with rounding towards zero.
+///
/// The layout for double is as following (IEEE Standard 754):
/// --------------------------------------
/// | Sign Exponent Fraction Bias |
@@ -898,58 +900,16 @@ APInt llvm::APIntOps::RoundDoubleToAPInt(double Double, unsigned width) {
/// | 1[63] 11[62-52] 52[51-00] 1023 |
/// --------------------------------------
double APInt::roundToDouble(bool isSigned) const {
- // Handle the simple case where the value is contained in one uint64_t.
- // It is wrong to optimize getWord(0) to VAL; there might be more than one word.
- if (isSingleWord() || getActiveBits() <= APINT_BITS_PER_WORD) {
- if (isSigned) {
- int64_t sext = SignExtend64(getWord(0), BitWidth);
- return double(sext);
- }
- return double(getWord(0));
- }
-
- // Determine if the value is negative.
- bool isNeg = isSigned ? (*this)[BitWidth-1] : false;
-
- // Construct the absolute value if we're negative.
- APInt Tmp(isNeg ? -(*this) : (*this));
-
- // Figure out how many bits we're using.
- unsigned n = Tmp.getActiveBits();
-
- // The exponent (without bias normalization) is just the number of bits
- // we are using. Note that the sign bit is gone since we constructed the
- // absolute value.
- uint64_t exp = n;
+ // Early exit for zero-length values; APFloat::convertFromAPInt only
+ // applies to values with at least one word.
+ if (BitWidth == 0)
+ return 0.0;
- // Return infinity for exponent overflow
- if (exp > 1023) {
- if (!isSigned || !isNeg)
- return std::numeric_limits<double>::infinity();
- else
- return -std::numeric_limits<double>::infinity();
- }
- exp += 1023; // Increment for 1023 bias
-
- // Number of bits in mantissa is 52. To obtain the mantissa value, we must
- // extract the high 52 bits from the correct words in pVal.
- uint64_t mantissa;
- unsigned hiWord = whichWord(n-1);
- if (hiWord == 0) {
- mantissa = Tmp.U.pVal[0];
- if (n > 52)
- mantissa >>= n - 52; // shift down, we want the top 52 bits.
- } else {
- assert(hiWord > 0 && "huh?");
- uint64_t hibits = Tmp.U.pVal[hiWord] << (52 - n % APINT_BITS_PER_WORD);
- uint64_t lobits = Tmp.U.pVal[hiWord-1] >> (11 + n % APINT_BITS_PER_WORD);
- mantissa = hibits | lobits;
- }
+ APFloat f(APFloat::IEEEdouble());
+ f.convertFromAPInt(*this, isSigned,
+ llvm::APFloatBase::roundingMode::TowardZero);
- // The leading bit of mantissa is implicit, so get rid of it.
- uint64_t sign = isNeg ? (1ULL << (APINT_BITS_PER_WORD - 1)) : 0;
- uint64_t I = sign | (exp << 52) | mantissa;
- return bit_cast<double>(I);
+ return f.convertToDouble();
}
// Truncate to new width.
diff --git a/llvm/unittests/ADT/APIntTest.cpp b/llvm/unittests/ADT/APIntTest.cpp
index ee4c59de34fc4..56cf0a83fc807 100644
--- a/llvm/unittests/ADT/APIntTest.cpp
+++ b/llvm/unittests/ADT/APIntTest.cpp
@@ -3980,4 +3980,43 @@ TEST(APIntTest, clmulh) {
.getSExtValue(),
21845);
}
+
+TEST(APIntTest, roundToDouble) {
+ EXPECT_EQ(APInt(0, 0, false).roundToDouble(false), 0.0);
+
+ // Single-word, positive
+ EXPECT_EQ(APInt(64, 0, false).roundToDouble(false), 0.0);
+ EXPECT_EQ(APInt(64, 1, false).roundToDouble(false), 1.0);
+ EXPECT_EQ(APInt(64, 2, false).roundToDouble(false), 2.0);
+ EXPECT_EQ(APInt(64, 1ULL << 63, false).roundToDouble(false),
+ 9223372036854775808.0);
+
+ // Single-word, negative
+ EXPECT_EQ(APInt(64, 0, true).roundToDouble(true), 0.0);
+ EXPECT_EQ(APInt(64, -1, true).roundToDouble(true), -1.0);
+ EXPECT_EQ(APInt(64, -2, true).roundToDouble(true), -2.0);
+ EXPECT_EQ(APInt(64, 1ULL << 63, true).roundToDouble(true),
+ -9223372036854775808.0);
+
+ // Multi-word, positive, active bits in first word
+ EXPECT_EQ(APInt(65, 0, false).roundToDouble(false), 0.0);
+ EXPECT_EQ(APInt(65, 1, false).roundToDouble(false), 1.0);
+ EXPECT_EQ(APInt(65, 2, false).roundToDouble(false), 2.0);
+ EXPECT_EQ(APInt(65, 1ULL << 63, false).roundToDouble(true),
+ 9223372036854775808.0);
+
+ // Multi-word, positive, active bits outside first word
+ EXPECT_EQ(
+ APInt(65, "18446744073709551616" /* 2^64 */, 10).roundToDouble(false),
+ 18446744073709551616.0);
+
+ // Multi-word, negative
+ EXPECT_EQ(APInt(65, 0, true).roundToDouble(true), 0.0);
+
+ EXPECT_EQ(APInt(65, -1, true).roundToDouble(true), -1.0);
+ EXPECT_EQ(APInt(65, -2, true).roundToDouble(true), -2.0);
+ EXPECT_EQ(
+ APInt(65, "18446744073709551616" /* 2^64 */, 10).roundToDouble(true),
+ -18446744073709551616.0);
+}
} // end anonymous namespace
More information about the llvm-commits
mailing list