You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by am...@apache.org on 2018/03/30 14:19:49 UTC

[trafficserver] branch master updated: Support floating points for bwformat

This is an automated email from the ASF dual-hosted git repository.

amc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new d9a4e9a  Support floating points for bwformat
d9a4e9a is described below

commit d9a4e9aceda117697b24c124dbb085d939a9a620
Author: Alan Wang <xf...@gmail.com>
AuthorDate: Mon Mar 26 11:28:08 2018 -0700

    Support floating points for bwformat
---
 lib/ts/BufferWriter.h                        |  15 ++
 lib/ts/BufferWriterFormat.cc                 | 244 ++++++++++++++++++---------
 lib/ts/unit-tests/test_BufferWriterFormat.cc | 191 +++++++++++++++++++++
 3 files changed, 368 insertions(+), 82 deletions(-)

diff --git a/lib/ts/BufferWriter.h b/lib/ts/BufferWriter.h
index 0e9616d..cdce9ec 100644
--- a/lib/ts/BufferWriter.h
+++ b/lib/ts/BufferWriter.h
@@ -493,6 +493,9 @@ namespace bw_fmt
   /// Generic integral conversion.
   BufferWriter &Format_Integer(BufferWriter &w, BWFSpec const &spec, uintmax_t n, bool negative_p);
 
