[flang] initial exact decimal conversion code
authorpeter klausler <pklausler@nvidia.com>
Mon, 26 Nov 2018 19:18:53 +0000 (11:18 -0800)
committerpeter klausler <pklausler@nvidia.com>
Wed, 28 Nov 2018 18:33:08 +0000 (10:33 -0800)
Original-commit: flang-compiler/f18@a34afdc73b8ca43cb20234849a2e9d3f77bbc8e6
Reviewed-on: https://github.com/flang-compiler/f18/pull/231
Tree-same-pre-rewrite: false

flang/lib/evaluate/CMakeLists.txt
flang/lib/evaluate/complex.cc
flang/lib/evaluate/decimal.cc [new file with mode: 0644]
flang/lib/evaluate/decimal.h [new file with mode: 0644]
flang/lib/evaluate/real.h
flang/lib/evaluate/rounding-bits.h

index e54fafc..3c5a14a 100644 (file)
@@ -16,6 +16,7 @@ add_library(FortranEvaluate
   call.cc
   common.cc
   complex.cc
+  decimal.cc
   expression.cc
   fold.cc
   integer.cc
index 6883cf5..d8d7562 100644 (file)
@@ -95,7 +95,8 @@ template<typename R> std::string Complex<R>::DumpHexadecimal() const {
   return result;
 }
 
-template<typename R> std::ostream &Complex<R>::AsFortran(std::ostream &o, int kind) const {
+template<typename R>
+std::ostream &Complex<R>::AsFortran(std::ostream &o, int kind) const {
   re_.AsFortran(o << '(', kind);
   im_.AsFortran(o << ',', kind);
   return o << ')';
diff --git a/flang/lib/evaluate/decimal.cc b/flang/lib/evaluate/decimal.cc
new file mode 100644 (file)
index 0000000..8c30053
--- /dev/null
@@ -0,0 +1,370 @@
+// Copyright (c) 2018, NVIDIA CORPORATION.  All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "decimal.h"
+#include "integer.h"
+
+namespace Fortran::evaluate::value {
+
+static std::ostream *debug{nullptr};
+
+template<typename REAL>
+std::ostream &Decimal<REAL>::Dump(std::ostream &o) const {
+  if (isNegative_) {
+    o << '-';
+  }
+  for (int j{digits_ - 1}; j >= 0; --j) {
+    o << ' ' << digit_[j];
+  }
+  return o << " e" << exponent_ << '\n';
+}
+
+template<typename REAL> void Decimal<REAL>::FromReal(const Real &x) {
+  if (x.IsNegative()) {
+    FromReal(x.Negate());
+    isNegative_ = true;
+    return;
+  }
+  if (debug) {
+    *debug << "FromReal(" << x.DumpHexadecimal() << ")\n";
+  }
+  if (x.IsZero()) {
+    return;
+  }
+  int twoPow{x.UnbiasedExponent()};
+  twoPow -= Real::bits - 1;
+  if (debug) {
+    *debug << "initial twoPow " << twoPow << '\n';
+  }
+  int lshift{x.exponentBits};
+  if (twoPow <= -lshift) {
+    twoPow += lshift;
+    lshift = 0;
+  } else if (twoPow < 0) {
+    lshift += twoPow;
+    twoPow = 0;
+  }
+  if (debug) {
+    *debug << "second twoPow " << twoPow << ", lshift " << lshift << '\n';
+  }
+  SetTo(x.GetFraction().SHIFTL(lshift));
+
+  for (; twoPow > 0 && IsDivisibleBy<5>(); --twoPow) {
+    DivideBy<5>();
+    Normalize();
+    ++exponent_;
+    if (debug) {
+      Dump(*debug << "/5 ");
+    }
+  }
+
+  // Scale by factors of 8, then by 2.
+  static constexpr int log2FastForward{3};
+  static constexpr int fastForward{1 << log2FastForward};
+  for (; twoPow >= log2FastForward; twoPow -= log2FastForward) {
+    MultiplyBy<fastForward>();
+    if (debug) {
+      Dump(*debug << '*' << fastForward << ' ');
+    }
+  }
+  for (; twoPow > 0; --twoPow) {
+    MultiplyBy<2>();
+    if (debug) {
+      Dump(*debug << "*2 ");
+    }
+  }
+  for (; twoPow <= -log2FastForward && IsDivisibleBy<fastForward>();
+       twoPow += log2FastForward) {
+    DivideBy<fastForward>();
+    Normalize();
+    if (debug) {
+      Dump(*debug << '/' << fastForward << ' ');
+    }
+  }
+  for (; twoPow < 0 && IsDivisibleBy<2>(); ++twoPow) {
+    DivideBy<2>();
+    Normalize();
+    if (debug) {
+      Dump(*debug << "/2 ");
+    }
+  }
+  for (; twoPow < 0; ++twoPow) {
+    MultiplyBy<5>();
+    --exponent_;
+    if (debug) {
+      Dump(*debug << "*5 ");
+    }
+  }
+}
+
+// Represents an unrounded binary floating-point
+// value with an unbiased (signed) binary exponent.
+template<typename REAL> class IntermediateFloat {
+public:
+  using Real = REAL;
+  using Word = typename Real::Word;
+
+  void SetTo(Word n) {
+    word_ = n;
+    exponent_ = 0;
+  }
+
+  void MultiplyAndAdd(std::uint32_t n, std::uint32_t plus = 0) {
+    auto product{word_.MultiplyUnsigned(Word{n})};
+    if (plus != 0) {
+      auto sum{product.lower.AddUnsigned(Word{plus})};
+      product.lower = sum.value;
+      if (sum.carry) {
+        product.upper = product.upper.AddUnsigned(1).value;
+      }
+    }
+    bool sticky{false};
+    while (!product.upper.IsZero()) {
+      sticky |= product.lower.BTEST(0);
+      product.lower = product.lower.DSHIFTR(product.upper, 1);
+      product.upper = product.upper.SHIFTR(1);
+    }
+    word_ = product.lower;
+    if (sticky) {
+      word_ = word_.IOR(word_.MASKR(1));
+    }
+  }
+
+  bool IsZero() const { return word_.IsZero(); }
+
+  bool IsFull() const { return word_.IsNegative(); }
+
+  std::ostream &Dump(std::ostream &) const;
+
+  void AdjustExponent(int by = -1) { exponent_ += by; }
+
+  Real ToReal(bool isNegative = false) const;
+
+private:
+  Word word_{0};
+  int exponent_{0};
+};
+
+template<typename REAL>
+std::ostream &IntermediateFloat<REAL>::Dump(std::ostream &o) const {
+  return o << "0x" << word_.Hexadecimal() << " *2**" << exponent_;
+}
+
+template<typename REAL>
+REAL IntermediateFloat<REAL>::ToReal(bool isNegative) const {
+  if (word_.IsNegative()) {
+    IntermediateFloat shifted;
+    shifted.word_ =
+        word_.SHIFTR(1).IOR(word_.IAND(word_.MASKR(1)));  // sticky bit
+    shifted.exponent_ = exponent_ + 1;
+    return shifted.ToReal(false);
+  }
+  Real result = Real::FromInteger(word_).value;
+  int expo{exponent_};
+  while (expo + Real::exponentBias < 1) {
+    Real twoPow{Word{1}.SHIFTL(Real::significandBits)};
+    result = result.Multiply(twoPow).value;
+    expo += Real::exponentBias - 1;
+  }
+  while (expo + Real::exponentBias >= Real::maxExponent) {
+    Real twoPow{Word{Real::maxExponent - 1}.SHIFTL(Real::significandBits)};
+    result = result.Multiply(twoPow).value;
+    expo += Real::maxExponent - 1 - Real::exponentBias;
+  }
+  Real twoPow{Word{expo + Real::exponentBias}.SHIFTL(Real::significandBits)};
+  if (isNegative) {
+    twoPow = twoPow.Negate();
+  }
+  return result.Multiply(twoPow).value;
+}
+
+template<typename REAL> REAL Decimal<REAL>::ToReal(const char *&p) {
+  while (*p == ' ') {
+    ++p;
+  }
+  SetToZero();
+  digitLimit_ = maxDigits;
+  isNegative_ = *p == '-';
+  if (*p == '-' || *p == '+') {
+    ++p;
+  }
+
+  while (*p == '0') {
+    ++p;
+  }
+  bool decimalPoint{false};
+  for (; *p != '\0'; ++p) {
+    char c{*p};
+    if (c == '.') {
+      if (decimalPoint) {
+        break;
+      }
+      decimalPoint = true;
+    } else if (c < '0' || c > '9') {
+      break;
+    } else if (IsFull()) {
+      if (!decimalPoint) {
+        ++exponent_;
+      }
+    } else {
+      MultiplyBy<10>(c - '0');
+      if (decimalPoint) {
+        --exponent_;
+      }
+    }
+  }
+
+  switch (*p) {
+  case 'e':
+  case 'E':
+  case 'd':
+  case 'D':
+  case 'q':
+  case 'Q':
+    ++p;
+    bool negExpo{*p == '-'};
+    if (*p == '+' || *p == '-') {
+      ++p;
+    }
+    char *q;
+    long expoVal{std::strtol(p, &q, 10)};
+    p = const_cast<const char *>(q);
+    if (negExpo) {
+      exponent_ -= expoVal;
+    } else {
+      exponent_ += expoVal;
+    }
+  }
+
+  if (debug) {
+    Dump(*debug << "ToReal start: ");
+  }
+  if (IsZero()) {
+    return Real{}.Negate();  // -0.0
+  }
+
+  if (exponent_ < 0) {
+    // There are decimal digits to the right of the decimal point.
+    // Align that decimal point on a quintillion radix point by
+    // appending zero-valued decimal digits.
+    int align{-exponent_ % log10Quintillion};
+    if (align > 0) {
+      for (; align < log10Quintillion; ++align) {
+        --exponent_;
+        MultiplyBy<10>();
+      }
+      if (debug) {
+        Dump(*debug << "aligned\n");
+      }
+    }
+  }
+
+  IntermediateFloat<Real> f;
+
+  // Transfer the integer part, if any, to the floating-point
+  // result.  The most significant digit can be moved directly;
+  // lesser-order digits require transfer of carries.
+  if (exponent_ >= -(digits_ - 1) * log10Quintillion) {
+    f.SetTo(digit_[--digits_]);
+    while (exponent_ > -digits_ * log10Quintillion) {
+      digitLimit_ = digits_;
+      int carry{MultiplyBy<10>()};
+      f.MultiplyAndAdd(10, carry);
+      --exponent_;
+    }
+  }
+
+  // Shift the decimal point up above the remaining
+  // digits.  If exponent_ remains negative after this
+  // adjustment, additional digits will be created
+  // in higher order positions as carries take place.
+  // Once exponent_ is zero, the carries will then be
+  // appended to the floating-point result.
+  exponent_ += digits_ * log10Quintillion;
+  if (debug) {
+    *debug << "after converting integer part ";
+    f.Dump(*debug) << '\n';
+    Dump(*debug);
+  }
+
+  // Convert the remaining fraction into bits of the
+  // resulting floating-point value until we run out of
+  // room.
+  while (!f.IsFull() && !IsZero()) {
+    if (debug) {
+      f.Dump(*debug << "step: ") << '\n';
+      Dump(*debug);
+    }
+    f.AdjustExponent(-1);
+    digitLimit_ = digits_;
+    std::uint32_t carry = MultiplyBy<2>();
+    RemoveLeastOrderZeroDigits();
+    if (carry != 0) {
+      if (exponent_ < 0) {
+        exponent_ += log10Quintillion;
+        digit_[digits_++] = carry;
+        carry = 0;
+      }
+    }
+    f.MultiplyAndAdd(2, carry);
+  }
+  if (debug) {
+    f.Dump(*debug << "after converting fraction ") << '\n';
+    Dump(*debug);
+  }
+
+  return f.ToReal(isNegative_);
+}
+
+template<typename REAL>
+std::string Decimal<REAL>::ToString(int maxDigits) const {
+  std::string result;
+  if (isNegative_) {
+    result += '-';
+  }
+  if (IsZero()) {
+    result += "0.";
+  } else {
+    std::string d{std::to_string(digit_[digits_ - 1])};
+    for (int j{digits_ - 2}; j >= 0; --j) {
+      auto part{std::to_string(digit_[j])};
+      unsigned zeroes = log10Quintillion - part.size();
+      d += std::string(zeroes, '0');
+      d += part;
+    }
+    int dn = d.size();
+    result += d[0];
+    result += '.';
+    if (dn > maxDigits) {
+      result += d.substr(1, maxDigits - 1);
+    } else {
+      result += d.substr(1);
+    }
+    while (result.back() == '0') {
+      result.pop_back();
+    }
+    if (exponent_ + dn - 1 != 0) {
+      result += 'e';
+      result += std::to_string(exponent_ + dn - 1);
+    }
+  }
+  return result;
+}
+
+template class Decimal<Real<Integer<16>, 11>>;
+template class Decimal<Real<Integer<32>, 24>>;
+template class Decimal<Real<Integer<64>, 53>>;
+template class Decimal<Real<Integer<80>, 64, false>>;
+template class Decimal<Real<Integer<128>, 112>>;
+}
diff --git a/flang/lib/evaluate/decimal.h b/flang/lib/evaluate/decimal.h
new file mode 100644 (file)
index 0000000..6a420fc
--- /dev/null
@@ -0,0 +1,181 @@
+// Copyright (c) 2018, NVIDIA CORPORATION.  All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef FORTRAN_EVALUATE_DECIMAL_H_
+#define FORTRAN_EVALUATE_DECIMAL_H_
+
+#include "real.h"
+#include <cinttypes>
+#include <limits>
+#include <ostream>
+
+// This is a helper class for use in floating-point conversions
+// to and from decimal representations.  It holds a multiple-precision
+// integer value using digits in base 10**18 (one quintillion).  This
+// radix is the largest power of ten such that 10 times that value will
+// fit in an unsigned 64-bit binary integer.  It is accompanied by a
+// signed exponent that denotes multiplication by a power of ten.
+//
+// The operations supported by this class are limited to those required
+// for conversions between binary and decimal representations; it is not
+// a general-purpose facility.
+
+namespace Fortran::evaluate::value {
+
+template<typename REAL> class Decimal {
+private:
+  using Digit = std::uint64_t;
+  using Real = REAL;
+
+  // 10 * quintillion must not overflow a 64-bit unsigned integer
+  static constexpr int log10Quintillion{18};
+  static constexpr Digit quintillion{
+      static_cast<Digit>(1000000) * 1000000 * 1000000};
+  static_assert(quintillion < std::numeric_limits<Digit>::max() / 10,
+      "10**18 is too big somehow");
+  static_assert(quintillion > std::numeric_limits<Digit>::max() / 100,
+      "10**18 is too small somehow");
+
+  // The base-2 logarithm of the least significant bit that can arise
+  // in a subnormal IEEE floating-point number.
+  static constexpr int minLog2AnyBit{
+      -static_cast<int>(Real::exponentBias) - Real::precision};
+  static constexpr int maxDigits{2 - minLog2AnyBit / log10Quintillion};
+
+public:
+  Decimal() {}
+
+  void SetToZero() {
+    isNegative_ = false;
+    digits_ = 0;
+    first_ = 0;
+    exponent_ = 0;
+  }
+  void FromReal(const Real &);
+  Real ToReal(const char *&);  // arg left pointing to first unparsed char
+  std::string ToString(int maxDigits = 1000000) const;
+
+private:
+  std::ostream &Dump(std::ostream &) const;
+
+  bool IsZero() const {
+    for (int j{first_}; j < digits_; ++j) {
+      if (digit_[j] != 0) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  bool IsFull() const {
+    return digits_ == digitLimit_ && 10 * digit_[digits_ - 1] >= quintillion;
+  }
+
+  template<typename INT> INT SetTo(INT n) {
+    SetToZero();
+    while (!n.IsZero()) {
+      auto qr{n.DivideUnsigned(10)};
+      if (!qr.remainder.IsZero()) {
+        break;
+      }
+      ++exponent_;
+      n = qr.quotient;
+    }
+    while (!n.IsZero() && digits_ < digitLimit_) {
+      auto qr{n.DivideUnsigned(quintillion)};
+      digit_[digits_++] = qr.remainder.ToUInt64();
+      if (digits_ == first_ + 1 && digit_[first_] == 0) {
+        ++first_;
+      }
+      n = qr.quotient;
+    }
+    return n;
+  }
+
+  int RemoveLeastOrderZeroDigits() {
+    int removed{0};
+    while (first_ < digits_ && digit_[first_] == 0) {
+      ++first_;
+      ++removed;
+    }
+    if (first_ == digits_) {
+      first_ = digits_ = 0;
+    }
+    return removed;
+  }
+
+  void Normalize() {
+    while (digits_ > 0 && digit_[digits_ - 1] == 0) {
+      --digits_;
+    }
+    exponent_ += RemoveLeastOrderZeroDigits() * log10Quintillion;
+  }
+
+  // This limited divisibility test only works for even divisors of 10**18,
+  // which is fine since it's only used with 2 and 5.
+  template<int N> bool IsDivisibleBy() const {
+    static_assert(N > 1 && quintillion % N == 0, "bad modulus");
+    return digits_ == first_ || (digit_[first_] % N) == 0;
+  }
+
+  template<int N> int DivideBy() {
+    int remainder{0};
+    for (int j{digits_ - 1}; j >= 0; --j) {
+      if (j < first_) {
+        if (remainder == 0) {
+          break;
+        }
+        first_ = j;
+      }
+      int nrem = digit_[j] % N;
+      digit_[j] /= N;
+      digit_[j] += (quintillion / N) * remainder;
+      remainder = nrem;
+    }
+    return remainder;
+  }
+
+  template<int N> int MultiplyBy(int carry = 0) {
+    for (int j{first_}; j < digits_; ++j) {
+      digit_[j] = N * digit_[j] + carry;
+      carry = digit_[j] / quintillion;
+      digit_[j] %= quintillion;
+      if (j == first_ && digit_[j] == 0) {
+        ++first_;
+      }
+    }
+    if (carry != 0) {
+      if (digits_ < digitLimit_) {
+        digit_[digits_++] = carry;
+        carry = 0;
+      }
+    }
+    return carry;
+  }
+
+  Digit digit_[maxDigits];  // base-quintillion digits in little-endian order
+  int digits_{0};  // significant elements in digit_[] array
+  int first_{0};  // digits below this are all zero
+  int digitLimit_{maxDigits};  // clamp
+  int exponent_{0};  // signed power of ten
+  bool isNegative_{false};
+};
+
+extern template class Decimal<Real<Integer<16>, 11>>;
+extern template class Decimal<Real<Integer<32>, 24>>;
+extern template class Decimal<Real<Integer<64>, 53>>;
+extern template class Decimal<Real<Integer<80>, 64, false>>;
+extern template class Decimal<Real<Integer<128>, 112>>;
+}
+#endif  // FORTRAN_EVALUATE_DECIMAL_H_
index 6ddcf16..09b631d 100644 (file)
@@ -39,8 +39,10 @@ namespace Fortran::evaluate::value {
 template<typename WORD, int PRECISION, bool IMPLICIT_MSB = true> class Real {
 public:
   using Word = WORD;
-  static constexpr int bits{Word::bits};
   static constexpr int precision{PRECISION};
+  using Fraction = Integer<precision>;  // all bits made explicit
+
+  static constexpr int bits{Word::bits};
   static constexpr bool implicitMSB{IMPLICIT_MSB};
   static constexpr int significandBits{precision - implicitMSB};
   static constexpr int exponentBits{bits - significandBits - 1 /*sign*/};
@@ -57,6 +59,7 @@ public:
   static constexpr int decimalDigits{(precision * 43) / 100};
 
   // Associates a decimal exponent with an integral value for formatting.
+  // TODO pmk remove
   struct ScaledDecimal {
     bool negative{false};
     Word integer;  // unsigned
@@ -322,6 +325,21 @@ public:
     return exponent;
   }
 
+  // Extracts the fraction; any implied bit is made explicit.
+  constexpr Fraction GetFraction() const {
+    Fraction result{Fraction::ConvertUnsigned(word_).value};
+    if constexpr (!implicitMSB) {
+      return result;
+    } else {
+      std::uint64_t exponent{Exponent()};
+      if (exponent > 0 && exponent < maxExponent) {
+        return result.IBSET(significandBits);
+      } else {
+        return result.IBCLR(significandBits);
+      }
+    }
+  }
+
   static ValueWithRealFlags<Real> Read(
       const char *&, Rounding rounding = defaultRounding);
   std::string DumpHexadecimal() const;
@@ -331,27 +349,12 @@ public:
   std::ostream &AsFortran(std::ostream &, int kind) const;
 
 private:
-  using Fraction = Integer<precision>;  // all bits made explicit
   using Significand = Integer<significandBits>;  // no implicit bit
 
   constexpr Significand GetSignificand() const {
     return Significand::ConvertUnsigned(word_).value;
   }
 
-  constexpr Fraction GetFraction() const {
-    Fraction result{Fraction::ConvertUnsigned(word_).value};
-    if constexpr (!implicitMSB) {
-      return result;
-    } else {
-      std::uint64_t exponent{Exponent()};
-      if (exponent > 0 && exponent < maxExponent) {
-        return result.IBSET(significandBits);
-      } else {
-        return result.IBCLR(significandBits);
-      }
-    }
-  }
-
   constexpr std::int64_t CombineExponents(const Real &y, bool forDivide) const {
     std::int64_t exponent = Exponent(), yExponent = y.Exponent();
     // A zero exponent field value has the same weight as 1.
index 2799937..5c55d7b 100644 (file)
@@ -15,9 +15,9 @@
 #ifndef FORTRAN_EVALUATE_ROUNDING_BITS_H_
 #define FORTRAN_EVALUATE_ROUNDING_BITS_H_
 
-// A helper class used by Real<> (below) to determine rounding of inexact
-// results.  Bits lost from intermediate computations by being shifted
-// rightward are accumulated here.
+// A helper class used by Real<> to determine rounding of rational results
+// to floating-point values.  Bits lost from intermediate computations by
+// being shifted rightward are accumulated in instances of this class.
 
 namespace Fortran::evaluate::value {
 
@@ -96,7 +96,7 @@ public:
   }
 
 private:
-  bool guard_{false};  // 0.5 * ulp
+  bool guard_{false};  // 0.5 * ulp (unit in lowest place)
   bool round_{false};  // 0.25 * ulp
   bool sticky_{false};  // true if any lesser-valued bit would be set
 };