[llvm] [APFloat] Add APFloat::format (PR #99088)

via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 16 12:34:46 PDT 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-support

Author: None (Ariel-Burton)

<details>
<summary>Changes</summary>

This PR adds the format method to the APFloat family. This is intended to ease formatting of an APFloat to a string without having to convert the APFloat to a host-format floating point value.
This is useful when the target type cannot be losslessly converted to a host type.

---

Patch is 22.42 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/99088.diff


3 Files Affected:

- (modified) llvm/include/llvm/ADT/APFloat.h (+25) 
- (modified) llvm/lib/Support/APFloat.cpp (+361) 
- (modified) llvm/unittests/ADT/APFloatTest.cpp (+156) 


``````````diff
diff --git a/llvm/include/llvm/ADT/APFloat.h b/llvm/include/llvm/ADT/APFloat.h
index db2fa480655c6..f2ed3a5d22b82 100644
--- a/llvm/include/llvm/ADT/APFloat.h
+++ b/llvm/include/llvm/ADT/APFloat.h
@@ -15,10 +15,12 @@
 #ifndef LLVM_ADT_APFLOAT_H
 #define LLVM_ADT_APFLOAT_H
 
+#include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/APInt.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/FloatingPointMode.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/NativeFormatting.h"
 #include "llvm/Support/float128.h"
 #include <memory>
 
@@ -557,6 +559,14 @@ class IEEEFloat final : public APFloatBase {
 
   cmpResult compareAbsoluteValue(const IEEEFloat &) const;
 
+  /// returns the exponent in the format's radix
+  ExponentType getExponent() const;
+
+  /// returns the significand
+  APInt getSignificand() const;
+
+  /// decompose to an integer and exponent to radix 2
+  bool decomposeToIntegerAndPowerOf2(APInt &, int &) const;
 private:
   /// \name Simple Queries
   /// @{
@@ -784,6 +794,8 @@ class DoubleAPFloat final : public APFloatBase {
 
   bool getExactInverse(APFloat *inv) const;
 
+  bool decomposeToIntegerAndPowerOf2(APInt &, int &) const;
+
   LLVM_READONLY
   int getExactLog2() const;
   LLVM_READONLY
@@ -1381,6 +1393,19 @@ class APFloat : public APFloatBase {
         toString(Str, FormatPrecision, FormatMaxPadding, TruncateZero));
   }
 
+  /// Decomposes the value to an integer and an exponent such that
+  /// value = I * 2^exp
+  /// returns true on success, false otherwise (e.g., NaN, Infinity)
+  /// similar to the standard modf, but exponent does not
+  /// have to have the same sign as the value.
+  bool decomposeToIntegerAndPowerOf2 (APInt &I, int &exp) const {
+    APFLOAT_DISPATCH_ON_SEMANTICS( decomposeToIntegerAndPowerOf2(I, exp));
+  }
+
+  SmallVectorImpl<char> &format(SmallVectorImpl<char> &strout,
+              llvm::FloatStyle style = llvm::FloatStyle::Exponent,
+              std::optional<size_t> precision = std::nullopt);
+
   void print(raw_ostream &) const;
   void dump() const;
 
diff --git a/llvm/lib/Support/APFloat.cpp b/llvm/lib/Support/APFloat.cpp
index 3664de71d06df..cf9793d6e3f76 100644
--- a/llvm/lib/Support/APFloat.cpp
+++ b/llvm/lib/Support/APFloat.cpp
@@ -4596,6 +4596,39 @@ void IEEEFloat::makeZero(bool Negative) {
   APInt::tcSet(significandParts(), 0, partCount());
 }
 
+IEEEFloat::ExponentType IEEEFloat::getExponent() const {
+  return exponent;
+}
+
+APInt IEEEFloat::getSignificand() const {
+  APInt result(semantics->precision, ArrayRef<integerPart>(significandParts(), partCount()));
+
+  return result;
+}
+
+bool IEEEFloat::decomposeToIntegerAndPowerOf2(APInt &I, int &exp) const {
+  if (isNaN() || isInfinity())
+    return false;
+
+  if (isZero()) {
+    I = APInt(semantics->precision, 0);
+    exp = 0;
+  } else {
+    // the significant bits are the significand
+    I = getSignificand();
+    // now set up the exp.
+    // In IEEEFloat the radix point is immediately to the right
+    // of the left most bit, i.e., the significand is s.ssssss... .
+    exp = exponent - (semantics->precision - 1);
+
+    // get rid of any trailing zeros
+    int nTrailingZeros = I.countr_zero();
+    exp += nTrailingZeros;
+    I.lshrInPlace(nTrailingZeros);
+  }
+  return true;
+}
+
 void IEEEFloat::makeQuiet() {
   assert(isNaN());
   if (semantics->nonFiniteBehavior != fltNonfiniteBehavior::NanOnly)
@@ -5217,6 +5250,50 @@ bool DoubleAPFloat::getExactInverse(APFloat *inv) const {
   return Ret;
 }
 
+bool DoubleAPFloat::decomposeToIntegerAndPowerOf2(APInt &I, int &exp) const {
+  APInt a, b;
+  int aExp, bExp;
+  bool aOK = Floats[0].decomposeToIntegerAndPowerOf2(a, aExp);
+  bool bOK = Floats[1].decomposeToIntegerAndPowerOf2(b, bExp);
+
+  if (aOK && bOK) {
+    if (aExp > bExp) {
+      int deltaExp = aExp - bExp;
+      // shift a left to reduce its exponent to be the same as b
+      a = a.zext(a.getBitWidth() + deltaExp);
+      a <<= deltaExp;
+      exp = bExp;
+    } else if (bExp > aExp) {
+      int deltaExp = bExp - aExp;
+      // shift b left to reduce its exponent to be the same as a
+      b = b.zext(b.getBitWidth() + deltaExp);
+      b <<= deltaExp;
+      exp = aExp;
+    } else {
+      exp = aExp;
+    }
+    // now do the addition
+
+    // need to extend the operands to be the same length,
+    // and allow a bit for a possible carry (hence the +1).
+    // Note: the +1 means that the new length is greater
+    //       than the lengths of both a and b.
+    //       That means that we can use zext safely.
+    int bw = 1 + std::max(a.getBitWidth(), b.getBitWidth());
+    a = a.zext(bw);
+    b = b.zext(bw);
+    I = a + b;
+
+    // now check for trailing zeros
+    int nTrailingZeros = I.countr_zero();
+    exp += nTrailingZeros;
+    I.lshrInPlace(nTrailingZeros);
+
+    return true;
+  }
+  return false;
+}
+
 int DoubleAPFloat::getExactLog2() const {
   // TODO: Implement me
   return INT_MIN;
@@ -5323,6 +5400,290 @@ APFloat APFloat::getAllOnesValue(const fltSemantics &Semantics) {
   return APFloat(Semantics, APInt::getAllOnes(Semantics.sizeInBits));
 }
 
+SmallVectorImpl<char> &APFloat::format(SmallVectorImpl<char> &strout,
+                                     llvm::FloatStyle style,
+                                     std::optional<size_t> precision_in) {
+  size_t precision = precision_in.value_or(getDefaultPrecision(style));
+
+  // everything that follows assumes that precision >= 0
+  assert(precision >= 0);
+
+  // deal with the special cases
+  if (isNaN()) {
+    detail::append(strout, "nan");
+    return strout;
+  } else if (isInfinity()) {
+    if (isNegative())
+      detail::append(strout, "-INF");
+    else
+      detail::append(strout, "INF");
+    return strout;
+  } else if (isZero()) {
+    if (isNegative())
+      strout.push_back('-');
+    strout.push_back('0');
+    if (precision > 0) {
+      strout.push_back('.');
+      for (size_t i = 0; i < precision; ++i)
+        strout.push_back('0');
+    }  
+    if (style == FloatStyle::Exponent)
+      detail::append(strout, "e+00");
+    else if (style == FloatStyle::ExponentUpper)
+      detail::append(strout, "E+00");
+    else if (style == FloatStyle::Percent)
+      strout.push_back('%');
+    return strout;
+  }
+
+  // check we've dealt with all the special cases
+  assert(!isNaN() && !isInfinity() && !isZero());
+
+  // get as an integer and radix 2 exponent
+  APInt I;
+  int E;
+  decomposeToIntegerAndPowerOf2(I, E);
+
+  // convert from base 2 to base 10
+  if (0 == E) {
+    // then nothing to do --- s is an integer
+  } else if (E > 0) {
+    // shift left and reduce e to 0
+    int numLeadingZeros = I.countl_zero();
+    if (E > numLeadingZeros)
+      I = I.zext(I.getBitWidth() + E - numLeadingZeros);
+    I <<= E;
+    E = 0;
+  } else {
+    // we want to convert a negative base 2 exponent
+    // to a negative base 10 exponent:
+    //   I * 2^-E   = I * 2^-E * 5^E * 5^-E
+    //              = I * 5^E * 2^-E * 5^-E
+    //              = (I * 5^E) * (2*5)^-E
+    //              = (I * 5^E) * 10^-E
+    // that is, we need to multiply I by 5^-exp and treat
+    // the exponent as being base 10
+
+    // 5^13 = 5^(8+4+1)
+    //
+    // Now work our how many bits we need for the product I * 5^E
+    // We need log2(I * 5^E)  = log2(s) + log2(5^E)
+    //                        = log2(s) + e * log2(5)
+    // log2(5) ~ 2.321928096 < 2.322033898 ~ 137/59
+    // log2(I) <= I.getBitWidth()
+
+    I = I.zext(I.getBitWidth() + (137 * -E + 136) / 59);
+    APInt power5(I.getBitWidth(), 5);
+    int exp = -E;
+    assert(exp > 0);
+    for (;;) {
+      if (exp & 1)
+        I *= power5;
+      exp >>= 1;
+      if (0 == exp)
+        break;
+      power5 *= power5;
+    }
+  }
+
+  // at this point s is an intger, and exp is either 0, or is negative
+  assert(E <= 0);
+
+  // convert s to a string
+  SmallVector<char> s;
+  I.toString(s, 10, false);
+
+  // The decimal point is at position -E (recall E is <= 0)
+  // relative to the END of the string.  If E == 0, the decimal
+  // point is to the right of the last position.
+  // Thus, if decimalPoint == s.size() + E, the decimal point is
+  // immediately to the left of s[decimalPoint].
+  // If decimalPoint is 0, the decimal point is immediately
+  // before the start of s.  If it is negative, then there are
+  // implicit zeros to the left of s.
+  int decimalPoint = s.size() + E;
+  E = 0;
+
+  if (style == FloatStyle::Exponent || style == FloatStyle::ExponentUpper) {
+    // this corresponds to printf("%e"), or [-]d[.dd..dd]e(+|-)dd
+    // We need one digit to the left of the decimal point.
+    // In other words, we need to make decimalPoint 1,
+    // and adjust E.
+    E = decimalPoint - 1;
+    decimalPoint = 1;
+
+    // now need to deal with the precision.
+    if (precision < (s.size() - 1) && (s[precision + 1] >= '5')) {
+      // then need to round.  What we do is extract the
+      // the first (precision + 1) digits from s (precision
+      // digits after the decimal point, and one for the
+      // the single dight to the left of the decimal point).
+      // We know that because the next position is at least 5
+      // that the left most place of the digits retain would
+      // be retained.
+      // What we need to do is increment what's left.
+
+      int nDigits = precision + 1;
+      StringRef significantDigits(s.data(), nDigits);
+      // temporarily move the decimal place to the end
+      // We do this to handle the case when the single
+      // digit to the left of the decimal point is 9,
+      // and it rolls over to 10.
+      E -= (nDigits - 1);
+
+      APInt temp(I.getBitWidth(), significantDigits, 10);
+      temp += 1;
+      s.clear();
+      temp.toString(s, 10, false);
+
+      // now move decimal point back
+      E += (s.size() - 1);
+    }
+    if(isNegative())
+      strout.push_back('-');
+    strout.push_back(s[0]);
+    if (precision > 0) {
+      strout.push_back('.');
+      if (precision > (s.size() - 1)) {
+        // then need to print all the digits we have,
+        // and possibly some trailing zeros
+        detail::append(strout, StringRef(s.data() + 1, s.size() - 1));
+        for (auto i = s.size() - 1; i < precision; i++)
+          strout.push_back('0');
+      } else {
+        // need only some of the digits
+        StringRef restOfDigits(s.data() + 1, precision);
+        detail::append(strout, restOfDigits);
+      }
+    }
+    if (style == FloatStyle::Exponent)
+      strout.push_back('e');
+    else
+      strout.push_back('E');
+    if (E < 0) {
+      strout.push_back('-');
+      E = -E;
+    } else {
+      strout.push_back('+');
+    }
+    // need a minimum of two digits for the exponent
+    if (E < 10) {
+      strout.push_back('0');
+      strout.push_back('0' + E);
+    } else {
+      s.clear();
+      while(E) {
+        char c = '0' + E % 10;
+        strout.push_back(c);
+        E /= 10;
+      }
+      std::reverse(s.begin(), s.end());
+      detail::append(strout, StringRef(s.data(), s.size()));
+    }
+    return strout;
+  }
+
+  // output to be formatted along the lines of %f
+  if (style == FloatStyle::Percent)
+    decimalPoint += 2;    // multiply by 100
+
+  int decidingDigit = decimalPoint + precision;
+
+  if (decidingDigit < 0) {
+    // theh value is zero.  The deciding digit is to the left
+    // of the digits in s.  This is in the area of zeros, and
+    // the contents of s can't affect the value.
+  } else if (decidingDigit >= (int) s.size()) {
+    // deciding is beyond the end of s
+  } else if (decidingDigit == 0) {
+    // this is a tricky case, because if the digit at position 0
+    // is >= 5, then the digit to the left, which is an implicit
+    // 0 that is not represented, needs to be incremented.
+    if (s[0] >= '5') {
+      // then need to carry.
+      // What we do is clear s, and insert a '1' at the start of s
+      // (note it must be 1), and increment decimalPoint.
+      // We don't need the rest of the digits because they
+      // don't contribute to the value to be displayed.
+      s.clear();
+      s.push_back('1');
+      decimalPoint++;
+    }
+  } else if (s[decidingDigit] >= '5') {
+    StringRef significantDigits(s.data(), decidingDigit);
+    int distanceBetweenDecimalPointAndDecidingDigit = decidingDigit - decimalPoint;
+    APInt temp (I.getBitWidth(), significantDigits, 10);
+    temp += 1;
+    s.clear();
+    temp.toString(s, 10, false);
+    // readjust decimalPoint in case the addition had a carry out
+    decimalPoint = (int) s.size() - distanceBetweenDecimalPointAndDecidingDigit;
+  }
+
+  if (isNegative())
+    strout.push_back('-');
+
+  // emit the integer part.  There will always be an integet:
+  // if there is a decimal point, then at least one digit
+  // must appear to its left.  If there is no decimal, then
+  // the value is displayed as an integer.
+  if (decimalPoint < 1) {
+    strout.push_back('0');
+  } else {
+    // we need to emit decimalPoint digits
+    int fromS = std::min(decimalPoint, (int) s.size());
+    int i;
+
+    for (i = 0; i < fromS; i++)
+      strout.push_back(s[i]);
+    for (; i < decimalPoint; i++)
+      strout.push_back('0');
+  }
+
+  if (precision > 0 ) {
+    // need to emit precision digits
+    // We need to emit what's to the right of the decimal point.
+    int i;
+    strout.push_back('.');
+    if (decimalPoint < 0) {
+      int numLeadingZeros = std::min((int) precision, -decimalPoint);
+      for (i = 0; i < numLeadingZeros; i++)
+        strout.push_back('0');
+      // update how many digits we have left to emit
+      precision -= numLeadingZeros;
+      // update where we need to emit them from
+      decimalPoint += numLeadingZeros;
+    }
+
+    if ( precision > 0) {
+      // decimalPoint must be >= 0.
+      // If it was < 0 befoew we emitted the integer to the left of the decimal ppint,
+      // then it would have been incremented by numLeadingZeros in the previous block.
+      // It would have been incremented by the smaller of |decimalPoint| and precision.
+      // if |decimalPoint| were smaller, then decimalPoint will now be 0.
+      // If precision were smaller, then precision would have been decremented to 0,
+      // so we wouldn't be in this block.
+      // Hencem if we are in this block, then decimalPoint must be >= 0.
+      assert(decimalPoint >= 0);
+      if (decimalPoint < (int) s.size()) {
+        // we need to emit digits from s
+        int fromS = std::min(precision, s.size() - decimalPoint);
+
+        for (i = 0; i < fromS; i++)
+          strout.push_back(s[decimalPoint + i]);
+        precision -= fromS;
+      }
+      for ( i = 0; i < (int) precision; i++ )
+        strout.push_back('0');
+    }
+  }
+
+  if (style == FloatStyle::Percent)
+    strout.push_back('%');
+
+  return strout;
+}
+
 void APFloat::print(raw_ostream &OS) const {
   SmallVector<char, 16> Buffer;
   toString(Buffer);
diff --git a/llvm/unittests/ADT/APFloatTest.cpp b/llvm/unittests/ADT/APFloatTest.cpp
index 86a25f4394e19..a718c49dc662e 100644
--- a/llvm/unittests/ADT/APFloatTest.cpp
+++ b/llvm/unittests/ADT/APFloatTest.cpp
@@ -1378,6 +1378,162 @@ TEST(APFloatTest, toString) {
   }
 }
 
+TEST(APFloatTest, format) {
+  auto doit_precision = [](const fltSemantics &S, const char *value, llvm::FloatStyle style, int precision, const char *expected) {
+
+    SmallString<16>  s;
+    APFloat apf(S, value);
+    apf.format(s, style, precision);
+    EXPECT_STREQ(expected, s.c_str());
+  };
+  auto doit = [](const fltSemantics &S, const char *value, llvm::FloatStyle style, const char *expected) {
+
+    SmallString<16>  s;
+    APFloat apf(S, value);
+    apf.format(s, style);
+    EXPECT_STREQ(expected, s.c_str());
+  };
+
+
+  // default precision for Exponent and ExponentUpper is 6
+  // and 2 for Fixed and Psercent
+  // All float formats should be able to handle small vlues.
+  // The smaller formats, like float8, are more restricted.
+  for (unsigned I = 0; I != APFloat::S_MaxSemantics + 1; ++I) {
+    auto SemEnum = static_cast<APFloat::Semantics>(I);
+    const auto &S = APFloat::EnumToSemantics(SemEnum);
+    auto Precision = APFloat::semanticsPrecision(S);
+
+    // check 0
+    doit(S, "0.0", llvm::FloatStyle::Exponent, "0.000000e+00");
+    doit(S, "0.0", llvm::FloatStyle::ExponentUpper, "0.000000E+00");
+    doit(S, "0.0", llvm::FloatStyle::Fixed, "0.00");
+    doit(S, "0.0", llvm::FloatStyle::Percent, "0.00%");
+
+    // check that Exponent shifts left
+    doit(S, "0.5", llvm::FloatStyle::Exponent, "5.000000e-01");
+    doit(S, "0.5", llvm::FloatStyle::ExponentUpper, "5.000000E-01");
+    doit(S, "0.5", llvm::FloatStyle::Fixed, "0.50");
+    doit(S, "0.5", llvm::FloatStyle::Percent, "50.00%");
+
+    // check 1
+    doit(S, "1.0", llvm::FloatStyle::Exponent, "1.000000e+00");
+    doit(S, "1.0", llvm::FloatStyle::ExponentUpper, "1.000000E+00");
+    doit(S, "1.0", llvm::FloatStyle::Fixed, "1.00");
+    doit(S, "1.0", llvm::FloatStyle::Percent, "100.00%");
+
+    // check something with both an integer and a fraction
+    doit(S, "1.5", llvm::FloatStyle::Exponent, "1.500000e+00");
+    doit(S, "1.5", llvm::FloatStyle::ExponentUpper, "1.500000E+00");
+    doit(S, "1.5", llvm::FloatStyle::Fixed, "1.50");
+    doit(S, "1.5", llvm::FloatStyle::Percent, "150.00%");
+
+    // check negative
+    doit(S, "-1.5", llvm::FloatStyle::Exponent, "-1.500000e+00");
+    doit(S, "-1.5", llvm::FloatStyle::ExponentUpper, "-1.500000E+00");
+    doit(S, "-1.5", llvm::FloatStyle::Fixed, "-1.50");
+    doit(S, "-1.5", llvm::FloatStyle::Percent, "-150.00%");
+
+
+    // check rounding: 0
+    doit_precision(S, "0.0", llvm::FloatStyle::Exponent, 0, "0e+00");
+    doit_precision(S, "0.0", llvm::FloatStyle::ExponentUpper, 0, "0E+00");
+    doit_precision(S, "0.0", llvm::FloatStyle::Fixed, 0, "0");
+    doit_precision(S, "0.0", llvm::FloatStyle::Percent, 0, "0%");
+
+    // check round down
+    if (Precision >= 3) {
+      doit_precision(S, "1.25", llvm::FloatStyle::Exponent, 0, "1e+00");
+      doit_precision(S, "1.25", llvm::FloatStyle::ExponentUpper, 0, "1E+00");
+      doit_precision(S, "1.25", llvm::FloatStyle::Fixed, 0, "1");
+      doit_precision(S, "1.25", llvm::FloatStyle::Percent, 0, "125%");
+    }
+
+    // check round up
+    if (Precision >= 3) {
+      doit_precision(S, "1.25", llvm::FloatStyle::Exponent, 1, "1.3e+00");
+      doit_precision(S, "1.25", llvm::FloatStyle::ExponentUpper, 1, "1.3E+00");
+      doit_precision(S, "1.25", llvm::FloatStyle::Fixed, 1, "1.3");
+      doit_precision(S, "1.25", llvm::FloatStyle::Percent, 1, "125.0%");
+    }
+
+    // check round up to integer
+    if (Precision >= 3) {
+      doit_precision(S, "1.75", llvm::FloatStyle::Exponent, 0, "2e+00");
+      doit_precision(S, "1.75", llvm::FloatStyle::ExponentUpper, 0, "2E+00");
+      doit_precision(S, "1.75", llvm::FloatStyle::Fixed, 0, "2");
+      doit_precision(S, "1.75", llvm::FloatStyle::Percent, 0, "175%");
+    }
+
+    // check round up
+    if (Precision >= 3) {
+      doit_precision(S, "1.75", llvm::FloatStyle::Exponent, 1, "1.8e+00");
+      doit_precision(S, "1.75", llvm::FloatStyle::ExponentUpper, 1, "1.8E+00");
+      doit_precision(S, "1.75", llvm::FloatStyle::Fixed, 1, "1.8");
+      doit_precision(S, "1.75", llvm::FloatStyle::Percent, 1, "175.0%");
+    }
+
+    // check appending fewer than default number of zeros
+    if (Precision >= 3) {
+      doit_precision(S, "1.75", llvm::FloatStyle::Exponent, 3, "1.750e+00");
+      doit_precision(S, "1.75", llvm::FloatStyle::ExponentUpper, 3, "1.750E+00");
+      doit_precision(S, "1.75", llvm::FloatStyle::Fixed, 3, "1.750");
+      doit_precision(S, "1.75", llvm::FloatStyle::Percent, 3, "175.000%");
+    }
+
+    // check appending more than default number of zeros
+    if (Precision >= 3) {
+      doit_precision(S, "1.75", llvm::FloatStyle::Exponent, 8, "1.75000000e+00");
+      doit_precision(S, "1.75", llvm::FloatStyle::ExponentUpper, 8, "1.75000000E+00");
+      doit_precision(S, "1.75", llvm::FloatStyle::Fixed, 8, "1.75000000");
+      doit_precision(S, "1.75", llvm::FloatStyle::Percent, 8, "175.00000000%");
+    }
+  }
+
+  // test the main types with wider ranges
+  auto sems = { llvm::APFloat::S_IEEEsingle, llvm::APFloat::S_IEEEdouble,
+                llvm::APFloat::S_IEEEquad, llvm::APFloat::S_PPCDoubleDouble,
+            ...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/99088


More information about the llvm-commits mailing list