+  /// Generic floating point conversion.
+  BufferWriter &Format_Floating(BufferWriter &w, BWFSpec const &spec, double n, bool negative_p);
+
 } // bw_fmt
 
 /** Compiled BufferWriter format
@@ -676,6 +679,18 @@ bwformat(BufferWriter &w, BWFSpec const &spec, TextView const &tv)
   return bwformat(w, spec, static_cast<string_view>(tv));
 }
 
+inline BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &spec, double const &d)
+{
+  return d < 0 ? bw_fmt::Format_Floating(w, spec, -d, true) : bw_fmt::Format_Floating(w, spec, d, false);
+}
+
+inline BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &spec, float const &f)
+{
+  return f < 0 ? bw_fmt::Format_Floating(w, spec, -f, true) : bw_fmt::Format_Floating(w, spec, f, false);
+}
+
 /* Integer types.
 
    Due to some oddities for MacOS building, need a bit more template magic here. The underlying
diff --git a/lib/ts/BufferWriterFormat.cc b/lib/ts/BufferWriterFormat.cc
index d660e8f..7c02beb 100644
--- a/lib/ts/BufferWriterFormat.cc
+++ b/lib/ts/BufferWriterFormat.cc
@@ -24,6 +24,9 @@
 #include <ts/BufferWriter.h>
 #include <ctype.h>
 #include <ctime>
+#include <cmath>
+#include <math.h>
+#include <array>
 
 namespace
 {
@@ -260,6 +263,8 @@ namespace bw_fmt
   {
     char UPPER_DIGITS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
     char LOWER_DIGITS[] = "0123456789abcdefghijklmnopqrstuvwxyz";
+    static const std::array<uint64_t, 11> POWERS_OF_TEN = {
+      {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000}};
   }
 
   /// Templated radix based conversions. Only a small number of radix are supported
@@ -283,6 +288,49 @@ namespace bw_fmt
     return (buff + width) - out;
   }
 
+  template <typename F>
+  void
+  Write_Aligned(BufferWriter &w, F const &f, BWFSpec::Align align, int width, char fill, char neg)
+  {
+    switch (align) {
+    case BWFSpec::Align::LEFT:
+      if (neg)
+        w.write(neg);
+      f();
+      while (width-- > 0)
+        w.write(fill);
+      break;
+    case BWFSpec::Align::RIGHT:
+      while (width-- > 0)
+        w.write(fill);
+      if (neg)
+        w.write(neg);
+      f();
+      break;
+    case BWFSpec::Align::CENTER:
+      for (int i = width / 2; i > 0; --i)
+        w.write(fill);
+      if (neg)
+        w.write(neg);
+      f();
+      for (int i = (width + 1) / 2; i > 0; --i)
+        w.write(fill);
+      break;
+    case BWFSpec::Align::SIGN:
+      if (neg)
+        w.write(neg);
+      while (width-- > 0)
+        w.write(fill);
+      f();
+      break;
+    default:
+      if (neg)
+        w.write(neg);
+      f();
+      break;
+    }
+  }
+
   BufferWriter &
   Format_Integer(BufferWriter &w, BWFSpec const &spec, uintmax_t i, bool neg_p)
   {
@@ -336,10 +384,7 @@ namespace bw_fmt
     width -= static_cast<int>(n);
     string_view digits{buff + sizeof(buff) - n, n};
 
-    // The idea here is the various pieces have all been assembled, the only difference
-    // is the order in which they are written to the output.
-    switch (spec._align) {
-    case BWFSpec::Align::LEFT:
+    if (spec._align == BWFSpec::Align::SIGN) { // custom for signed case because prefix and digits are seperated.
       if (neg)
         w.write(neg);
       if (prefix1) {
@@ -347,59 +392,123 @@ namespace bw_fmt
         if (prefix2)
           w.write(prefix2);
       }
-      w.write(digits);
-      while (width-- > 0)
-        w.write(spec._fill);
-      break;
-    case BWFSpec::Align::RIGHT:
       while (width-- > 0)
         w.write(spec._fill);
-      if (neg)
-        w.write(neg);
-      if (prefix1) {
-        w.write(prefix1);
-        if (prefix2)
-          w.write(prefix2);
-      }
       w.write(digits);
-      break;
-    case BWFSpec::Align::CENTER:
-      for (int i = width / 2; i > 0; --i)
-        w.write(spec._fill);
-      if (neg)
-        w.write(neg);
-      if (prefix1) {
-        w.write(prefix1);
-        if (prefix2)
-          w.write(prefix2);
-      }
-      w.write(digits);
-      for (int i = (width + 1) / 2; i > 0; --i)
-        w.write(spec._fill);
-      break;
-    case BWFSpec::Align::SIGN:
-      if (neg)
-        w.write(neg);
-      if (prefix1) {
-        w.write(prefix1);
-        if (prefix2)
-          w.write(prefix2);
+    } else { // use generic Write_Aligned
+      Write_Aligned(w,
+                    [&]() {
+                      if (prefix1) {
+                        w.write(prefix1);
+                        if (prefix2)
+                          w.write(prefix2);
+                      }
+                      w.write(digits);
+                    },
+                    spec._align, width, spec._fill, neg);
+    }
+    return w;
+  }
+
+  /// Format for floating point values. Seperates floating point into a whole number and a
+  /// fraction. The fraction is converted into an unsigned integer based on the specified
+  /// precision, spec._prec. ie. 3.1415 with precision two is seperated into two unsigned
+  /// integers 3 and 14. The different pieces are assembled and placed into the BufferWriter.
+  /// The default is two decimal places. ie. X.XX. The value is always written in base 10.
+  ///
+  /// format: whole.fraction
+  ///     or: left.right
+  BufferWriter &
+  Format_Floating(BufferWriter &w, BWFSpec const &spec, double f, bool neg_p)
+  {
+    static const ts::string_view infinity_bwf{"Inf"};
+    static const ts::string_view nan_bwf{"NaN"};
+    static const ts::string_view zero_bwf{"0"};
+    static const ts::string_view subnormal_bwf{"subnormal"};
+    static const ts::string_view unknown_bwf{"unknown float"};
+
+    // Handle floating values that are not normal
+    if (!std::isnormal(f)) {
+      ts::string_view unnormal;
+      switch (std::fpclassify(f)) {
+      case FP_INFINITE:
+        unnormal = infinity_bwf;
+        break;
+      case FP_NAN:
+        unnormal = nan_bwf;
+        break;
+      case FP_ZERO:
+        unnormal = zero_bwf;
+        break;
+      case FP_SUBNORMAL:
+        unnormal = subnormal_bwf;
+        break;
+      default:
+        unnormal = unknown_bwf;
       }
-      while (width-- > 0)
-        w.write(spec._fill);
-      w.write(digits);
-      break;
-    default:
-      if (neg)
-        w.write(neg);
-      if (prefix1) {
-        w.write(prefix1);
-        if (prefix2)
-          w.write(prefix2);
+
+      w.write(unnormal);
+      return w;
+    }
+
+    uint64_t whole_part = static_cast<uint64_t>(f);
+    if (whole_part == f || spec._prec == 0) { // integral
+      return Format_Integer(w, spec, whole_part, neg_p);
+    }
+
+    static constexpr char dec = '.';
+    double frac;
+    size_t l = 0;
+    size_t r = 0;
+    char whole[std::numeric_limits<double>::digits10 + 1];
+    char fraction[std::numeric_limits<double>::digits10 + 1];
+    char neg               = 0;
+    int width              = static_cast<int>(spec._min);                             // amount left to fill.
+    unsigned int precision = (spec._prec == BWFSpec::DEFAULT._prec) ? 2 : spec._prec; // default precision 2
+
+    frac = f - whole_part; // split the number
+
+    if (neg_p) {
+      neg = '-';
+    } else if (spec._sign != '-') {
+      neg = spec._sign;
+    }
+
+    // Shift the floating point based on the precision. Used to convert
+    //  trailing fraction into an integer value.
+    uint64_t shift;
+    if (precision < POWERS_OF_TEN.size()) {
+      shift = POWERS_OF_TEN[precision];
+    } else { // not precomputed.
+      shift = POWERS_OF_TEN.back();
+      for (precision -= (POWERS_OF_TEN.size() - 1); precision > 0; --precision) {
+        shift *= 10;
       }
-      w.write(digits);
-      break;
     }
+
+    uint64_t frac_part = static_cast<uint64_t>(frac * shift + 0.5 /* rounding */);
+
+    l = bw_fmt::To_Radix<10>(whole_part, whole, sizeof(whole), bw_fmt::LOWER_DIGITS);
+    r = bw_fmt::To_Radix<10>(frac_part, fraction, sizeof(fraction), bw_fmt::LOWER_DIGITS);
+
+    // Clip fill width
+    if (neg)
+      --width;
+    width -= static_cast<int>(l);
+    --width; // '.'
+    width -= static_cast<int>(r);
+
+    string_view whole_digits{whole + sizeof(whole) - l, l};
+    string_view frac_digits{fraction + sizeof(fraction) - r, r};
+
+    Write_Aligned(w,
+                  [&]() {
+                    w.write(whole_digits);
+                    w.write(dec);
+                    w.write(frac_digits);
+                  },
+                  spec._align, width, spec._fill, neg);
+
     return w;
   }
 
@@ -415,35 +524,6 @@ namespace bw_fmt
     }
   }
 
-  template <typename F>
-  void
-  Write_Aligned(BufferWriter &w, F const &f, BWFSpec::Align align, int width, char fill)
-  {
-    switch (align) {
-    case BWFSpec::Align::LEFT:
-    case BWFSpec::Align::SIGN:
-      f();
-      while (width-- > 0)
-        w.write(fill);
-      break;
-    case BWFSpec::Align::RIGHT:
-      while (width-- > 0)
-        w.write(fill);
-      f();
-      break;
-    case BWFSpec::Align::CENTER:
-      for (int i = width / 2; i > 0; --i)
-        w.write(fill);
-      f();
-      for (int i = (width + 1) / 2; i > 0; --i)
-        w.write(fill);
-      break;
-    default:
-      f();
-      break;
-    }
-  }
-
 } // bw_fmt
 
 BufferWriter &
@@ -461,10 +541,10 @@ bwformat(BufferWriter &w, BWFSpec const &spec, string_view sv)
       w.write(spec._type);
       width -= 2;
     }
-    bw_fmt::Write_Aligned(w, [&w, &sv, digits]() { bw_fmt::Hex_Dump(w, sv, digits); }, spec._align, width, spec._fill);
+    bw_fmt::Write_Aligned(w, [&w, &sv, digits]() { bw_fmt::Hex_Dump(w, sv, digits); }, spec._align, width, spec._fill, 0);
   } else {
     width -= sv.size();
-    bw_fmt::Write_Aligned(w, [&w, &sv]() { w.write(sv); }, spec._align, width, spec._fill);
+    bw_fmt::Write_Aligned(w, [&w, &sv]() { w.write(sv); }, spec._align, width, spec._fill, 0);
   }
   return w;
 }
