Skip to content

[APFloat] Add APFloat::format #99088

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

Ariel-Burton
Copy link
Contributor

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.

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.
@llvmbot
Copy link
Member

llvmbot commented Jul 16, 2024

@llvm/pr-subscribers-llvm-adt

@llvm/pr-subscribers-llvm-support

Author: None (Ariel-Burton)

Changes

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 --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]

@tschuett tschuett requested a review from arsenm July 16, 2024 19:36
Copy link

github-actions bot commented Jul 16, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@tschuett tschuett added the floating-point Floating-point math label Jul 16, 2024
APInt getSignificand() const;

/// decompose to an integer and exponent to radix 2
bool decomposeToIntegerAndPowerOf2(APInt &, int &) const;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is a limitation of APFloat, but this should return std::optional<APInt, int>.

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can any of the code be shared with toString?

Do we really want to mimic the old llvm::write_double API, as opposed to doing something closer to std::to_chars?

@Ariel-Burton
Copy link
Contributor Author

Can any of the code be shared with toString?

Do we really want to mimic the old llvm::write_double API, as opposed to doing something closer to std::to_chars?

format was introduced because toString didn't give the required functionality.

One place format is intended to be used is, for example,

APFloat F;
.
.
OS << F.convertToDouble();

@efriedma-quic
Copy link
Collaborator

That doesn't really answer either of my questions.

@Ariel-Burton
Copy link
Contributor Author

That doesn't really answer either of my questions.

My apologies, Let me try to be more explicit.

It's some time since I wrote format, and although I don't recall all the details, at the time I thought there was little that could be reused. Scanning the routines again, the only thing that jumps out at me that might be easily sharable is the transformation from base 2 to base 10. Not sure that's worth it.

With respect to considering to_chars-like functionality, that's out the scope of this change and represents a different policy. This PR is intended to eliminate the conversion to host-format floating point while at the same time preserving the same output that hitherto would have been emitted. In that sense we are trying to achieve a no-functional-change. So, yes, we could consider doing something like to_chars(), but not in this change.

@efriedma-quic
Copy link
Collaborator

What exactly is different between toString and format? Both routines support both fixed and scientific-style output, and they both use a constant "precision". There are minor differences in some specific usages, but I'd rather slightly extend the toString API, instead of maintaining two implementations of string conversion.

With respect to considering to_chars-like functionality

You mean, the round-trip precision stuff? Sure, we don't need to do that here.

@Ariel-Burton
Copy link
Contributor Author

What exactly is different between toString and format? Both routines support both fixed and scientific-style output, and they both use a constant "precision". There are minor differences in some specific usages, but I'd rather slightly extend the toString API, instead of maintaining two implementations of string conversion.

With respect to considering to_chars-like functionality

You mean, the round-trip precision stuff? Sure, we don't need to do that here.

As I said above, it's some time since I wrote format and some of he details elude me. I was unable to reconcile the differences between OS << F.toString() and OS << F.convertToDouble() by tweaking the parameters to toString.

@Ariel-Burton
Copy link
Contributor Author

ping ...

I = APInt(semantics->precision, 0);
exp = 0;
} else {
// the significant bits are the significand
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// the significant bits are the significand
// The significant bits are the significand

} else {
// the significant bits are the significand
I = getSignificand();
// now set up the exp.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// now set up the exp.
// Now set up the exp.

// of the left most bit, i.e., the significand is s.ssssss... .
exp = exponent - (semantics->precision - 1);

// get rid of any trailing zeros
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// get rid of any trailing zeros
// Get rid of any trailing zeros

if (aOK && bOK) {
if (aExp > bExp) {
int deltaExp = aExp - bExp;
// shift a left to reduce its exponent to be the same as b
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// shift a left to reduce its exponent to be the same as b
// Shift a left to reduce its exponent to be the same as b

exp = bExp;
} else if (bExp > aExp) {
int deltaExp = bExp - aExp;
// shift b left to reduce its exponent to be the same as a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// shift b left to reduce its exponent to be the same as a
// Shift b left to reduce its exponent to be the same as a


// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// then need to round. What we do is extract the
// Then need to round. What we do is extract the

Capitalize comments

}

SmallVectorImpl<char> &
format(SmallVectorImpl<char> &strout,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why isn't this on raw_ostream, and using a raw small string?

TEST(APFloatTest, format) {
auto doit_precision = [](const fltSemantics &S, const char *value,
llvm::FloatStyle style, int precision,
const char *expected) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const char *expected) {
StringRef expected) {

SmallString<16> s;
APFloat apf(S, value);
apf.format(s, style, precision);
EXPECT_STREQ(expected, s.c_str());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
EXPECT_STREQ(expected, s.c_str());
EXPECT_EQ(expected, s);

Regular EXPECT_EQ should work fine with StringRef + SmallString

// 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,
llvm::APFloat::S_x87DoubleExtended};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check the exotic cases, like the new type without zero

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants