[clang][APFloat] Print floating-point literals in the shortest round-trip form - #218471
[clang][APFloat] Print floating-point literals in the shortest round-trip form#218471conrade-ctc wants to merge 1 commit into
Conversation
|
@llvm/pr-subscribers-mlir-core @llvm/pr-subscribers-llvm-support Author: Emery Conrad (conrade-ctc) ChangesStmtPrinter printed a The probe step is a new Three tests pinned the old maximum-precision output and were updated. A new 🤖 Done with the help of Claude Code (Fable 5, human in the loop) Full diff: https://github.com/llvm/llvm-project/pull/218471.diff 12 Files Affected:
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index ca0dbfa2af229..078d0a74bbf4b 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -98,6 +98,10 @@ features cannot lower the translation-unit ABI level;
### AST Dumping Potentially Breaking Changes
+- The AST printer (`-ast-print`) now prints a floating-point literal as the
+ shortest decimal form that round-trips to the same value. For example,
+ `3.14` printed as `3.1400000000000001` before and prints as `3.14` now.
+
### Clang Frontend Potentially Breaking Changes
- Templight support has been removed.
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index e957b03b7e898..30df61440e15f 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -1594,7 +1594,7 @@ void StmtPrinter::VisitFixedPointLiteral(FixedPointLiteral *Node) {
static void PrintFloatingLiteral(raw_ostream &OS, FloatingLiteral *Node,
bool PrintSuffix) {
SmallString<16> Str;
- Node->getValue().toString(Str);
+ Node->getValue().toStringShortest(Str);
OS << Str;
if (Str.find_first_not_of("-0123456789") == StringRef::npos)
OS << '.'; // Trailing dot in order to separate from ints.
diff --git a/clang/test/AST/ast-print-float-shortest.cpp b/clang/test/AST/ast-print-float-shortest.cpp
new file mode 100644
index 0000000000000..b5f21a7ae27ec
--- /dev/null
+++ b/clang/test/AST/ast-print-float-shortest.cpp
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -ast-print %s | FileCheck %s
+
+// Floating literals print as the shortest decimal form that round-trips.
+
+double d1 = 3.14;
+// CHECK: double d1 = 3.14;
+double d2 = 4.0;
+// CHECK: double d2 = 4.;
+double d3 = 0.5;
+// CHECK: double d3 = 0.5;
+double d4 = 1e10;
+// CHECK: double d4 = 1.0E+10;
+double d5 = 1e-300;
+// CHECK: double d5 = 1.0E-300;
+double d6 = 0.1;
+// CHECK: double d6 = 0.1;
+// The smallest double denormal: the shortest round-trip form is 5E-324.
+double d7 = 4.9406564584124654e-324;
+// CHECK: double d7 = 5.0E-324;
+// DBL_MAX needs all 17 significant digits; nothing shorter round-trips.
+double d8 = 1.7976931348623157e+308;
+// CHECK: double d8 = 1.7976931348623157E+308;
+double d9 = -3.14;
+// CHECK: double d9 = -3.14;
+
+float f1 = 3.14f;
+// CHECK: float f1 = 3.14F;
+float f2 = 0.1f;
+// CHECK: float f2 = 0.1F;
+// A hex float keeps its value, printed in decimal.
+float f3 = 0x1.8p3f;
+// CHECK: float f3 = 12.F;
+
+// x86 80-bit extended precision.
+long double ld1 = 0.1L;
+// CHECK: long double ld1 = 0.1L;
+long double ld2 = 3.14L;
+// CHECK: long double ld2 = 3.14L;
+
+__float128 q1 = 0.1q;
+// CHECK: __float128 q1 = 0.1Q;
diff --git a/clang/test/PCH/floating-literal.c b/clang/test/PCH/floating-literal.c
index b5ff6fe84b88c..4590b1990b79d 100644
--- a/clang/test/PCH/floating-literal.c
+++ b/clang/test/PCH/floating-literal.c
@@ -8,12 +8,12 @@
// targets with 128-bit IEEE long doubles.
long double foo = 1.0E4000L;
-// CHECK: long double foo = 1.00000000000000000000000000000000004E+4000L;
+// CHECK: long double foo = 1.0E+4000L;
// Just as well check the others are still sane while we're here...
double bar = 1.0E300;
-// CHECK: double bar = 1.0000000000000001E+300;
+// CHECK: double bar = 1.0E+300;
float wibble = 1.0E40;
// CHECK: float wibble = 1.0E+40;
diff --git a/clang/test/PCH/objc_literals.m b/clang/test/PCH/objc_literals.m
index 16ed6aeb9b933..ede3358e2fe69 100644
--- a/clang/test/PCH/objc_literals.m
+++ b/clang/test/PCH/objc_literals.m
@@ -45,18 +45,18 @@ static inline void test_numeric_literals(void) {
// CHECK-PRINT: id intlit = @17
// CHECK-IR: {{call.*17}}
id intlit = @17;
- // CHECK-PRINT: id floatlit = @17.449999999999999
+ // CHECK-PRINT: id floatlit = @17.45
// CHECK-IR: {{call.*1.745}}
id floatlit = @17.45;
}
static inline void test_array_literals(void) {
- // CHECK-PRINT: id arraylit = @[ @17, @17.449999999999999
+ // CHECK-PRINT: id arraylit = @[ @17, @17.45
id arraylit = @[@17, @17.45];
}
static inline void test_dictionary_literals(void) {
- // CHECK-PRINT: id dictlit = @{ @17 : {{@17.449999999999999[^,]*}}, @"hello" : @"world" };
+ // CHECK-PRINT: id dictlit = @{ @17 : {{@17.45[^,]*}}, @"hello" : @"world" };
id dictlit = @{@17 : @17.45, @"hello" : @"world" };
}
diff --git a/clang/test/Sema/x86_64-linux-android.c b/clang/test/Sema/x86_64-linux-android.c
index 252570579f607..75158dc066dc1 100644
--- a/clang/test/Sema/x86_64-linux-android.c
+++ b/clang/test/Sema/x86_64-linux-android.c
@@ -7,4 +7,4 @@ extern int a1_i[__alignof(long double) == 16 ? 1 : -1];
// Verify that long double is 128 bit IEEEquad
long double foo = 1.0E4000L;
-// CHECK: long double foo = 1.00000000000000000000000000000000004E+4000L;
+// CHECK: long double foo = 1.0E+4000L;
diff --git a/llvm/include/llvm/ADT/APFloat.h b/llvm/include/llvm/ADT/APFloat.h
index a9a3dc89a1730..770ad0236ecf5 100644
--- a/llvm/include/llvm/ADT/APFloat.h
+++ b/llvm/include/llvm/ADT/APFloat.h
@@ -1615,6 +1615,20 @@ class APFloat : public APFloatBase {
toString(Str, FormatPrecision, FormatMaxPadding, TruncateZero));
}
+ /// Format the value as toString does and return true if the string parses
+ /// back bitwise equal. Non-finite spellings ("+Inf", "NaN") parse back
+ /// here but may not fit a caller's grammar.
+ LLVM_ABI bool toStringRoundTrip(SmallVectorImpl<char> &Str,
+ unsigned FormatPrecision = 0,
+ unsigned FormatMaxPadding = 3,
+ bool TruncateZero = true) const;
+
+ /// Like toString, but picks the smallest FormatPrecision whose output
+ /// parses back bitwise equal: "3.14", not "3.1400000000000001".
+ LLVM_ABI void toStringShortest(SmallVectorImpl<char> &Str,
+ unsigned FormatMaxPadding = 3,
+ bool TruncateZero = true) const;
+
LLVM_ABI void print(raw_ostream &) const;
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index c3202eea12c28..fb746cbecb6f3 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -1589,8 +1589,8 @@ static void writeAPFloatInternal(raw_ostream &Out, const APFloat &APF) {
// same APFloat value, then we know that it is safe to use it. Otherwise, fall
// back onto the hexadecimal format.
SmallString<128> StrVal;
- APF.toString(StrVal, 6, 0, false);
- if (APFloat(APF.getSemantics(), StrVal) == APF) {
+ if (APF.toStringRoundTrip(StrVal, /*FormatPrecision=*/6,
+ /*FormatMaxPadding=*/0, /*TruncateZero=*/false)) {
Out << StrVal;
return;
}
diff --git a/llvm/lib/Support/APFloat.cpp b/llvm/lib/Support/APFloat.cpp
index 22528d9e93f73..0b7452208fcae 100644
--- a/llvm/lib/Support/APFloat.cpp
+++ b/llvm/lib/Support/APFloat.cpp
@@ -18,6 +18,7 @@
#include "llvm/ADT/FoldingSet.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
@@ -5973,6 +5974,43 @@ APFloat APFloat::getAllOnesValue(const fltSemantics &Semantics) {
return APFloat(Semantics, APInt::getAllOnes(Semantics.sizeInBits));
}
+bool APFloat::toStringRoundTrip(SmallVectorImpl<char> &Str,
+ unsigned FormatPrecision,
+ unsigned FormatMaxPadding,
+ bool TruncateZero) const {
+ SmallString<32> Buf;
+ toString(Buf, FormatPrecision, FormatMaxPadding, TruncateZero);
+ Str.append(Buf.begin(), Buf.end());
+ APFloat Parsed(getSemantics());
+ Expected<opStatus> Status =
+ Parsed.convertFromString(Buf, rmNearestTiesToEven);
+ if (!Status) {
+ consumeError(Status.takeError());
+ return false;
+ }
+ return Parsed.bitwiseIsEqual(*this);
+}
+
+void APFloat::toStringShortest(SmallVectorImpl<char> &Str,
+ unsigned FormatMaxPadding,
+ bool TruncateZero) const {
+ SmallString<32> Best;
+ toString(Best, /*FormatPrecision=*/0, FormatMaxPadding, TruncateZero);
+ if (isFiniteNonZero()) {
+ // Probe below the conservative natural precision (toStringImpl's FIXME).
+ for (unsigned Precision = 1, MaxPrecision = Best.size();
+ Precision < MaxPrecision; ++Precision) {
+ SmallString<32> Candidate;
+ if (toStringRoundTrip(Candidate, Precision, FormatMaxPadding,
+ TruncateZero)) {
+ Best = Candidate;
+ break;
+ }
+ }
+ }
+ Str.append(Best.begin(), Best.end());
+}
+
void APFloat::print(raw_ostream &OS) const {
SmallVector<char, 16> Buffer;
toString(Buffer);
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 0993441abf22d..81445fd9a777d 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -99,9 +99,9 @@ void AnyValue::print(Context &Ctx, raw_ostream &OS) const {
// exponential notation if it is lossless, otherwise output it in
// hexadecimal notation.
SmallString<16> StrVal;
- FloatVal.toString(StrVal, /*FormatPrecision=*/6, /*FormatMaxPadding=*/0,
- /*TruncateZero=*/false);
- if (APFloat(FloatVal.getSemantics(), StrVal).bitwiseIsEqual(FloatVal)) {
+ if (FloatVal.toStringRoundTrip(StrVal, /*FormatPrecision=*/6,
+ /*FormatMaxPadding=*/0,
+ /*TruncateZero=*/false)) {
OS << StrVal;
} else {
StrVal.clear();
diff --git a/llvm/unittests/ADT/APFloatTest.cpp b/llvm/unittests/ADT/APFloatTest.cpp
index ca93953537a09..94072a914da67 100644
--- a/llvm/unittests/ADT/APFloatTest.cpp
+++ b/llvm/unittests/ADT/APFloatTest.cpp
@@ -1629,6 +1629,74 @@ TEST(APFloatTest, toString) {
}
}
+static std::string convertToStringShortest(const APFloat &F, unsigned Pad = 3,
+ bool Tr = true) {
+ llvm::SmallVector<char, 100> Buffer;
+ F.toStringShortest(Buffer, Pad, Tr);
+ return std::string(Buffer.data(), Buffer.size());
+}
+
+TEST(APFloatTest, toStringRoundTrip) {
+ SmallString<32> Str;
+ // 6-digit formatting is lossless for 3.14 but lossy for pi/4; the string
+ // is appended either way (mlir::AsmPrinter's printFloatValue contract).
+ ASSERT_TRUE(APFloat(3.14).toStringRoundTrip(Str, 6, 0, false));
+ ASSERT_EQ("3.140000e+00", Str);
+ Str.clear();
+ ASSERT_FALSE(
+ APFloat(0.78539816339744830961).toStringRoundTrip(Str, 6, 0, false));
+ ASSERT_EQ("7.853980e-01", Str);
+ Str.clear();
+ // FormatPrecision = 0 (natural precision) always round-trips.
+ ASSERT_TRUE(APFloat(0.78539816339744830961).toStringRoundTrip(Str));
+ ASSERT_EQ("0.78539816339744828", Str);
+ Str.clear();
+ // The Inf spelling parses back via convertFromString, although it is not
+ // valid in the IR or MLIR grammars (callers gate non-finite values).
+ ASSERT_TRUE(APFloat::getInf(APFloat::IEEEdouble()).toStringRoundTrip(Str));
+ ASSERT_EQ("+Inf", Str);
+}
+
+TEST(APFloatTest, toStringShortest) {
+ ASSERT_EQ("3.14", convertToStringShortest(APFloat(3.14)));
+ ASSERT_EQ("-3.14", convertToStringShortest(APFloat(-3.14)));
+ ASSERT_EQ("4", convertToStringShortest(APFloat(4.0)));
+ ASSERT_EQ("0.5", convertToStringShortest(APFloat(0.5)));
+ ASSERT_EQ("0.1", convertToStringShortest(APFloat(0.1)));
+ ASSERT_EQ("873.1834", convertToStringShortest(APFloat(873.1834)));
+ ASSERT_EQ("1.0E+10", convertToStringShortest(APFloat(1e10)));
+ // DBL_MAX needs every natural-precision digit.
+ ASSERT_EQ("1.7976931348623157E+308",
+ convertToStringShortest(APFloat(1.7976931348623157E+308)));
+ // The smallest double denormal.
+ ASSERT_EQ("5.0E-324",
+ convertToStringShortest(APFloat(4.9406564584124654e-324)));
+
+ // Non-double semantics.
+ ASSERT_EQ("3.14", convertToStringShortest(APFloat(3.14f)));
+ ASSERT_EQ("0.1", convertToStringShortest(APFloat(0.1f)));
+ ASSERT_EQ("0.1", convertToStringShortest(
+ APFloat(APFloat::x87DoubleExtended(), "0.1")));
+ ASSERT_EQ("0.1",
+ convertToStringShortest(APFloat(APFloat::IEEEquad(), "0.1")));
+ ASSERT_EQ("0.1", convertToStringShortest(
+ APFloat(APFloat::PPCDoubleDouble(), "0.1")));
+
+ // FormatMaxPadding and TruncateZero pass through to toString.
+ ASSERT_EQ("1.0E+1", convertToStringShortest(APFloat(10.0), 0));
+ ASSERT_EQ("1.0e+01", convertToStringShortest(APFloat(10.0), 0, false));
+
+ // Zero and non-finite values format exactly as toString.
+ ASSERT_EQ("0", convertToStringShortest(APFloat(0.0)));
+ ASSERT_EQ("-0", convertToStringShortest(APFloat(-0.0)));
+ ASSERT_EQ("+Inf",
+ convertToStringShortest(APFloat::getInf(APFloat::IEEEdouble())));
+ ASSERT_EQ("-Inf", convertToStringShortest(
+ APFloat::getInf(APFloat::IEEEdouble(), true)));
+ ASSERT_EQ("NaN",
+ convertToStringShortest(APFloat::getNaN(APFloat::IEEEdouble())));
+}
+
TEST(APFloatTest, toInteger) {
bool isExact = false;
APSInt result(5, /*isUnsigned=*/true);
diff --git a/mlir/lib/IR/AsmPrinter.cpp b/mlir/lib/IR/AsmPrinter.cpp
index 47a33a116f92c..274e9976e5733 100644
--- a/mlir/lib/IR/AsmPrinter.cpp
+++ b/mlir/lib/IR/AsmPrinter.cpp
@@ -2265,8 +2265,10 @@ static void printFloatValue(const APFloat &apValue, raw_ostream &os,
bool isNaN = apValue.isNaN();
if (!isInf && !isNaN) {
SmallString<128> strValue;
- apValue.toString(strValue, /*FormatPrecision=*/6, /*FormatMaxPadding=*/0,
- /*TruncateZero=*/false);
+ bool isLossless = apValue.toStringRoundTrip(strValue,
+ /*FormatPrecision=*/6,
+ /*FormatMaxPadding=*/0,
+ /*TruncateZero=*/false);
// Check to make sure that the stringized number is not some string like
// "Inf" or NaN, that atof will accept, but the lexer will not. Check
@@ -2276,9 +2278,9 @@ static void printFloatValue(const APFloat &apValue, raw_ostream &os,
(strValue[1] >= '0' && strValue[1] <= '9'))) &&
"[-+]?[0-9] regex does not match!");
- // Parse back the stringized version and check that the value is equal
- // (i.e., there is no precision loss).
- if (APFloat(apValue.getSemantics(), strValue).bitwiseIsEqual(apValue)) {
+ // The stringized version has no precision loss if it parses back to the
+ // same value.
+ if (isLossless) {
os << strValue;
return;
}
|
|
@llvm/pr-subscribers-llvm-adt Author: Emery Conrad (conrade-ctc) ChangesStmtPrinter printed a The probe step is a new Three tests pinned the old maximum-precision output and were updated. A new 🤖 Done with the help of Claude Code (Fable 5, human in the loop) Full diff: https://github.com/llvm/llvm-project/pull/218471.diff 12 Files Affected:
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index ca0dbfa2af229..078d0a74bbf4b 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -98,6 +98,10 @@ features cannot lower the translation-unit ABI level;
### AST Dumping Potentially Breaking Changes
+- The AST printer (`-ast-print`) now prints a floating-point literal as the
+ shortest decimal form that round-trips to the same value. For example,
+ `3.14` printed as `3.1400000000000001` before and prints as `3.14` now.
+
### Clang Frontend Potentially Breaking Changes
- Templight support has been removed.
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index e957b03b7e898..30df61440e15f 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -1594,7 +1594,7 @@ void StmtPrinter::VisitFixedPointLiteral(FixedPointLiteral *Node) {
static void PrintFloatingLiteral(raw_ostream &OS, FloatingLiteral *Node,
bool PrintSuffix) {
SmallString<16> Str;
- Node->getValue().toString(Str);
+ Node->getValue().toStringShortest(Str);
OS << Str;
if (Str.find_first_not_of("-0123456789") == StringRef::npos)
OS << '.'; // Trailing dot in order to separate from ints.
diff --git a/clang/test/AST/ast-print-float-shortest.cpp b/clang/test/AST/ast-print-float-shortest.cpp
new file mode 100644
index 0000000000000..b5f21a7ae27ec
--- /dev/null
+++ b/clang/test/AST/ast-print-float-shortest.cpp
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -ast-print %s | FileCheck %s
+
+// Floating literals print as the shortest decimal form that round-trips.
+
+double d1 = 3.14;
+// CHECK: double d1 = 3.14;
+double d2 = 4.0;
+// CHECK: double d2 = 4.;
+double d3 = 0.5;
+// CHECK: double d3 = 0.5;
+double d4 = 1e10;
+// CHECK: double d4 = 1.0E+10;
+double d5 = 1e-300;
+// CHECK: double d5 = 1.0E-300;
+double d6 = 0.1;
+// CHECK: double d6 = 0.1;
+// The smallest double denormal: the shortest round-trip form is 5E-324.
+double d7 = 4.9406564584124654e-324;
+// CHECK: double d7 = 5.0E-324;
+// DBL_MAX needs all 17 significant digits; nothing shorter round-trips.
+double d8 = 1.7976931348623157e+308;
+// CHECK: double d8 = 1.7976931348623157E+308;
+double d9 = -3.14;
+// CHECK: double d9 = -3.14;
+
+float f1 = 3.14f;
+// CHECK: float f1 = 3.14F;
+float f2 = 0.1f;
+// CHECK: float f2 = 0.1F;
+// A hex float keeps its value, printed in decimal.
+float f3 = 0x1.8p3f;
+// CHECK: float f3 = 12.F;
+
+// x86 80-bit extended precision.
+long double ld1 = 0.1L;
+// CHECK: long double ld1 = 0.1L;
+long double ld2 = 3.14L;
+// CHECK: long double ld2 = 3.14L;
+
+__float128 q1 = 0.1q;
+// CHECK: __float128 q1 = 0.1Q;
diff --git a/clang/test/PCH/floating-literal.c b/clang/test/PCH/floating-literal.c
index b5ff6fe84b88c..4590b1990b79d 100644
--- a/clang/test/PCH/floating-literal.c
+++ b/clang/test/PCH/floating-literal.c
@@ -8,12 +8,12 @@
// targets with 128-bit IEEE long doubles.
long double foo = 1.0E4000L;
-// CHECK: long double foo = 1.00000000000000000000000000000000004E+4000L;
+// CHECK: long double foo = 1.0E+4000L;
// Just as well check the others are still sane while we're here...
double bar = 1.0E300;
-// CHECK: double bar = 1.0000000000000001E+300;
+// CHECK: double bar = 1.0E+300;
float wibble = 1.0E40;
// CHECK: float wibble = 1.0E+40;
diff --git a/clang/test/PCH/objc_literals.m b/clang/test/PCH/objc_literals.m
index 16ed6aeb9b933..ede3358e2fe69 100644
--- a/clang/test/PCH/objc_literals.m
+++ b/clang/test/PCH/objc_literals.m
@@ -45,18 +45,18 @@ static inline void test_numeric_literals(void) {
// CHECK-PRINT: id intlit = @17
// CHECK-IR: {{call.*17}}
id intlit = @17;
- // CHECK-PRINT: id floatlit = @17.449999999999999
+ // CHECK-PRINT: id floatlit = @17.45
// CHECK-IR: {{call.*1.745}}
id floatlit = @17.45;
}
static inline void test_array_literals(void) {
- // CHECK-PRINT: id arraylit = @[ @17, @17.449999999999999
+ // CHECK-PRINT: id arraylit = @[ @17, @17.45
id arraylit = @[@17, @17.45];
}
static inline void test_dictionary_literals(void) {
- // CHECK-PRINT: id dictlit = @{ @17 : {{@17.449999999999999[^,]*}}, @"hello" : @"world" };
+ // CHECK-PRINT: id dictlit = @{ @17 : {{@17.45[^,]*}}, @"hello" : @"world" };
id dictlit = @{@17 : @17.45, @"hello" : @"world" };
}
diff --git a/clang/test/Sema/x86_64-linux-android.c b/clang/test/Sema/x86_64-linux-android.c
index 252570579f607..75158dc066dc1 100644
--- a/clang/test/Sema/x86_64-linux-android.c
+++ b/clang/test/Sema/x86_64-linux-android.c
@@ -7,4 +7,4 @@ extern int a1_i[__alignof(long double) == 16 ? 1 : -1];
// Verify that long double is 128 bit IEEEquad
long double foo = 1.0E4000L;
-// CHECK: long double foo = 1.00000000000000000000000000000000004E+4000L;
+// CHECK: long double foo = 1.0E+4000L;
diff --git a/llvm/include/llvm/ADT/APFloat.h b/llvm/include/llvm/ADT/APFloat.h
index a9a3dc89a1730..770ad0236ecf5 100644
--- a/llvm/include/llvm/ADT/APFloat.h
+++ b/llvm/include/llvm/ADT/APFloat.h
@@ -1615,6 +1615,20 @@ class APFloat : public APFloatBase {
toString(Str, FormatPrecision, FormatMaxPadding, TruncateZero));
}
+ /// Format the value as toString does and return true if the string parses
+ /// back bitwise equal. Non-finite spellings ("+Inf", "NaN") parse back
+ /// here but may not fit a caller's grammar.
+ LLVM_ABI bool toStringRoundTrip(SmallVectorImpl<char> &Str,
+ unsigned FormatPrecision = 0,
+ unsigned FormatMaxPadding = 3,
+ bool TruncateZero = true) const;
+
+ /// Like toString, but picks the smallest FormatPrecision whose output
+ /// parses back bitwise equal: "3.14", not "3.1400000000000001".
+ LLVM_ABI void toStringShortest(SmallVectorImpl<char> &Str,
+ unsigned FormatMaxPadding = 3,
+ bool TruncateZero = true) const;
+
LLVM_ABI void print(raw_ostream &) const;
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index c3202eea12c28..fb746cbecb6f3 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -1589,8 +1589,8 @@ static void writeAPFloatInternal(raw_ostream &Out, const APFloat &APF) {
// same APFloat value, then we know that it is safe to use it. Otherwise, fall
// back onto the hexadecimal format.
SmallString<128> StrVal;
- APF.toString(StrVal, 6, 0, false);
- if (APFloat(APF.getSemantics(), StrVal) == APF) {
+ if (APF.toStringRoundTrip(StrVal, /*FormatPrecision=*/6,
+ /*FormatMaxPadding=*/0, /*TruncateZero=*/false)) {
Out << StrVal;
return;
}
diff --git a/llvm/lib/Support/APFloat.cpp b/llvm/lib/Support/APFloat.cpp
index 22528d9e93f73..0b7452208fcae 100644
--- a/llvm/lib/Support/APFloat.cpp
+++ b/llvm/lib/Support/APFloat.cpp
@@ -18,6 +18,7 @@
#include "llvm/ADT/FoldingSet.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
@@ -5973,6 +5974,43 @@ APFloat APFloat::getAllOnesValue(const fltSemantics &Semantics) {
return APFloat(Semantics, APInt::getAllOnes(Semantics.sizeInBits));
}
+bool APFloat::toStringRoundTrip(SmallVectorImpl<char> &Str,
+ unsigned FormatPrecision,
+ unsigned FormatMaxPadding,
+ bool TruncateZero) const {
+ SmallString<32> Buf;
+ toString(Buf, FormatPrecision, FormatMaxPadding, TruncateZero);
+ Str.append(Buf.begin(), Buf.end());
+ APFloat Parsed(getSemantics());
+ Expected<opStatus> Status =
+ Parsed.convertFromString(Buf, rmNearestTiesToEven);
+ if (!Status) {
+ consumeError(Status.takeError());
+ return false;
+ }
+ return Parsed.bitwiseIsEqual(*this);
+}
+
+void APFloat::toStringShortest(SmallVectorImpl<char> &Str,
+ unsigned FormatMaxPadding,
+ bool TruncateZero) const {
+ SmallString<32> Best;
+ toString(Best, /*FormatPrecision=*/0, FormatMaxPadding, TruncateZero);
+ if (isFiniteNonZero()) {
+ // Probe below the conservative natural precision (toStringImpl's FIXME).
+ for (unsigned Precision = 1, MaxPrecision = Best.size();
+ Precision < MaxPrecision; ++Precision) {
+ SmallString<32> Candidate;
+ if (toStringRoundTrip(Candidate, Precision, FormatMaxPadding,
+ TruncateZero)) {
+ Best = Candidate;
+ break;
+ }
+ }
+ }
+ Str.append(Best.begin(), Best.end());
+}
+
void APFloat::print(raw_ostream &OS) const {
SmallVector<char, 16> Buffer;
toString(Buffer);
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 0993441abf22d..81445fd9a777d 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -99,9 +99,9 @@ void AnyValue::print(Context &Ctx, raw_ostream &OS) const {
// exponential notation if it is lossless, otherwise output it in
// hexadecimal notation.
SmallString<16> StrVal;
- FloatVal.toString(StrVal, /*FormatPrecision=*/6, /*FormatMaxPadding=*/0,
- /*TruncateZero=*/false);
- if (APFloat(FloatVal.getSemantics(), StrVal).bitwiseIsEqual(FloatVal)) {
+ if (FloatVal.toStringRoundTrip(StrVal, /*FormatPrecision=*/6,
+ /*FormatMaxPadding=*/0,
+ /*TruncateZero=*/false)) {
OS << StrVal;
} else {
StrVal.clear();
diff --git a/llvm/unittests/ADT/APFloatTest.cpp b/llvm/unittests/ADT/APFloatTest.cpp
index ca93953537a09..94072a914da67 100644
--- a/llvm/unittests/ADT/APFloatTest.cpp
+++ b/llvm/unittests/ADT/APFloatTest.cpp
@@ -1629,6 +1629,74 @@ TEST(APFloatTest, toString) {
}
}
+static std::string convertToStringShortest(const APFloat &F, unsigned Pad = 3,
+ bool Tr = true) {
+ llvm::SmallVector<char, 100> Buffer;
+ F.toStringShortest(Buffer, Pad, Tr);
+ return std::string(Buffer.data(), Buffer.size());
+}
+
+TEST(APFloatTest, toStringRoundTrip) {
+ SmallString<32> Str;
+ // 6-digit formatting is lossless for 3.14 but lossy for pi/4; the string
+ // is appended either way (mlir::AsmPrinter's printFloatValue contract).
+ ASSERT_TRUE(APFloat(3.14).toStringRoundTrip(Str, 6, 0, false));
+ ASSERT_EQ("3.140000e+00", Str);
+ Str.clear();
+ ASSERT_FALSE(
+ APFloat(0.78539816339744830961).toStringRoundTrip(Str, 6, 0, false));
+ ASSERT_EQ("7.853980e-01", Str);
+ Str.clear();
+ // FormatPrecision = 0 (natural precision) always round-trips.
+ ASSERT_TRUE(APFloat(0.78539816339744830961).toStringRoundTrip(Str));
+ ASSERT_EQ("0.78539816339744828", Str);
+ Str.clear();
+ // The Inf spelling parses back via convertFromString, although it is not
+ // valid in the IR or MLIR grammars (callers gate non-finite values).
+ ASSERT_TRUE(APFloat::getInf(APFloat::IEEEdouble()).toStringRoundTrip(Str));
+ ASSERT_EQ("+Inf", Str);
+}
+
+TEST(APFloatTest, toStringShortest) {
+ ASSERT_EQ("3.14", convertToStringShortest(APFloat(3.14)));
+ ASSERT_EQ("-3.14", convertToStringShortest(APFloat(-3.14)));
+ ASSERT_EQ("4", convertToStringShortest(APFloat(4.0)));
+ ASSERT_EQ("0.5", convertToStringShortest(APFloat(0.5)));
+ ASSERT_EQ("0.1", convertToStringShortest(APFloat(0.1)));
+ ASSERT_EQ("873.1834", convertToStringShortest(APFloat(873.1834)));
+ ASSERT_EQ("1.0E+10", convertToStringShortest(APFloat(1e10)));
+ // DBL_MAX needs every natural-precision digit.
+ ASSERT_EQ("1.7976931348623157E+308",
+ convertToStringShortest(APFloat(1.7976931348623157E+308)));
+ // The smallest double denormal.
+ ASSERT_EQ("5.0E-324",
+ convertToStringShortest(APFloat(4.9406564584124654e-324)));
+
+ // Non-double semantics.
+ ASSERT_EQ("3.14", convertToStringShortest(APFloat(3.14f)));
+ ASSERT_EQ("0.1", convertToStringShortest(APFloat(0.1f)));
+ ASSERT_EQ("0.1", convertToStringShortest(
+ APFloat(APFloat::x87DoubleExtended(), "0.1")));
+ ASSERT_EQ("0.1",
+ convertToStringShortest(APFloat(APFloat::IEEEquad(), "0.1")));
+ ASSERT_EQ("0.1", convertToStringShortest(
+ APFloat(APFloat::PPCDoubleDouble(), "0.1")));
+
+ // FormatMaxPadding and TruncateZero pass through to toString.
+ ASSERT_EQ("1.0E+1", convertToStringShortest(APFloat(10.0), 0));
+ ASSERT_EQ("1.0e+01", convertToStringShortest(APFloat(10.0), 0, false));
+
+ // Zero and non-finite values format exactly as toString.
+ ASSERT_EQ("0", convertToStringShortest(APFloat(0.0)));
+ ASSERT_EQ("-0", convertToStringShortest(APFloat(-0.0)));
+ ASSERT_EQ("+Inf",
+ convertToStringShortest(APFloat::getInf(APFloat::IEEEdouble())));
+ ASSERT_EQ("-Inf", convertToStringShortest(
+ APFloat::getInf(APFloat::IEEEdouble(), true)));
+ ASSERT_EQ("NaN",
+ convertToStringShortest(APFloat::getNaN(APFloat::IEEEdouble())));
+}
+
TEST(APFloatTest, toInteger) {
bool isExact = false;
APSInt result(5, /*isUnsigned=*/true);
diff --git a/mlir/lib/IR/AsmPrinter.cpp b/mlir/lib/IR/AsmPrinter.cpp
index 47a33a116f92c..274e9976e5733 100644
--- a/mlir/lib/IR/AsmPrinter.cpp
+++ b/mlir/lib/IR/AsmPrinter.cpp
@@ -2265,8 +2265,10 @@ static void printFloatValue(const APFloat &apValue, raw_ostream &os,
bool isNaN = apValue.isNaN();
if (!isInf && !isNaN) {
SmallString<128> strValue;
- apValue.toString(strValue, /*FormatPrecision=*/6, /*FormatMaxPadding=*/0,
- /*TruncateZero=*/false);
+ bool isLossless = apValue.toStringRoundTrip(strValue,
+ /*FormatPrecision=*/6,
+ /*FormatMaxPadding=*/0,
+ /*TruncateZero=*/false);
// Check to make sure that the stringized number is not some string like
// "Inf" or NaN, that atof will accept, but the lexer will not. Check
@@ -2276,9 +2278,9 @@ static void printFloatValue(const APFloat &apValue, raw_ostream &os,
(strValue[1] >= '0' && strValue[1] <= '9'))) &&
"[-+]?[0-9] regex does not match!");
- // Parse back the stringized version and check that the value is equal
- // (i.e., there is no precision loss).
- if (APFloat(apValue.getSemantics(), strValue).bitwiseIsEqual(apValue)) {
+ // The stringized version has no precision loss if it parses back to the
+ // same value.
+ if (isLossless) {
os << strValue;
return;
}
|
|
@llvm/pr-subscribers-llvm-ir Author: Emery Conrad (conrade-ctc) ChangesStmtPrinter printed a The probe step is a new Three tests pinned the old maximum-precision output and were updated. A new 🤖 Done with the help of Claude Code (Fable 5, human in the loop) Full diff: https://github.com/llvm/llvm-project/pull/218471.diff 12 Files Affected:
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index ca0dbfa2af229..078d0a74bbf4b 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -98,6 +98,10 @@ features cannot lower the translation-unit ABI level;
### AST Dumping Potentially Breaking Changes
+- The AST printer (`-ast-print`) now prints a floating-point literal as the
+ shortest decimal form that round-trips to the same value. For example,
+ `3.14` printed as `3.1400000000000001` before and prints as `3.14` now.
+
### Clang Frontend Potentially Breaking Changes
- Templight support has been removed.
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index e957b03b7e898..30df61440e15f 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -1594,7 +1594,7 @@ void StmtPrinter::VisitFixedPointLiteral(FixedPointLiteral *Node) {
static void PrintFloatingLiteral(raw_ostream &OS, FloatingLiteral *Node,
bool PrintSuffix) {
SmallString<16> Str;
- Node->getValue().toString(Str);
+ Node->getValue().toStringShortest(Str);
OS << Str;
if (Str.find_first_not_of("-0123456789") == StringRef::npos)
OS << '.'; // Trailing dot in order to separate from ints.
diff --git a/clang/test/AST/ast-print-float-shortest.cpp b/clang/test/AST/ast-print-float-shortest.cpp
new file mode 100644
index 0000000000000..b5f21a7ae27ec
--- /dev/null
+++ b/clang/test/AST/ast-print-float-shortest.cpp
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -ast-print %s | FileCheck %s
+
+// Floating literals print as the shortest decimal form that round-trips.
+
+double d1 = 3.14;
+// CHECK: double d1 = 3.14;
+double d2 = 4.0;
+// CHECK: double d2 = 4.;
+double d3 = 0.5;
+// CHECK: double d3 = 0.5;
+double d4 = 1e10;
+// CHECK: double d4 = 1.0E+10;
+double d5 = 1e-300;
+// CHECK: double d5 = 1.0E-300;
+double d6 = 0.1;
+// CHECK: double d6 = 0.1;
+// The smallest double denormal: the shortest round-trip form is 5E-324.
+double d7 = 4.9406564584124654e-324;
+// CHECK: double d7 = 5.0E-324;
+// DBL_MAX needs all 17 significant digits; nothing shorter round-trips.
+double d8 = 1.7976931348623157e+308;
+// CHECK: double d8 = 1.7976931348623157E+308;
+double d9 = -3.14;
+// CHECK: double d9 = -3.14;
+
+float f1 = 3.14f;
+// CHECK: float f1 = 3.14F;
+float f2 = 0.1f;
+// CHECK: float f2 = 0.1F;
+// A hex float keeps its value, printed in decimal.
+float f3 = 0x1.8p3f;
+// CHECK: float f3 = 12.F;
+
+// x86 80-bit extended precision.
+long double ld1 = 0.1L;
+// CHECK: long double ld1 = 0.1L;
+long double ld2 = 3.14L;
+// CHECK: long double ld2 = 3.14L;
+
+__float128 q1 = 0.1q;
+// CHECK: __float128 q1 = 0.1Q;
diff --git a/clang/test/PCH/floating-literal.c b/clang/test/PCH/floating-literal.c
index b5ff6fe84b88c..4590b1990b79d 100644
--- a/clang/test/PCH/floating-literal.c
+++ b/clang/test/PCH/floating-literal.c
@@ -8,12 +8,12 @@
// targets with 128-bit IEEE long doubles.
long double foo = 1.0E4000L;
-// CHECK: long double foo = 1.00000000000000000000000000000000004E+4000L;
+// CHECK: long double foo = 1.0E+4000L;
// Just as well check the others are still sane while we're here...
double bar = 1.0E300;
-// CHECK: double bar = 1.0000000000000001E+300;
+// CHECK: double bar = 1.0E+300;
float wibble = 1.0E40;
// CHECK: float wibble = 1.0E+40;
diff --git a/clang/test/PCH/objc_literals.m b/clang/test/PCH/objc_literals.m
index 16ed6aeb9b933..ede3358e2fe69 100644
--- a/clang/test/PCH/objc_literals.m
+++ b/clang/test/PCH/objc_literals.m
@@ -45,18 +45,18 @@ static inline void test_numeric_literals(void) {
// CHECK-PRINT: id intlit = @17
// CHECK-IR: {{call.*17}}
id intlit = @17;
- // CHECK-PRINT: id floatlit = @17.449999999999999
+ // CHECK-PRINT: id floatlit = @17.45
// CHECK-IR: {{call.*1.745}}
id floatlit = @17.45;
}
static inline void test_array_literals(void) {
- // CHECK-PRINT: id arraylit = @[ @17, @17.449999999999999
+ // CHECK-PRINT: id arraylit = @[ @17, @17.45
id arraylit = @[@17, @17.45];
}
static inline void test_dictionary_literals(void) {
- // CHECK-PRINT: id dictlit = @{ @17 : {{@17.449999999999999[^,]*}}, @"hello" : @"world" };
+ // CHECK-PRINT: id dictlit = @{ @17 : {{@17.45[^,]*}}, @"hello" : @"world" };
id dictlit = @{@17 : @17.45, @"hello" : @"world" };
}
diff --git a/clang/test/Sema/x86_64-linux-android.c b/clang/test/Sema/x86_64-linux-android.c
index 252570579f607..75158dc066dc1 100644
--- a/clang/test/Sema/x86_64-linux-android.c
+++ b/clang/test/Sema/x86_64-linux-android.c
@@ -7,4 +7,4 @@ extern int a1_i[__alignof(long double) == 16 ? 1 : -1];
// Verify that long double is 128 bit IEEEquad
long double foo = 1.0E4000L;
-// CHECK: long double foo = 1.00000000000000000000000000000000004E+4000L;
+// CHECK: long double foo = 1.0E+4000L;
diff --git a/llvm/include/llvm/ADT/APFloat.h b/llvm/include/llvm/ADT/APFloat.h
index a9a3dc89a1730..770ad0236ecf5 100644
--- a/llvm/include/llvm/ADT/APFloat.h
+++ b/llvm/include/llvm/ADT/APFloat.h
@@ -1615,6 +1615,20 @@ class APFloat : public APFloatBase {
toString(Str, FormatPrecision, FormatMaxPadding, TruncateZero));
}
+ /// Format the value as toString does and return true if the string parses
+ /// back bitwise equal. Non-finite spellings ("+Inf", "NaN") parse back
+ /// here but may not fit a caller's grammar.
+ LLVM_ABI bool toStringRoundTrip(SmallVectorImpl<char> &Str,
+ unsigned FormatPrecision = 0,
+ unsigned FormatMaxPadding = 3,
+ bool TruncateZero = true) const;
+
+ /// Like toString, but picks the smallest FormatPrecision whose output
+ /// parses back bitwise equal: "3.14", not "3.1400000000000001".
+ LLVM_ABI void toStringShortest(SmallVectorImpl<char> &Str,
+ unsigned FormatMaxPadding = 3,
+ bool TruncateZero = true) const;
+
LLVM_ABI void print(raw_ostream &) const;
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index c3202eea12c28..fb746cbecb6f3 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -1589,8 +1589,8 @@ static void writeAPFloatInternal(raw_ostream &Out, const APFloat &APF) {
// same APFloat value, then we know that it is safe to use it. Otherwise, fall
// back onto the hexadecimal format.
SmallString<128> StrVal;
- APF.toString(StrVal, 6, 0, false);
- if (APFloat(APF.getSemantics(), StrVal) == APF) {
+ if (APF.toStringRoundTrip(StrVal, /*FormatPrecision=*/6,
+ /*FormatMaxPadding=*/0, /*TruncateZero=*/false)) {
Out << StrVal;
return;
}
diff --git a/llvm/lib/Support/APFloat.cpp b/llvm/lib/Support/APFloat.cpp
index 22528d9e93f73..0b7452208fcae 100644
--- a/llvm/lib/Support/APFloat.cpp
+++ b/llvm/lib/Support/APFloat.cpp
@@ -18,6 +18,7 @@
#include "llvm/ADT/FoldingSet.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
@@ -5973,6 +5974,43 @@ APFloat APFloat::getAllOnesValue(const fltSemantics &Semantics) {
return APFloat(Semantics, APInt::getAllOnes(Semantics.sizeInBits));
}
+bool APFloat::toStringRoundTrip(SmallVectorImpl<char> &Str,
+ unsigned FormatPrecision,
+ unsigned FormatMaxPadding,
+ bool TruncateZero) const {
+ SmallString<32> Buf;
+ toString(Buf, FormatPrecision, FormatMaxPadding, TruncateZero);
+ Str.append(Buf.begin(), Buf.end());
+ APFloat Parsed(getSemantics());
+ Expected<opStatus> Status =
+ Parsed.convertFromString(Buf, rmNearestTiesToEven);
+ if (!Status) {
+ consumeError(Status.takeError());
+ return false;
+ }
+ return Parsed.bitwiseIsEqual(*this);
+}
+
+void APFloat::toStringShortest(SmallVectorImpl<char> &Str,
+ unsigned FormatMaxPadding,
+ bool TruncateZero) const {
+ SmallString<32> Best;
+ toString(Best, /*FormatPrecision=*/0, FormatMaxPadding, TruncateZero);
+ if (isFiniteNonZero()) {
+ // Probe below the conservative natural precision (toStringImpl's FIXME).
+ for (unsigned Precision = 1, MaxPrecision = Best.size();
+ Precision < MaxPrecision; ++Precision) {
+ SmallString<32> Candidate;
+ if (toStringRoundTrip(Candidate, Precision, FormatMaxPadding,
+ TruncateZero)) {
+ Best = Candidate;
+ break;
+ }
+ }
+ }
+ Str.append(Best.begin(), Best.end());
+}
+
void APFloat::print(raw_ostream &OS) const {
SmallVector<char, 16> Buffer;
toString(Buffer);
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 0993441abf22d..81445fd9a777d 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -99,9 +99,9 @@ void AnyValue::print(Context &Ctx, raw_ostream &OS) const {
// exponential notation if it is lossless, otherwise output it in
// hexadecimal notation.
SmallString<16> StrVal;
- FloatVal.toString(StrVal, /*FormatPrecision=*/6, /*FormatMaxPadding=*/0,
- /*TruncateZero=*/false);
- if (APFloat(FloatVal.getSemantics(), StrVal).bitwiseIsEqual(FloatVal)) {
+ if (FloatVal.toStringRoundTrip(StrVal, /*FormatPrecision=*/6,
+ /*FormatMaxPadding=*/0,
+ /*TruncateZero=*/false)) {
OS << StrVal;
} else {
StrVal.clear();
diff --git a/llvm/unittests/ADT/APFloatTest.cpp b/llvm/unittests/ADT/APFloatTest.cpp
index ca93953537a09..94072a914da67 100644
--- a/llvm/unittests/ADT/APFloatTest.cpp
+++ b/llvm/unittests/ADT/APFloatTest.cpp
@@ -1629,6 +1629,74 @@ TEST(APFloatTest, toString) {
}
}
+static std::string convertToStringShortest(const APFloat &F, unsigned Pad = 3,
+ bool Tr = true) {
+ llvm::SmallVector<char, 100> Buffer;
+ F.toStringShortest(Buffer, Pad, Tr);
+ return std::string(Buffer.data(), Buffer.size());
+}
+
+TEST(APFloatTest, toStringRoundTrip) {
+ SmallString<32> Str;
+ // 6-digit formatting is lossless for 3.14 but lossy for pi/4; the string
+ // is appended either way (mlir::AsmPrinter's printFloatValue contract).
+ ASSERT_TRUE(APFloat(3.14).toStringRoundTrip(Str, 6, 0, false));
+ ASSERT_EQ("3.140000e+00", Str);
+ Str.clear();
+ ASSERT_FALSE(
+ APFloat(0.78539816339744830961).toStringRoundTrip(Str, 6, 0, false));
+ ASSERT_EQ("7.853980e-01", Str);
+ Str.clear();
+ // FormatPrecision = 0 (natural precision) always round-trips.
+ ASSERT_TRUE(APFloat(0.78539816339744830961).toStringRoundTrip(Str));
+ ASSERT_EQ("0.78539816339744828", Str);
+ Str.clear();
+ // The Inf spelling parses back via convertFromString, although it is not
+ // valid in the IR or MLIR grammars (callers gate non-finite values).
+ ASSERT_TRUE(APFloat::getInf(APFloat::IEEEdouble()).toStringRoundTrip(Str));
+ ASSERT_EQ("+Inf", Str);
+}
+
+TEST(APFloatTest, toStringShortest) {
+ ASSERT_EQ("3.14", convertToStringShortest(APFloat(3.14)));
+ ASSERT_EQ("-3.14", convertToStringShortest(APFloat(-3.14)));
+ ASSERT_EQ("4", convertToStringShortest(APFloat(4.0)));
+ ASSERT_EQ("0.5", convertToStringShortest(APFloat(0.5)));
+ ASSERT_EQ("0.1", convertToStringShortest(APFloat(0.1)));
+ ASSERT_EQ("873.1834", convertToStringShortest(APFloat(873.1834)));
+ ASSERT_EQ("1.0E+10", convertToStringShortest(APFloat(1e10)));
+ // DBL_MAX needs every natural-precision digit.
+ ASSERT_EQ("1.7976931348623157E+308",
+ convertToStringShortest(APFloat(1.7976931348623157E+308)));
+ // The smallest double denormal.
+ ASSERT_EQ("5.0E-324",
+ convertToStringShortest(APFloat(4.9406564584124654e-324)));
+
+ // Non-double semantics.
+ ASSERT_EQ("3.14", convertToStringShortest(APFloat(3.14f)));
+ ASSERT_EQ("0.1", convertToStringShortest(APFloat(0.1f)));
+ ASSERT_EQ("0.1", convertToStringShortest(
+ APFloat(APFloat::x87DoubleExtended(), "0.1")));
+ ASSERT_EQ("0.1",
+ convertToStringShortest(APFloat(APFloat::IEEEquad(), "0.1")));
+ ASSERT_EQ("0.1", convertToStringShortest(
+ APFloat(APFloat::PPCDoubleDouble(), "0.1")));
+
+ // FormatMaxPadding and TruncateZero pass through to toString.
+ ASSERT_EQ("1.0E+1", convertToStringShortest(APFloat(10.0), 0));
+ ASSERT_EQ("1.0e+01", convertToStringShortest(APFloat(10.0), 0, false));
+
+ // Zero and non-finite values format exactly as toString.
+ ASSERT_EQ("0", convertToStringShortest(APFloat(0.0)));
+ ASSERT_EQ("-0", convertToStringShortest(APFloat(-0.0)));
+ ASSERT_EQ("+Inf",
+ convertToStringShortest(APFloat::getInf(APFloat::IEEEdouble())));
+ ASSERT_EQ("-Inf", convertToStringShortest(
+ APFloat::getInf(APFloat::IEEEdouble(), true)));
+ ASSERT_EQ("NaN",
+ convertToStringShortest(APFloat::getNaN(APFloat::IEEEdouble())));
+}
+
TEST(APFloatTest, toInteger) {
bool isExact = false;
APSInt result(5, /*isUnsigned=*/true);
diff --git a/mlir/lib/IR/AsmPrinter.cpp b/mlir/lib/IR/AsmPrinter.cpp
index 47a33a116f92c..274e9976e5733 100644
--- a/mlir/lib/IR/AsmPrinter.cpp
+++ b/mlir/lib/IR/AsmPrinter.cpp
@@ -2265,8 +2265,10 @@ static void printFloatValue(const APFloat &apValue, raw_ostream &os,
bool isNaN = apValue.isNaN();
if (!isInf && !isNaN) {
SmallString<128> strValue;
- apValue.toString(strValue, /*FormatPrecision=*/6, /*FormatMaxPadding=*/0,
- /*TruncateZero=*/false);
+ bool isLossless = apValue.toStringRoundTrip(strValue,
+ /*FormatPrecision=*/6,
+ /*FormatMaxPadding=*/0,
+ /*TruncateZero=*/false);
// Check to make sure that the stringized number is not some string like
// "Inf" or NaN, that atof will accept, but the lexer will not. Check
@@ -2276,9 +2278,9 @@ static void printFloatValue(const APFloat &apValue, raw_ostream &os,
(strValue[1] >= '0' && strValue[1] <= '9'))) &&
"[-+]?[0-9] regex does not match!");
- // Parse back the stringized version and check that the value is equal
- // (i.e., there is no precision loss).
- if (APFloat(apValue.getSemantics(), strValue).bitwiseIsEqual(apValue)) {
+ // The stringized version has no precision loss if it parses back to the
+ // same value.
+ if (isLossless) {
os << strValue;
return;
}
|
|
@vgvassilev for viz, this is the PR in response to compiler-research/CppInterOp#1069 (comment). |
|
I think this is a really useful improvement for clang's ast printer. cc: @AaronBallman |
Fails on the first LLVM that prints shortest round-trip floats (llvm/llvm-project#218471), which changes the output for defaults the constants-as-written policy cannot cover. Co-developed-with-the-help-of: Claude Code (Fable 5, human in the loop)
Fails on the first LLVM that prints shortest round-trip floats (llvm/llvm-project#218471), which changes the output for defaults the constants-as-written policy cannot cover. Co-developed-with-the-help-of: Claude Code (Fable 5, human in the loop)
| SmallString<32> Best; | ||
| toString(Best, /*FormatPrecision=*/0, FormatMaxPadding, TruncateZero); | ||
| if (isFiniteNonZero()) { | ||
| // Probe below the conservative natural precision (toStringImpl's FIXME). |
There was a problem hiding this comment.
This is a terribly inefficient way to do this; see https://dl.acm.org/doi/10.1145/3192366.3192369. But I guess the speed doesn't matter that much for ast-print.
There was a problem hiding this comment.
looking at paper now, maybe worth updating since it's in a core util that could be re-used :)
There was a problem hiding this comment.
I do agree that this is a terribly inefficient implementation; the best algorithm right now I think is Dragonbox: https://github.com/jk-jeon/dragonbox/blob/master/other_files/Dragonbox.pdf
(see also https://onlinelibrary.wiley.com/doi/epdf/10.1002/spe.70056 for empirical testing).
There was a problem hiding this comment.
@jcranmer-intel, nice, that's a more recent ref and mentions ryu, so i'll look at that first and implement accordingly. thanks for pointing this out.
There was a problem hiding this comment.
@efriedma-quic, @jcranmer-intel, updated and now in the PR. The shortest digit count comes from the value's rounding interval (Ryu's formulation) in exact APInt arithmetic, so every IEEEFloat semantics works; the existing toString formats at that count and the round trip is still verified. For the search step I measured both shapes: a Ryu-style binary search over power-of-ten grids runs 38µs/value on random doubles, the Dragonbox anchor 26µs (old probing loop: 160µs; short literals 8.9 → 5.9µs), so the anchor is what's in the patch. Output is unchanged — exhaustive over all small formats plus millions of random single/double patterns, ~22.5M comparisons. Per-format power tables would be faster still but give up the semantics-generic exact arithmetic, and toString remains the only formatter so output cannot drift. The earlier CI red was the AsmWriter migration swapping numeric equality for bitwise on ppc_fp128 (a -0.0 low component); AsmWriter keeps its numeric comparison now.
🐧 Linux x64 Test Results
✅ The build succeeded and all tests passed. |
🪟 Windows x64 Test Results
✅ The build succeeded and all tests passed. |
Fails on the first LLVM that prints shortest round-trip floats (llvm/llvm-project#218471), which changes the output for defaults the constants-as-written policy cannot cover. Co-developed-with-the-help-of: Claude Code (Fable 5, human in the loop)
deb6c3d to
50152d5
Compare
…trip form StmtPrinter printed a FloatingLiteral at the maximum round-trip precision, so 3.14 printed as 3.1400000000000001. Add APFloat::toStringShortest and call it from PrintFloatingLiteral. It computes the shortest digit count from the value's rounding interval (Ryu's formulation with a Dragonbox-style grid anchor) in exact APInt arithmetic, so every IEEEFloat semantics works, then formats with the existing toString at that count and verifies the round trip. PPCDoubleDouble and the largest value of saturating formats keep a plain probe from precision 1. The verify step is a new APFloat::toStringRoundTrip; the MLIR and llubi printers hand-rolled the same bitwise check and now call it, with no output change. The IR AsmWriter keeps its numeric comparison: a ppc_fp128 constant whose low double is -0.0 (any negated literal) parses back with +0.0, and such numerically equal forms print in decimal. Updated three tests that pinned the old maximum-precision output; added an -ast-print test and unit tests for both helpers. Co-developed-with-the-help-of: Claude Code (Fable 5, human in the loop)
50152d5 to
2aa0b89
Compare
Fails on the first LLVM that prints shortest round-trip floats (llvm/llvm-project#218471), which changes the output for defaults the constants-as-written policy cannot cover. Co-developed-with-the-help-of: Claude Code (Fable 5, human in the loop)
Print the default-argument expression with printPretty, the interpreter's ASTContext, and ConstantsAsWritten = true. The policy prints literal leaves from their source text, so defaults keep their spelling (4.0 stays "4.0"). This replaces the AST re-print at maximum float precision, which turned 3.14 into 3.1400000000000001. A canary test fails on the first LLVM that prints shortest round-trip floats (llvm/llvm-project#218471), which changes the output for defaults the policy cannot cover. Content matches compiler-research#1069. Co-developed-with-the-help-of: Claude Code (Fable 5, human in the loop)
StmtPrinter printed a
FloatingLiteralat the maximum round-trip precision, so3.14printed as3.1400000000000001. This patch addsAPFloat::toStringShortestand calls it fromPrintFloatingLiteral. NaN, infinity, and zero keep the old path.toStringShortestcomputes the shortest digit count from the value's rounding interval (Ryu's formulation with a Dragonbox-style grid anchor) in exact APInt arithmetic, so every IEEEFloat semantics works. The existingtoStringthen formats at that count and the round trip is verified. PPCDoubleDouble and the largest value of saturating formats keep a plain probe from precision 1.The verify step is a new
APFloat::toStringRoundTrip: format at a given precision and report whether the string parses back bitwise-equal. The MLIR and llubi printers hand-rolled the same check and now call the helper, with no output change. The IR AsmWriter keeps its numeric parse-back comparison: for PPCDoubleDouble, numerically equal is not bitwise equal (the low component can be -0.0).Three tests pinned the old maximum-precision output and were updated. A new
-ast-printtest covers double/float/x87/quad, denormals, and a full-precision value; unit tests cover both helpers.🤖 Done with the help of Claude Code (Fable 5, human in the loop)