diff --git a/lib/ts/unit-tests/test_BufferWriterFormat.cc b/lib/ts/unit-tests/test_BufferWriterFormat.cc
index 6431803..b1a5b03 100644
--- a/lib/ts/unit-tests/test_BufferWriterFormat.cc
+++ b/lib/ts/unit-tests/test_BufferWriterFormat.cc
@@ -232,6 +232,197 @@ TEST_CASE("bwstring", "[bwprint][bwstring]")
   REQUIRE(s == "32767 .. |e99a18c428cb38d5f260|");
 }
 
+TEST_CASE("BWFormat integral", "[bwprint][bwformat]")
+{
+  ts::LocalBufferWriter<256> bw;
+  ts::BWFSpec spec;
+  uint32_t num = 30;
+  int num_neg  = -30;
+
+  // basic
+  bwformat(bw, spec, num);
+  REQUIRE(bw.view() == "30");
+  bw.reduce(0);
+  bwformat(bw, spec, num_neg);
+  REQUIRE(bw.view() == "-30");
+  bw.reduce(0);
+
+  // radix
+  ts::BWFSpec spec_hex;
+  spec_hex._radix_lead_p = true;
+  spec_hex._type         = 'x';
+  bwformat(bw, spec_hex, num);
+  REQUIRE(bw.view() == "0x1e");
+  bw.reduce(0);
+
+  ts::BWFSpec spec_dec;
+  spec_dec._type = '0';
+  bwformat(bw, spec_dec, num);
+  REQUIRE(bw.view() == "30");
+  bw.reduce(0);
+
+  ts::BWFSpec spec_bin;
+  spec_bin._radix_lead_p = true;
+  spec_bin._type         = 'b';
+  bwformat(bw, spec_bin, num);
+  REQUIRE(bw.view() == "0b11110");
+  bw.reduce(0);
+
+  int one     = 1;
+  int two     = 2;
+  int three_n = -3;
+  // alignment
+  ts::BWFSpec left;
+  left._align = ts::BWFSpec::Align::LEFT;
+  left._min   = 5;
+  ts::BWFSpec right;
+  right._align = ts::BWFSpec::Align::RIGHT;
+  right._min   = 5;
+  ts::BWFSpec center;
+  center._align = ts::BWFSpec::Align::CENTER;
+  center._min   = 5;
+
+  bwformat(bw, left, one);
+  bwformat(bw, right, two);
+  REQUIRE(bw.view() == "1        2");
+  bwformat(bw, right, two);
+  REQUIRE(bw.view() == "1        2    2");
+  bwformat(bw, center, three_n);
+  REQUIRE(bw.view() == "1        2    2 -3  ");
+}
+
+TEST_CASE("BWFormat floating", "[bwprint][bwformat]")
+{
+  ts::LocalBufferWriter<256> bw;
+  ts::BWFSpec spec;
+
+  bw.reduce(0);
+  bw.print("{}", 3.14);
+  REQUIRE(bw.view() == "3.14");
+  bw.reduce(0);
+  bw.print("{} {:.2} {:.0} ", 32.7, 32.7, 32.7);
+  REQUIRE(bw.view() == "32.70 32.70 32 ");
+  bw.reduce(0);
+  bw.print("{} neg {:.3}", -123.2, -123.2);
+  REQUIRE(bw.view() == "-123.20 neg -123.200");
+  bw.reduce(0);
+  bw.print("zero {} quarter {} half {} 3/4 {}", 0, 0.25, 0.50, 0.75);
+  REQUIRE(bw.view() == "zero 0 quarter 0.25 half 0.50 3/4 0.75");
+  bw.reduce(0);
+  bw.print("long {:.11}", 64.9);
+  REQUIRE(bw.view() == "long 64.90000000000");
+  bw.reduce(0);
+
+  double n   = 180.278;
+  double neg = -238.47;
+  bwformat(bw, spec, n);
+  REQUIRE(bw.view() == "180.28");
+  bw.reduce(0);
+  bwformat(bw, spec, neg);
+  REQUIRE(bw.view() == "-238.47");
+  bw.reduce(0);
+
+  spec._prec = 5;
+  bwformat(bw, spec, n);
+  REQUIRE(bw.view() == "180.27800");
+  bw.reduce(0);
+  bwformat(bw, spec, neg);
+  REQUIRE(bw.view() == "-238.47000");
+  bw.reduce(0);
+
+  float f    = 1234;
+  float fneg = -1;
+  bwformat(bw, spec, f);
+  REQUIRE(bw.view() == "1234");
+  bw.reduce(0);
+  bwformat(bw, spec, fneg);
+  REQUIRE(bw.view() == "-1");
+  bw.reduce(0);
+  f          = 1234.5667;
+  spec._prec = 4;
+  bwformat(bw, spec, f);
+  REQUIRE(bw.view() == "1234.5667");
+  bw.reduce(0);
+
+  bw << 1234 << .567;
+  REQUIRE(bw.view() == "12340.57");
+  bw.reduce(0);
+  bw << f;
+  REQUIRE(bw.view() == "1234.57");
+  bw.reduce(0);
+  bw << n;
+  REQUIRE(bw.view() == "180.28");
+  bw.reduce(0);
+  bw << f << n;
+  REQUIRE(bw.view() == "1234.57180.28");
+  bw.reduce(0);
+
+  double edge = 0.345;
+  spec._prec  = 3;
+  bwformat(bw, spec, edge);
+  REQUIRE(bw.view() == "0.345");
+  bw.reduce(0);
+  edge = .1234;
+  bwformat(bw, spec, edge);
+  REQUIRE(bw.view() == "0.123");
+  bw.reduce(0);
+  edge = 1.0;
+  bwformat(bw, spec, edge);
+  REQUIRE(bw.view() == "1");
+  bw.reduce(0);
+
+  // alignment
+  double first  = 1.23;
+  double second = 2.35;
+  double third  = -3.5;
+  ts::BWFSpec left;
+  left._align = ts::BWFSpec::Align::LEFT;
+  left._min   = 5;
+  ts::BWFSpec right;
+  right._align = ts::BWFSpec::Align::RIGHT;
+  right._min   = 5;
+  ts::BWFSpec center;
+  center._align = ts::BWFSpec::Align::CENTER;
+  center._min   = 5;
+
+  bwformat(bw, left, first);
+  bwformat(bw, right, second);
+  REQUIRE(bw.view() == "1.23  2.35");
+  bwformat(bw, right, second);
+  REQUIRE(bw.view() == "1.23  2.35 2.35");
+  bwformat(bw, center, third);
+  REQUIRE(bw.view() == "1.23  2.35 2.35-3.50");
+  bw.reduce(0);
+
+  double over = 1.4444444;
+  ts::BWFSpec over_min;
+  over_min._prec = 7;
+  over_min._min  = 5;
+  bwformat(bw, over_min, over);
+  REQUIRE(bw.view() == "1.4444444");
+  bw.reduce(0);
+
+  // Edge
+  bw.print("{}", (1.0 / 0.0));
+  REQUIRE(bw.view() == "Inf");
+  bw.reduce(0);
+
+  double inf = std::numeric_limits<double>::infinity();
+  bw.print("  {} ", inf);
+  REQUIRE(bw.view() == "  Inf ");
+  bw.reduce(0);
+
+  double nan_1 = std::nan("1");
+  bw.print("{} {}", nan_1, nan_1);
+  REQUIRE(bw.view() == "NaN NaN");
+  bw.reduce(0);
+
+  double z = 0.0;
+  bw.print("{}  ", z);
+  REQUIRE(bw.view() == "0  ");
+  bw.reduce(0);
+}
+
 // Normally there's no point in running the performance tests, but it's worth keeping the code
 // for when additional testing needs to be done.
 #if 0

-- 
To stop receiving notification emails like this one, please contact
amc@apache.org.