Project

General

Profile

Bug #9595 » 0002-Improve-floating-point-round-trip-for-Postgres.patch

Bruce Toll, 01/10/2022 12:22 PM

View differences:

src/Wt/Dbo/backend/Postgres.C
29 29
#include <cstring>
30 30
#include <ctime>
31 31

  
32
/*
33
 * include charconv (s.b. in c++ 17, but sometimes available with c++ 14, as an extension)
34
 * NOTE: Some implementations of charconv are incomplete, e.g. lack floating point.
35
 *       Therefore, a separate feature test, e.g. #ifdef __cpp_lib_to_chars, is still required
36
 */
37

  
38
#if defined __has_include
39
#  if __has_include (<charconv>)
40
#    include <charconv>
41
#    ifdef __cpp_lib_to_chars
42
#      define WT_CPP_LIB_TO_CHARS
43
#    endif
44
#  endif
45
#endif
46

  
32 47
#include <boost/config/warning_disable.hpp>
33 48
#include <boost/spirit/include/karma.hpp>
34 49
#include <boost/spirit/include/qi_parse.hpp>
......
106 121

  
107 122
  static inline std::string double_to_s(const double d)
108 123
  {
124
#ifdef WT_CPP_LIB_TO_CHARS
125
    std::array<char, 30> buf;
126

  
127
    auto rv = std::to_chars(buf.data(), buf.data() + buf.size(), d);
128
    if (rv.ec != std::errc())
129
      throw std::invalid_argument(
130
          "double_to_s: to_chars failed for double (as to_string() '" +
131
          std::to_string(d)  + "'), err: " + std::error_condition(rv.ec).message());
132
    return std::string(buf.data(), rv.ptr - buf.data());
133
#else
109 134
    char buf[30];
110 135
    char *p = buf;
111 136
    if (d != 0) {
......
115 140
    }
116 141
    *p = '\0';
117 142
    return std::string(buf, p);
143
#endif
118 144
  }
119 145

  
120 146
  static inline std::string float_to_s(const float f)
121 147
  {
148
#ifdef WT_CPP_LIB_TO_CHARS
149
    std::array<char, 30> buf;
150

  
151
    auto rv = std::to_chars(buf.data(), buf.data() + buf.size(), f);
152
    if (rv.ec != std::errc())
153
      throw std::invalid_argument(
154
          "float_to_s: to_chars failed for float (as to_string() '" +
155
          std::to_string(f) + "'), err: " + std::error_condition(rv.ec).message());
156
    return std::string(buf.data(), rv.ptr - buf.data());
157
#else
122 158
    char buf[30];
123 159
    char *p = buf;
124 160
    if (f != 0) {
......
128 164
    }
129 165
    *p = '\0';
130 166
    return std::string(buf, p);
167
#endif
131 168
  }
132 169
}
133 170

  
......
553 590
    if (PQgetisnull(result_, row_, column))
554 591
      return false;
555 592

  
556
    *value = convert<float>("stof", boost::spirit::float_, PQgetvalue(result_, row_, column));
593
#ifdef WT_CPP_LIB_TO_CHARS
594
    std::string result_s = PQgetvalue(result_, row_, column);
595

  
596
    // try to convert with from_chars which has good round-trip properties
597
    auto rv = std::from_chars(result_s.data(), result_s.data() + result_s.size(), *value);
598

  
599
    // fall-back to boost::spirit for "out of range", e.g. subnormals in some implementations
600
    if (rv.ec == std::errc::result_out_of_range) {
601
      *value = convert<float>("stof", boost::spirit::float_, PQgetvalue(result_, row_, column));
602
    } else if (rv.ec != std::errc()) {
603
      throw PostgresException(std::string("getResult: from_chars (float) of '") +
604
          result_s + "' failed, err: " + std::error_condition(rv.ec).message());
605
    }
606
#else
607
    try {
608
      // try to convert with std::stof for backwards-compatibility and better round-trip/precision
609
      *value = std::stof(PQgetvalue(result_, row_, column));
610
    } catch (std::out_of_range) {
611
      // fall-back to boost::spirit for "out of range", e.g. subnormals in some implementations
612
      *value = convert<float>("stof", boost::spirit::float_, PQgetvalue(result_, row_, column));
613
    }
614
#endif
557 615

  
558 616
    LOG_DEBUG(this << " result float " << column << " " << *value);
559 617

  
......
565 623
    if (PQgetisnull(result_, row_, column))
566 624
      return false;
567 625

  
568
    *value = convert<double>("stod", boost::spirit::double_, PQgetvalue(result_, row_, column));
626
#ifdef WT_CPP_LIB_TO_CHARS
627
    std::string result_s = PQgetvalue(result_, row_, column);
628

  
629
    // try to convert with from_chars which has good round-trip properties
630
    auto rv = std::from_chars(result_s.data(), result_s.data() + result_s.size(), *value);
631

  
632
    // fall-back on boost::spirit for "out of range", e.g. subnormals in some implementations
633
    if (rv.ec == std::errc::result_out_of_range) {
634
      *value = convert<double>("stod", boost::spirit::double_, PQgetvalue(result_, row_, column));
635
    } else if (rv.ec != std::errc()) {
636
      throw PostgresException(std::string("getResult: from_chars (double) of '") +
637
          result_s + "' failed, err: " + std::error_condition(rv.ec).message());
638
    }
639
#else
640
    try {
641
      // try to convert with std::stod for backwards-compatibility and better round-trip/precision
642
      *value = std::stod(PQgetvalue(result_, row_, column));
643
    } catch (std::out_of_range) {
644
      // fall-back to boost::spirit for "out of range", e.g. subnormals in some implementations
645
      *value = convert<double>("stod", boost::spirit::double_, PQgetvalue(result_, row_, column));
646
    }
647
#endif
569 648

  
570 649
    LOG_DEBUG(this << " result double " << column << " " << *value);
571 650

  
572
- 
(2-2/2)