https://github.com/conrade-ctc updated https://github.com/llvm/llvm-project/pull/218471
>From 997312def19e859ffd207d0d6b3766441b70cfa7 Mon Sep 17 00:00:00 2001 From: Emery Conrad <[email protected]> Date: Mon, 24 Aug 2026 16:20:28 -0500 Subject: [PATCH] [clang][APFloat] Print floating-point literals in the shortest round-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 keeps a plain probe from precision 1. Two cases keep the natural-precision form. An integer in fixed notation has exact digits; a shorter form would only switch it to scientific notation (100.0 stays "100", not "1.0E+2"). The largest value of a format with neither infinity nor NaN parses back from any larger string by saturation, which is not a round trip. 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 is unchanged: its numeric comparison hides a ppc_fp128 low-component sign difference (issue #224351) that a bitwise check would send to the hex path. Updated three tests that pinned the old maximum-precision output. The -ast-print test covers hex literals, integers, boundary values, and long double as x87, IEEE quad, and double-double. Unit tests pin corner cases from the Ryu test suite for double and float, the 8-bit and smaller formats, and check every value of every format up to 16 bits against a brute-force reference. Co-developed-with-the-help-of: Claude Code (Fable 5.1, human in the loop) --- clang/docs/ReleaseNotes.md | 4 + clang/lib/AST/StmtPrinter.cpp | 2 +- clang/test/AST/ast-print-float-shortest.cpp | 134 ++++++++++ clang/test/PCH/floating-literal.c | 4 +- clang/test/PCH/objc_literals.m | 6 +- clang/test/Sema/x86_64-linux-android.c | 2 +- llvm/include/llvm/ADT/APFloat.h | 15 ++ llvm/lib/Support/APFloat.cpp | 175 +++++++++++++ llvm/tools/llubi/lib/Value.cpp | 6 +- llvm/unittests/ADT/APFloatTest.cpp | 261 ++++++++++++++++++++ mlir/lib/IR/AsmPrinter.cpp | 11 +- 11 files changed, 605 insertions(+), 15 deletions(-) create mode 100644 clang/test/AST/ast-print-float-shortest.cpp diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 9a69d1888b1bfc..0ba52429092ed1 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -159,6 +159,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 4f184868659f48..5e1d45e0f2ff02 100644 --- a/clang/lib/AST/StmtPrinter.cpp +++ b/clang/lib/AST/StmtPrinter.cpp @@ -1603,7 +1603,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 00000000000000..4cc328bd5df40b --- /dev/null +++ b/clang/test/AST/ast-print-float-shortest.cpp @@ -0,0 +1,134 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -ast-print %s | FileCheck %s --check-prefixes=CHECK,F16,X87,Q128 +// RUN: %clang_cc1 -triple aarch64-linux-gnu -ast-print %s | FileCheck %s --check-prefixes=CHECK,F16,QUAD +// RUN: %clang_cc1 -triple powerpc64-linux-gnu -ast-print %s | FileCheck %s --check-prefixes=CHECK,PPC + +// Floating literals print as the shortest decimal form that round-trips to +// the same value in the literal's type. + +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; + +// A hex float keeps its value, printed in decimal. +double h1 = 0x10.1p0; +// CHECK: double h1 = 16.0625; +double h2 = 0x1p-1074; +// CHECK: double h2 = 5.0E-324; + +// Integers keep their digits; the shortest form would only switch them to +// scientific notation. More than three trailing zeros still do, as before. +double i1 = 10.0; +// CHECK: double i1 = 10.; +double i2 = 100.0; +// CHECK: double i2 = 100.; +double i3 = 1200.0; +// CHECK: double i3 = 1200.; +double i4 = 120000.0; +// CHECK: double i4 = 1.2E+5; +// 2^53 + 1 rounds to 2^53. +double i5 = 9007199254740993.0; +// CHECK: double i5 = 9007199254740992.; +// 1e23 is not exactly representable; 1E23 is still the shortest form. +double i6 = 1e23; +// CHECK: double i6 = 1.0E+23; + +// The smallest normal and the largest denormal double. +double b1 = 2.2250738585072014e-308; +// CHECK: double b1 = 2.2250738585072014E-308; +double b2 = 2.2250738585072009e-308; +// CHECK: double b2 = 2.225073858507201E-308; +// 0.1 + 0.2 needs all 17 digits. +double b3 = 0.30000000000000004; +// CHECK: double b3 = 0.30000000000000004; +double b4 = 0.0001; +// CHECK: double b4 = 1.0E-4; +// A significand that looks like a power of five (Ryu's LooksLikePow5). +double b5 = 5.764607523034235e39; +// CHECK: double b5 = 5.764607523034235E+39; + +float f1 = 3.14f; +// CHECK: float f1 = 3.14F; +float f2 = 0.1f; +// CHECK: float f2 = 0.1F; +float f3 = 0x1.8p3f; +// CHECK: float f3 = 12.F; +float f4 = 0x10.1p0f; +// CHECK: float f4 = 16.0625F; +float f5 = 100.0f; +// CHECK: float f5 = 100.F; +// FLT_MAX and the smallest float denormal. +float f6 = 3.4028235e38f; +// CHECK: float f6 = 3.4028235E+38F; +float f7 = 1.4e-45f; +// CHECK: float f7 = 1.0E-45F; +// 2^24 + 1 rounds to 2^24. +float f8 = 16777217.0f; +// CHECK: float f8 = 16777216.F; +// The upper bound of the rounding interval is a round-trip form when the +// significand is even (Ryu's BoundaryRoundEven). +float f9 = 3.4366717e10f; +// CHECK: float f9 = 3.436672E+10F; + +#ifdef __FLT16_MANT_DIG__ +_Float16 h16_1 = 0.1f16; +// F16: _Float16 h16_1 = 0.1F16; +_Float16 h16_2 = 65504.0f16; +// F16: _Float16 h16_2 = 65504.F16; +// 16.0625 is exact in _Float16, but 16.06 already round-trips. +_Float16 h16_3 = 0x10.1p0f16; +// F16: _Float16 h16_3 = 16.06F16; +// The smallest _Float16 denormal. +_Float16 h16_4 = 5.9604644775390625e-8f16; +// F16: _Float16 h16_4 = 6.0E-8F16; +#endif + +// long double: x87 80-bit on x86_64, IEEE quad on AArch64, IBM double-double +// on PowerPC. +long double ld1 = 0.1L; +// CHECK: long double ld1 = 0.1L; +long double ld2 = 3.14L; +// CHECK: long double ld2 = 3.14L; +long double ld3 = 0x10.1p0L; +// CHECK: long double ld3 = 16.0625L; +long double ld4 = 100.0L; +// CHECK: long double ld4 = 100.L; +// 1 + 2^-52 is exact in every long double format; the digit count follows +// the format's precision. +long double ld5 = 1.0000000000000002220446049250313L; +// X87: long double ld5 = 1.000000000000000222L; +// QUAD: long double ld5 = 1.0000000000000002220446049250313L; +// PPC: long double ld5 = 1.0000000000000002220446049250313L; +// The smallest double denormal is a denormal only in double-double. +long double ld6 = 4.9406564584124654e-324L; +// X87: long double ld6 = 4.9406564584124654E-324L; +// QUAD: long double ld6 = 4.9406564584124654E-324L; +// PPC: long double ld6 = 5.0E-324L; +// 1 + 1e-37 is below every format's precision. +long double ld7 = 1.0000000000000000000000000000000000001L; +// CHECK: long double ld7 = 1.L; + +#ifdef __SIZEOF_FLOAT128__ +__float128 q1 = 0.1q; +// Q128: __float128 q1 = 0.1Q; +__float128 q2 = 0x10.1p0q; +// Q128: __float128 q2 = 16.0625Q; +__float128 q3 = 100.0q; +// Q128: __float128 q3 = 100.Q; +#endif diff --git a/clang/test/PCH/floating-literal.c b/clang/test/PCH/floating-literal.c index b5ff6fe84b88ce..4590b1990b79de 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 16ed6aeb9b9339..ede3358e2fe698 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 252570579f6076..75158dc066dc11 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 9b903fc888b4cf..6655720bee27a6 100644 --- a/llvm/include/llvm/ADT/APFloat.h +++ b/llvm/include/llvm/ADT/APFloat.h @@ -1623,6 +1623,21 @@ 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". An integer + /// in fixed notation keeps its digits: "100", not "1.0E+2". + 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/Support/APFloat.cpp b/llvm/lib/Support/APFloat.cpp index 1435c35efc2ab5..a2848fc46d90d5 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/SmallVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" @@ -6071,6 +6072,180 @@ 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); +} + +/// 5^Exp zero-extended to \p Width bits. Each step multiplies by one 64-bit +/// word, a single linear pass. Square-and-multiply needs full-width APInt +/// products, which are quadratic and measure slower at these widths. +static APInt pow5AsAPInt(unsigned Exp, unsigned Width) { + APInt Result(Width, 1); + for (; Exp >= 27; Exp -= 27) + Result *= UINT64_C(7450580596923828125); // 5^27, the largest 64-bit power. + uint64_t Tail = 1; + while (Exp--) + Tail *= 5; + Result *= Tail; + return Result; +} + +/// Minimum number of significant decimal digits a string needs to parse back +/// (round-to-nearest-even) to exactly \p Val. Val must be finite, non-zero, +/// and use the IEEEFloat layout. +/// +/// A string parses back to Val exactly when its value rounds to Val, so the +/// answer is the fewest significant digits of any decimal in Val's rounding +/// interval. The interval follows Ryu (Adams, PLDI 2018, +/// https://doi.org/10.1145/3192366.3192369); the shortest decimal in it is +/// found as in Dragonbox (Jeon, github.com/jk-jeon/dragonbox, after +/// Giulietti's Schubfach): anchor on the coarsest power-of-ten grid the +/// interval width guarantees to hit, then walk the trailing zeros. Exact +/// APInt arithmetic replaces the per-format power tables, so every IEEEFloat +/// semantics works. +static unsigned shortestDigitCount(const APFloat &Val) { + const fltSemantics &Sem = Val.getSemantics(); + const unsigned Prec = APFloat::semanticsPrecision(Sem); + const int MinExp = APFloat::semanticsMinExponent(Sem); + + // Exact integer significand M and exponent Q with |Val| == M * 2^Q. + const APFloat AbsVal = abs(Val); + const int Q = std::max(ilogb(AbsVal), MinExp) - int(Prec - 1); + APFloat Wide = AbsVal; + if (APFloat::semanticsSizeInBits(Sem) <= 32) { + // Tiny formats cannot hold their own significand as an integer (e.g. + // Float6E2M3FN); go through IEEEdouble, an exact superset of them all. + bool LosesInfo = false; + Wide.convert(APFloat::IEEEdouble(), APFloat::rmNearestTiesToEven, + &LosesInfo); + assert(!LosesInfo && "widening conversion is exact"); + } + bool IsExact = false; + APSInt M(Prec + 2, /*isUnsigned=*/true); + scalbn(Wide, -Q, APFloat::rmNearestTiesToEven) + .convertToInteger(M, APFloat::rmNearestTiesToEven, &IsExact); + assert(IsExact && "significand extraction is exact"); + + // The gap below halves when the significand is a power of two above the + // minimum exponent (Ryu's mmShift). The interval is (Low, High) * 2^E2; + // its bounds parse back to Val only when the significand is even (a + // halfway decimal ties to the even neighbor). + const bool HalfBelow = M.isPowerOf2() && ilogb(AbsVal) > MinExp; + const bool Inclusive = !M[0]; + const int E2 = Q - (HalfBelow ? 2 : 1); + // Width holds every value below: the bounds are under 2^(Prec + 2); scaling + // by 2^E2 adds E2 bits, and by 5^-E2 at most floor(-E2 * log2(5)) + 1 bits + // (2322/1000 is just above log2(5) = 2.32193); the grid search forms + // P10 * 10 < 16 * High, which adds 4 more. + const unsigned ScaleBits = + E2 >= 0 ? unsigned(E2) : unsigned((uint64_t(-E2) * 2322) / 1000) + 1; + const unsigned Width = Prec + 2 + ScaleBits + 4; + APInt MW = M.zext(Width); + APInt Low = HalfBelow ? (MW << 2) - 1 : (MW << 1) - 1; + APInt High = HalfBelow ? (MW << 2) + 2 : (MW << 1) + 1; + + // Scale the bounds to integers by a shared power of ten; every candidate + // keeps its significant digit count. + if (E2 >= 0) { + Low <<= unsigned(E2); + High <<= unsigned(E2); + } else { + const APInt P5 = pow5AsAPInt(unsigned(-E2), Width); + Low *= P5; + High *= P5; + } + + // Anchor grid: the largest T0 with 10^T0 <= the interval width, so + // multiples of 10^T0 cannot all miss. 30102/100000 underestimates + // log10(2), so the estimate never overshoots and trails T0 by at most two. + const APInt IntervalWidth = High - Low; + unsigned T0 = + unsigned((uint64_t(IntervalWidth.getActiveBits() - 1) * 30102) / 100000); + APInt P10 = pow5AsAPInt(T0, Width).shl(T0); + while ((P10 * 10).ule(IntervalWidth)) { + P10 *= 10; + ++T0; + } + + // Candidate multiples of 10^T0 in the interval: CMin..CMax, at most ten of + // them. Open bounds can eat a lone candidate; one grid finer cannot miss. + APInt CMin(Width, 0), CMax(Width, 0); + auto FindCandidates = [&]() { + APInt Quo(Width, 0), Rem(Width, 0); + APInt::udivrem(Low, P10, Quo, Rem); + CMin = (Inclusive && Rem.isZero()) ? Quo : Quo + 1; + APInt::udivrem(High, P10, Quo, Rem); + CMax = (!Inclusive && Rem.isZero()) ? Quo - 1 : Quo; + }; + FindCandidates(); + while (CMin.ugt(CMax)) { + P10 = P10.udiv(10); + FindCandidates(); + } + + // Step to coarser grids while the range still holds a multiple of the next + // power of ten. The candidates are small, so these divisions are cheap. + while (true) { + const APInt CMax10 = CMax.udiv(10); + if ((CMax10 * 10).ult(CMin)) + break; + CMin = (CMin + 9).udiv(10); + CMax = CMax10; + } + unsigned Digits = 0; + for (; !CMin.isZero(); ++Digits) + CMin = CMin.udiv(10); + return Digits; +} + +void APFloat::toStringShortest(SmallVectorImpl<char> &Str, + unsigned FormatMaxPadding, + bool TruncateZero) const { + SmallString<32> Best; + toString(Best, /*FormatPrecision=*/0, FormatMaxPadding, TruncateZero); + // An integer printed in fixed notation has exact digits; a shorter form + // only trades them for scientific notation ("1.0E+2" for 100). Keep it. + const bool KeepIntegerDigits = + StringRef(Best).find_first_of(".Ee") == StringRef::npos; + // A format with neither infinity nor NaN parses every larger string as its + // largest value. That is saturation, not a round trip; keep the natural + // form ("7.5", not "8", for the largest Float6E2M3FN). + const bool Saturates = !semanticsHasInf(getSemantics()) && + !semanticsHasNaN(getSemantics()) && + abs(*this).bitwiseIsEqual(getLargest(getSemantics())); + if (isFiniteNonZero() && !KeepIntegerDigits && !Saturates) { + // Probe below the conservative natural precision (toStringImpl's FIXME). + // No precision below shortestDigitCount can parse back equal, so start + // there; the result matches probing from 1. DoubleAPFloat layouts keep + // the full probe. + unsigned Precision = + usesLayout<IEEEFloat>(getSemantics()) ? shortestDigitCount(*this) : 1; + for (unsigned 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 0993441abf22d2..81445fd9a777da 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 35393ef8ee20bf..06d23bed4edaa9 100644 --- a/llvm/unittests/ADT/APFloatTest.cpp +++ b/llvm/unittests/ADT/APFloatTest.cpp @@ -1842,6 +1842,267 @@ 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 digits: lossless for 3.14, lossy for pi/4; the string appends either way. + 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(); + // "+Inf" parses back even though the IR and MLIR grammars reject it. + 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))); + // A hex literal prints in decimal. + ASSERT_EQ("16.0625", convertToStringShortest(APFloat(0x10.1p0))); + // 0.1 + 0.2 and DBL_MAX need every natural-precision digit. + ASSERT_EQ("0.30000000000000004", + convertToStringShortest(APFloat(0.30000000000000004))); + ASSERT_EQ("1.7976931348623157E+308", + convertToStringShortest(APFloat(1.7976931348623157E+308))); + // The smallest denormal, the smallest normal, and the largest denormal. + ASSERT_EQ("5.0E-324", + convertToStringShortest(APFloat(4.9406564584124654e-324))); + ASSERT_EQ("2.2250738585072014E-308", + convertToStringShortest(APFloat(2.2250738585072014e-308))); + ASSERT_EQ("2.225073858507201E-308", + convertToStringShortest(APFloat(2.2250738585072009e-308))); + // 1e23 is not exactly representable; 1E23 is still the shortest form. + ASSERT_EQ("1.0E+23", convertToStringShortest(APFloat(1e23))); + // Significands that look like powers of five (Ryu's LooksLikePow5). + ASSERT_EQ("5.764607523034235E+39", + convertToStringShortest( + APFloat(APFloat::IEEEdouble(), APInt(64, 0x4830F0CF064DD592)))); + ASSERT_EQ("1.152921504606847E+40", + convertToStringShortest( + APFloat(APFloat::IEEEdouble(), APInt(64, 0x4840F0CF064DD592)))); + // Neighbors around 2^32 differ in the tenth digit. + ASSERT_EQ("4.294967295", convertToStringShortest(APFloat(4.294967295))); + ASSERT_EQ("4.294967296", convertToStringShortest(APFloat(4.294967296))); + ASSERT_EQ("4.294967297", convertToStringShortest(APFloat(4.294967297))); + // A power of two has a shorter gap below than above. + ASSERT_EQ("1.7800590868057611E-307", + convertToStringShortest( + APFloat(APFloat::IEEEdouble(), APInt(64, uint64_t(4) << 52)))); + ASSERT_EQ("2.900835519859558E-216", + convertToStringShortest(APFloat(APFloat::IEEEdouble(), + APInt(64, uint64_t(307) << 52)))); + + // An integer in fixed notation keeps its digits; the shortest form would + // only trade them for scientific notation. More than FormatMaxPadding + // trailing zeros still switch to scientific notation, as in toString. + ASSERT_EQ("10", convertToStringShortest(APFloat(10.0))); + ASSERT_EQ("100", convertToStringShortest(APFloat(100.0))); + ASSERT_EQ("1200", convertToStringShortest(APFloat(1200.0))); + ASSERT_EQ("1.2E+5", convertToStringShortest(APFloat(120000.0))); + // 2^53 + 1 rounds to 2^53. + ASSERT_EQ("9007199254740992", + convertToStringShortest(APFloat(9007199254740993.0))); + // 17 digits fit the natural precision; 18 do not. + ASSERT_EQ("18014398509481984", + convertToStringShortest(APFloat(18014398509481984.0))); + ASSERT_EQ("1.2345678901234568E+17", + convertToStringShortest(APFloat(123456789012345680.0))); + + // Single precision. + ASSERT_EQ("3.14", convertToStringShortest(APFloat(3.14f))); + ASSERT_EQ("0.1", convertToStringShortest(APFloat(0.1f))); + ASSERT_EQ("16.0625", convertToStringShortest(APFloat(0x10.1p0f))); + ASSERT_EQ("100", convertToStringShortest(APFloat(100.0f))); + ASSERT_EQ("3.4028235E+38", convertToStringShortest(APFloat(3.4028235e38f))); + ASSERT_EQ("1.0E-45", convertToStringShortest(APFloat(1.4e-45f))); + ASSERT_EQ("1.1754944E-38", convertToStringShortest(APFloat(1.1754944e-38f))); + // 2^24 + 1 rounds to 2^24. + ASSERT_EQ("16777216", convertToStringShortest(APFloat(16777217.0f))); + // The upper bound of the rounding interval parses back when the + // significand is even (Ryu's BoundaryRoundEven). + ASSERT_EQ("3.436672E+10", convertToStringShortest(APFloat(3.4366717e10f))); + ASSERT_EQ("1.5846086E+29", convertToStringShortest(APFloat(1.5846085e29f))); + ASSERT_EQ("6.7108864E+17", + convertToStringShortest( + APFloat(APFloat::IEEEsingle(), APInt(32, 0x5D1502F9)))); + // FIXME: 9.0E+9 also parses back, but toString truncates the guard digit + // before it rounds and prints 8.0E+9 at precision 1. + ASSERT_EQ("8.999999E+9", convertToStringShortest(APFloat(8.999999e9f))); + + // Half precision and bfloat. + ASSERT_EQ("0.1", + convertToStringShortest(APFloat(APFloat::IEEEhalf(), "0.1"))); + ASSERT_EQ("65504", + convertToStringShortest(APFloat(APFloat::IEEEhalf(), "65504"))); + // 16.0625 is exact, but 16.06 already parses back. + ASSERT_EQ("16.06", + convertToStringShortest(APFloat(APFloat::IEEEhalf(), "16.0625"))); + ASSERT_EQ("6.0E-8", convertToStringShortest(APFloat( + APFloat::IEEEhalf(), "5.9604644775390625e-8"))); + ASSERT_EQ("0.1", convertToStringShortest(APFloat(APFloat::BFloat(), "0.1"))); + ASSERT_EQ("3.39E+38", convertToStringShortest(APFloat( + APFloat::BFloat(), "3.3895313892515355e38"))); + // 99840 has more digits than bfloat's natural precision, so the integer + // rule does not apply and 1E5 is the shortest form. + ASSERT_EQ("1.0E+5", + convertToStringShortest(APFloat(APFloat::BFloat(), "99840"))); + + // 8-bit and smaller formats. + ASSERT_EQ("448", + convertToStringShortest(APFloat(APFloat::Float8E4M3FN(), "448"))); + ASSERT_EQ("0.002", convertToStringShortest( + APFloat(APFloat::Float8E4M3FN(), "0.001953125"))); + // The largest Float8E5M2 has five digits; 6E4 rounds to it without + // overflow. + ASSERT_EQ("6.0E+4", + convertToStringShortest(APFloat(APFloat::Float8E5M2(), "57344"))); + ASSERT_EQ("28", + convertToStringShortest(APFloat(APFloat::Float6E3M2FN(), "28"))); + // "8" parses back to the largest Float6E2M3FN by saturation, not rounding. + ASSERT_EQ("7.5", + convertToStringShortest(APFloat(APFloat::Float6E2M3FN(), "7.5"))); + ASSERT_EQ("6", + convertToStringShortest(APFloat(APFloat::Float4E2M1FN(), "6"))); + ASSERT_EQ("1.5", + convertToStringShortest(APFloat(APFloat::Float4E2M1FN(), "1.5"))); + ASSERT_EQ("0.5", + convertToStringShortest(APFloat(APFloat::Float8E8M0FNU(), "0.5"))); + ASSERT_EQ("2.0E+38", convertToStringShortest(APFloat( + APFloat::Float8E8M0FNU(), "1.7014118346046923e38"))); + ASSERT_EQ("6.0E-39", convertToStringShortest(APFloat( + APFloat::Float8E8M0FNU(), "5.877471754111438e-39"))); + ASSERT_EQ("3.14", + convertToStringShortest(APFloat(APFloat::FloatTF32(), "3.14"))); + + // x87 extended, quad, and double-double. 1 + 2^-52 is exact in all three; + // the digit count follows the format's precision. + ASSERT_EQ("0.1", convertToStringShortest( + APFloat(APFloat::x87DoubleExtended(), "0.1"))); + ASSERT_EQ("1.0E+4000", convertToStringShortest( + APFloat(APFloat::x87DoubleExtended(), "1e4000"))); + ASSERT_EQ("1.189731495357231765E+4932", + convertToStringShortest(APFloat(APFloat::x87DoubleExtended(), + "1.18973149535723176502e+4932"))); + ASSERT_EQ("4.0E-4951", + convertToStringShortest(APFloat(APFloat::x87DoubleExtended(), + "3.6451995318824746025e-4951"))); + ASSERT_EQ("1.000000000000000222", convertToStringShortest(APFloat( + APFloat::x87DoubleExtended(), + "1.0000000000000002220446049250313"))); + ASSERT_EQ("0.1", + convertToStringShortest(APFloat(APFloat::IEEEquad(), "0.1"))); + ASSERT_EQ( + "1.189731495357231765085759326628007E+4932", + convertToStringShortest(APFloat( + APFloat::IEEEquad(), "1.18973149535723176508575932662800702e+4932"))); + ASSERT_EQ("6.0E-4966", convertToStringShortest(APFloat( + APFloat::IEEEquad(), + "6.475175119438025110924438958227646552e-4966"))); + ASSERT_EQ("1.0000000000000002220446049250313", + convertToStringShortest(APFloat( + APFloat::IEEEquad(), "1.0000000000000002220446049250313"))); + ASSERT_EQ("0.1", convertToStringShortest( + APFloat(APFloat::PPCDoubleDouble(), "0.1"))); + ASSERT_EQ("3.14", convertToStringShortest( + APFloat(APFloat::PPCDoubleDouble(), "3.14"))); + ASSERT_EQ("100", convertToStringShortest( + APFloat(APFloat::PPCDoubleDouble(), "100"))); + ASSERT_EQ("1.7976931348623157E+308", + convertToStringShortest( + APFloat(APFloat::PPCDoubleDouble(), "1.7976931348623157e308"))); + ASSERT_EQ("5.0E-324", + convertToStringShortest(APFloat(APFloat::PPCDoubleDouble(), + "4.9406564584124654e-324"))); + ASSERT_EQ( + "1.0000000000000002220446049250313", + convertToStringShortest(APFloat(APFloat::PPCDoubleDouble(), + "1.0000000000000002220446049250313"))); + // A negated double-double has a -0.0 low component; no decimal string + // parses back to it bitwise, so the natural-precision form is returned. + { + APFloat NegOne(APFloat::PPCDoubleDouble(), "1.0"); + NegOne.changeSign(); + ASSERT_EQ("-1", convertToStringShortest(NegOne)); + } + + // 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()))); +} + +// Every finite value of the small formats: the string parses back bitwise +// equal, and it is the first FormatPrecision that does so. An integer in +// fixed notation and the largest value of a format that saturates keep the +// natural-precision form. +TEST(APFloatTest, toStringShortestExhaustive) { + const fltSemantics *Semantics[] = { + &APFloat::IEEEhalf(), &APFloat::BFloat(), + &APFloat::Float8E5M2(), &APFloat::Float8E5M2FNUZ(), + &APFloat::Float8E4M3(), &APFloat::Float8E4M3FN(), + &APFloat::Float8E4M3FNUZ(), &APFloat::Float8E4M3B11FNUZ(), + &APFloat::Float8E3M4(), &APFloat::Float8E8M0FNU(), + &APFloat::Float6E3M2FN(), &APFloat::Float6E2M3FN(), + &APFloat::Float4E2M1FN(), &APFloat::Float8E5M3FNU(), + }; + for (const fltSemantics *Sem : Semantics) { + unsigned Bits = APFloat::getSizeInBits(*Sem); + for (uint64_t I = 0, E = uint64_t(1) << Bits; I != E; ++I) { + APFloat F(*Sem, APInt(Bits, I)); + if (!F.isFiniteNonZero()) + continue; + std::string Shortest = convertToStringShortest(F); + EXPECT_TRUE(APFloat(*Sem, Shortest).bitwiseIsEqual(F)) + << Shortest << " bits 0x" << utohexstr(I); + SmallString<32> Str; + F.toString(Str); + bool Saturates = !APFloat::semanticsHasInf(*Sem) && + !APFloat::semanticsHasNaN(*Sem) && + abs(F).bitwiseIsEqual(APFloat::getLargest(*Sem)); + if (Saturates || StringRef(Str).find_first_of(".Ee") == StringRef::npos) { + EXPECT_EQ(Str, Shortest) << "bits 0x" << utohexstr(I); + continue; + } + for (unsigned Precision = 1; Precision < 16; ++Precision) { + Str.clear(); + F.toString(Str, Precision); + if (APFloat(*Sem, Str).bitwiseIsEqual(F)) { + EXPECT_EQ(Str, Shortest) << "bits 0x" << utohexstr(I); + break; + } + } + } + } +} + 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 a2c4c81a3ecad8..546e0a0531f2de 100644 --- a/mlir/lib/IR/AsmPrinter.cpp +++ b/mlir/lib/IR/AsmPrinter.cpp @@ -2298,8 +2298,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 @@ -2309,9 +2311,8 @@ 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)) { + // Lossless if it parses back to the same value. + if (isLossless) { os << strValue; return; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
