On 19/08/20 17:57 -0400, Patrick Palka via Libstdc++ wrote:
On Wed, 22 Jul 2020, Patrick Palka wrote:

On Mon, 20 Jul 2020, Patrick Palka wrote:

> On Mon, 20 Jul 2020, Jonathan Wakely wrote:
>
> > On 20/07/20 08:53 -0400, Patrick Palka via Libstdc++ wrote:
> > > On Mon, 20 Jul 2020, Jonathan Wakely wrote:
> > >
> > > > On 19/07/20 23:37 -0400, Patrick Palka via Libstdc++ wrote:
> > > > > On Fri, 17 Jul 2020, Patrick Palka wrote:
> > > > >
> > > > > > On Fri, 17 Jul 2020, Patrick Palka wrote:
> > > > > >
> > > > > > > On Wed, 15 Jul 2020, Patrick Palka wrote:
> > > > > > >
> > > > > > > > On Tue, 14 Jul 2020, Patrick Palka wrote:
> > > > > > > >
> > > > > > > > > This implements the floating-point std::to_chars overloads for
> > > > > > float,
> > > > > > > > > double and long double.  We use the Ryu library to compute the
> > > > > > shortest
> > > > > > > > > round-trippable fixed and scientific forms of a number for
> > > > float,
> > > > > > double
> > > > > > > > > and long double.  We also use Ryu for performing fixed and
> > > > > > scientific
> > > > > > > > > formatting of float and double. For formatting long double 
with
> > > > an
> > > > > > > > > explicit precision argument we use a printf fallback.
> > > > Hexadecimal
> > > > > > > > > formatting for float, double and long double is implemented 
from
> > > > > > > > > scratch.
> > > > > > > > >
> > > > > > > > > The supported long double binary formats are float64 (same as
> > > > > > double),
> > > > > > > > > float80 (x86 extended precision), float128 and ibm128.
> > > > > > > > >
> > > > > > > > > Much of the complexity of the implementation is in computing 
the
> > > > > > exact
> > > > > > > > > output length before handing it off to Ryu (which doesn't do
> > > > bounds
> > > > > > > > > checking).  In some cases it's hard to compute the output 
length
> > > > > > before
> > > > > > > > > the fact, so in these cases we instead compute an upper bound 
on
> > > > the
> > > > > > > > > output length and use a sufficiently-sized intermediate buffer
> > > > (if
> > > > > > the
> > > > > > > > > output range is smaller than the upper bound).
> > > > > > > > >
> > > > > > > > > Another source of complexity is in the general-with-precision
> > > > > > formatting
> > > > > > > > > mode, where we need to do zero-trimming of the string returned
> > > > by
> > > > > > Ryu, and
> > > > > > > > > where we also take care to avoid having to format the string a
> > > > > > second
> > > > > > > > > time when the general formatting mode resolves to fixed.
> > > > > > > > >
> > > > > > > > > Tested on x86_64-pc-linux-gnu, aarch64-unknown-linux-gnu,
> > > > > > > > > s390x-ibm-linux-gnu, and powerpc64-unknown-linux-gnu.
> > > > > > > > >
> > > > > > > > > libstdc++-v3/ChangeLog:
> > > > > > > > >
> > > > > > > > >    * acinclude.m4 (libtool_VERSION): Bump to 6:29:0.
> > > > > > > > >    * config/abi/pre/gnu.ver: Add new exports.
> > > > > > > > >    * configure: Regenerate.
> > > > > > > > >    * include/std/charconv (to_chars): Declare the 
floating-point
> > > > > > > > >    overloads for float, double and long double.
> > > > > > > > >    * src/c++17/Makefile.am (sources): Add 
floating_to_chars.cc.
> > > > > > > > >    * src/c++17/Makefile.in: Regenerate.
> > > > > > > > >    * src/c++17/floating_to_chars.cc: New file.
> > > > > > > > >    * testsuite/20_util/to_chars/long_double.cc: New test.
> > > > > > > > >    * testsuite/util/testsuite_abi.cc: Add new symbol version.
> > > > > > > >
> > > > > > > > Here is v2 of this patch, which fixes a build failure on i386 
due
> > > > to
> > > > > > > > __int128 being unavailable, by refactoring the long double 
binary
> > > > > > format
> > > > > > > > selection to avoid referring to __int128 when it doesn't exist.
> > > > The
> > > > > > > > patch also makes the hex formatting for 80-bit long double use
> > > > > > uint64_t
> > > > > > > > instead of __int128 since the mantissa has exactly 64 bits in 
this
> > > > > > case.
> > > > > > >
> > > > > > > Here's v3 which just makes some minor stylistic adjustments, and
> > > > most
> > > > > > > notably replaces the use of _GLIBCXX_DEBUG with 
_GLIBCXX_ASSERTIONS
> > > > > > > since we just want to enable __glibcxx_assert and not all of debug
> > > > mode.
> > > > > >
> > > > > > Here's v4, which should now correctly support using <charconv> with
> > > > > > -mlong-double-64 on targets with a large default long double type.
> > > > > > This is done by defining the long double to_chars overloads as 
inline
> > > > > > wrappers around the double overloads within <charconv> whenever
> > > > > > __DBL_MANT_DIG__ equals __LDBL_MANT_DIG__.
> > > > >
> > > > > >
> > > > > > -- >8 --
> > > > > >
> > > > > > Subject: [PATCH 3/4] libstdc++: Add floating-point std::to_chars
> > > > > >  implementation
> > > > > >
> > > > > > This implements the floating-point std::to_chars overloads for 
float,
> > > > > > double and long double.  We use the Ryu library to compute the
> > > > shortest
> > > > > > round-trippable fixed and scientific forms of a number for float,
> > > > double
> > > > > > and long double.  We also use Ryu for performing explicit-precision
> > > > > > fixed and scientific formatting of float and double. For
> > > > > > explicit-precision formatting of long double we fall back to using
> > > > > > printf.  Hexadecimal formatting for float, double and long double is
> > > > > > implemented from scratch.
> > > > > >
> > > > > > The supported long double binary formats are binary64, binary80 (x86
> > > > > > 80-bit extended precision), binary128 and ibm128.
> > > > > >
> > > > > > Much of the complexity of the implementation is in computing the 
exact
> > > > > > output length before handing it off to Ryu (which doesn't do bounds
> > > > > > checking).  In some cases it's hard to compute the output length
> > > > > > beforehand, so in these cases we instead compute an upper bound on 
the
> > > > > > output length and use a sufficiently-sized intermediate buffer if
> > > > > > necessary.
> > > > > >
> > > > > > Another source of complexity is in the general-with-precision
> > > > formatting
> > > > > > mode, where we need to do zero-trimming of the string returned by 
Ryu,
> > > > > > and where we also take care to avoid having to format the string a
> > > > > > second time when the general formatting mode resolves to fixed.
> > > > > >
> > > > > > This implementation is non-conforming in a couple of ways:
> > > > > >
> > > > > > 1. For the shortest hexadecimal formatting, we currently follow the
> > > > > >    Microsoft implementation's approach of being consistent with the
> > > > > >    output of printf's '%a' specifier at the expense of sometimes not
> > > > > >    printing the shortest representation.  For example, the shortest
> > > > hex
> > > > > >    form of 1.08p+0 is 2.1p-1, but we output the former instead of 
the
> > > > > >    latter, as does printf.
> > > > > >
> > > > > > 2. The Ryu routines for doing shortest formatting on types larger 
than
> > > > > >    binary64 use the __int128 type, and some targets (e.g. i386) 
have a
> > > > > >    large long double type but lack __int128.  For such targets we 
make
> > > > > >    the long double to_chars overloads go through the double 
overloads,
> > > > > >    which means we lose precision in the output.  (The mantissa of 
long
> > > > > >    double is 64 bits on i386, so I think we could potentially fix 
this
> > > > > >    by writing a specialized version of the generic Ryu formatting
> > > > > >    routine which works with uint64_t instead of __int128.)
> > > > > >
> > > > > > 3. The __ibm128 shortest formatting routines don't guarantee
> > > > > >    round-trippability if the difference between the high- and
> > > > low-order
> > > > > >    exponent is too large.  This is because we treat the type as if 
it
> > > > > >    has a contiguous 105-bit mantissa by merging the high- and
> > > > low-order
> > > > > >    mantissas, so we potentially lose precision from the low-order
> > > > part.
> > > > > >    Although this precision-dropping behavior is non-conforming, it
> > > > seems
> > > > > >    consistent with how printf formats __ibm128.
> > > > > >
> > > > > > libstdc++-v3/ChangeLog:
> > > > > >
> > > > > >   * acinclude.m4 (libtool_VERSION): Bump to 6:29:0.
> > > > > >   * config/abi/pre/gnu.ver: Add new exports.
> > > > > >   * configure: Regenerate.
> > > > > >   * include/std/charconv (to_chars): Declare the floating-point
> > > > > >   overloads for float, double and long double.
> > > > > >   * src/c++17/Makefile.am (sources): Add floating_to_chars.cc.
> > > > > >   * src/c++17/Makefile.in: Regenerate.
> > > > > >   * src/c++17/floating_to_chars.cc: New file.
> > > > > >   * testsuite/20_util/to_chars/long_double.cc: New test.
> > > > > >   * testsuite/util/testsuite_abi.cc: Add new symbol version.
> > > > > > ---
> > > > > >  libstdc++-v3/acinclude.m4                     |    2 +-
> > > > > >  libstdc++-v3/config/abi/pre/gnu.ver           |   12 +
> > > > > >  libstdc++-v3/configure                        |    2 +-
> > > > > >  libstdc++-v3/include/std/charconv             |   43 +
> > > > > >  libstdc++-v3/src/c++17/Makefile.am            |    1 +
> > > > > >  libstdc++-v3/src/c++17/Makefile.in            |    5 +-
> > > > > >  libstdc++-v3/src/c++17/floating_to_chars.cc   | 1514
> > > > +++++++++++++++++
> > > > > >  .../testsuite/20_util/to_chars/long_double.cc |  197 +++
> > > > > >  libstdc++-v3/testsuite/util/testsuite_abi.cc  |    3 +-
> > > > > >  9 files changed, 1774 insertions(+), 5 deletions(-)
> > > > > >  create mode 100644 libstdc++-v3/src/c++17/floating_to_chars.cc
> > > > > >  create mode 100644
> > > > libstdc++-v3/testsuite/20_util/to_chars/long_double.cc
> > > > > >
> > > > > > diff --git a/libstdc++-v3/acinclude.m4 b/libstdc++-v3/acinclude.m4
> > > > > > index ee5e0336f2c..e3926e1c9c2 100644
> > > > > > --- a/libstdc++-v3/acinclude.m4
> > > > > > +++ b/libstdc++-v3/acinclude.m4
> > > > > > @@ -3846,7 +3846,7 @@ changequote([,])dnl
> > > > > >  fi
> > > > > >
> > > > > >  # For libtool versioning info, format is CURRENT:REVISION:AGE
> > > > > > -libtool_VERSION=6:28:0
> > > > > > +libtool_VERSION=6:29:0
> > > > > >
> > > > > >  # Everything parsed; figure out what files and settings to use.
> > > > > >  case $enable_symvers in
> > > > > > diff --git a/libstdc++-v3/config/abi/pre/gnu.ver
> > > > > > b/libstdc++-v3/config/abi/pre/gnu.ver
> > > > > > index edf4485e607..9a1bcfd25d1 100644
> > > > > > --- a/libstdc++-v3/config/abi/pre/gnu.ver
> > > > > > +++ b/libstdc++-v3/config/abi/pre/gnu.ver
> > > > > > @@ -2299,6 +2299,18 @@ GLIBCXX_3.4.28 {
> > > > > >
> > > > > >  } GLIBCXX_3.4.27;
> > > > > >
> > > > > > +GLIBCXX_3.4.29 {
> > > > > > +    # to_chars(char*, char*, [float|double|long double])
> > > > > > +    _ZSt8to_charsPcS_[fdeg];
> > > > > > +
> > > > > > +    # to_chars(char*, char*, [float|double|long double],
> > > > chars_format)
> > > > > > +    _ZSt8to_charsPcS_[fdeg]St12chars_format;
> > > > > > +
> > > > > > +    # to_chars(char*, char*, [float|double|long double],
> > > > chars_format,
> > > > > > int)
> > > > > > +    _ZSt8to_charsPcS_[fdeg]St12chars_formati;
> > > > > > +
> > > > > > +} GLIBCXX_3.4.28;
> > > > > > +
> > > > > >  # Symbols in the support library (libsupc++) have their own tag.
> > > > > >  CXXABI_1.3 {
> > > > > >
> > > > > > diff --git a/libstdc++-v3/configure b/libstdc++-v3/configure
> > > > > > index dd54bd406a9..73f771e7335 100755
> > > > > > --- a/libstdc++-v3/configure
> > > > > > +++ b/libstdc++-v3/configure
> > > > > > @@ -75231,7 +75231,7 @@ $as_echo "$as_me: WARNING: === Symbol
> > > > versioning
> > > > > > will be disabled." >&2;}
> > > > > >  fi
> > > > > >
> > > > > >  # For libtool versioning info, format is CURRENT:REVISION:AGE
> > > > > > -libtool_VERSION=6:28:0
> > > > > > +libtool_VERSION=6:29:0
> > > > > >
> > > > > >  # Everything parsed; figure out what files and settings to use.
> > > > > >  case $enable_symvers in
> > > > > > diff --git a/libstdc++-v3/include/std/charconv
> > > > > > b/libstdc++-v3/include/std/charconv
> > > > > > index cc7dd0e3758..bd59924f7e7 100644
> > > > > > --- a/libstdc++-v3/include/std/charconv
> > > > > > +++ b/libstdc++-v3/include/std/charconv
> > > > > > @@ -688,6 +688,49 @@ namespace __detail
> > > > > >    operator^=(chars_format& __lhs, chars_format __rhs) noexcept
> > > > > >    { return __lhs = __lhs ^ __rhs; }
> > > > > >
> > > > > > +  // Floating-point std::to_chars
> > > > > > +
> > > > > > +  // Overloads for float.
> > > > > > +  to_chars_result to_chars(char* __first, char* __last, float
> > > > __value)
> > > > > > noexcept;
> > > > > > +  to_chars_result to_chars(char* __first, char* __last, float
> > > > __value,
> > > > > > +                    chars_format __fmt) noexcept;
> > > > > > +  to_chars_result to_chars(char* __first, char* __last, float
> > > > __value,
> > > > > > +                    chars_format __fmt, int __precision)
> > > > noexcept;
> > > > > > +
> > > > > > +  // Overloads for double.
> > > > > > +  to_chars_result to_chars(char* __first, char* __last, double
> > > > __value)
> > > > > > noexcept;
> > > > > > +  to_chars_result to_chars(char* __first, char* __last, double
> > > > __value,
> > > > > > +                    chars_format __fmt) noexcept;
> > > > > > +  to_chars_result to_chars(char* __first, char* __last, double
> > > > __value,
> > > > > > +                    chars_format __fmt, int __precision)
> > > > noexcept;
> > > > > > +
> > > > > > +  // Overloads for long double.
> > > > > > +  to_chars_result to_chars(char* __first, char* __last, long double
> > > > > > __value)
> > > > > > +    noexcept;
> > > > > > +  to_chars_result to_chars(char* __first, char* __last, long double
> > > > > > __value,
> > > > > > +                    chars_format __fmt) noexcept;
> > > > > > +  to_chars_result to_chars(char* __first, char* __last, long double
> > > > > > __value,
> > > > > > +                    chars_format __fmt, int __precision)
> > > > noexcept;
> > > > > > +
> > > > > > +  // If long double has the same binary format as double, then we
> > > > just
> > > > > > define
> > > > > > +  // the long double overloads as wrappers around the corresponding
> > > > > > double
> > > > > > +  // overloads.
> > > > > > +#if __LDBL_MANT_DIG__ == __DBL_MANT_DIG__
> > > > > > +  inline to_chars_result
> > > > > > +  to_chars(char* __first, char* __last, long double __value) 
noexcept
> > > > > > +  { return to_chars(__first, __last, double(__value)); }
> > > > > > +
> > > > > > +  inline to_chars_result
> > > > > > +  to_chars(char* __first, char* __last, long double __value,
> > > > > > +    chars_format __fmt) noexcept
> > > > > > +  { return to_chars(__first, __last, double(__value), __fmt); }
> > > > > > +
> > > > > > +  inline to_chars_result
> > > > > > +  to_chars(char* __first, char* __last, long double __value,
> > > > > > +    chars_format __fmt, int __precision) noexcept
> > > > > > +  { return to_chars(__first, __last, double(__value), __fmt,
> > > > > > __precision); }
> > > > > > +#endif
> > > > >
> > > > > Hmm, I think this approach for supporting -mlong-double-64 might
> > > > > introduce an ODR violation because each long double to_chars overload
> > > > > could potentially have two different definitions available in a 
program,
> > > > > one out-of-line in floating_to_chars.cc (compiled without
> > > > > -mlong-double-64) and another inline in <charconv> (compiled with
> > > > > -mlong-double-64)..
> > > >
> > > > But they have different mangled names, so there's no ODR violation.
> > > > The 64-bit long double is mangled as 'e' and the 128-bit long double
> > > > is mangled as __float128. You *will* get an ODR violation on targets
> > > > where there's no -mlong-double-64 switch, where double and long double
> > > > are always the same representation.
> > > >
> > > > What I'm doing for std::from_chars is adding this in the new
> > > > src/c++17/floating_from_chars.cc file:
> > > >
> > > > #ifdef _GLIBCXX_LONG_DOUBLE_COMPAT
> > > > #pragma GCC diagnostic ignored "-Wattribute-alias"
> > > > extern "C" from_chars_result
> > > > _ZSt10from_charsPKcS0_ReSt12chars_format(double)
> > > > __attribute__((alias ("_ZSt10from_charsPKcS0_RdSt12chars_format")));
> > > > #endif
> > > >
> > > > This just defines the _ZSt10from_charsPKcS0_ReSt12chars_format symbol
> > > > (i.e. from_chars for 64-bit long double) as an alias of
> > > > _ZSt10from_charsPKcS0_RdSt12chars_format (i.e. from_chars for 64-bit
> > > > double).
> > >
> > > Aha, that makes sense.  I'll follow suit for std::to_chars.
> >
> > Actually that should be:
> >
> > #ifdef _GLIBCXX_LONG_DOUBLE_COMPAT
> > extern "C" from_chars_result
> > _ZSt10from_charsPKcS0_ReSt12chars_format(const char* first, const char* 
last,
> >                                        long double& value,
> >                                        chars_format fmt) noexcept
> > __attribute__((alias ("_ZSt10from_charsPKcS0_RdSt12chars_format")));
> > #endif
> >
> > With the right parameter list I don't need to disable the warning.
>
> Sounds good.  Here's patch v5 that defines such aliases, tested so far
> on x86_64-pc-linux-gnu and on powerpc64le-unknown-linux-gnu.

Here's v6 which is rebased against the floating-point from_chars patch
and which also works around a false-positive -Wmaybe-uninitialized
warning in __floating_to_chars_hex, as well as performs some minor
commentary/style cleanups:

Here's v7, with the following changes:

* Remove extraneous indentation (two spaces when inside the std
 namespace, two spaces after a template header) for better consistency
 with floating_from_chars.cc
* Guard the calls to fe[gs]etround with a preprocessor test for
 _GLIBCXX_USE_C99_FENV_TR1;
* Properly XFAIL the new long_double.cc test on targets with a
 large long double type but without __int128, most notably i386
* Reword the commit message slightly.

-- >8 --

Subject: [PATCH] libstdc++: Add floating-point std::to_chars implementation

This implements the floating-point std::to_chars overloads for float,
double and long double.  We use the Ryu library to compute the shortest
round-trippable fixed and scientific forms for float, double and long
double.  We also use Ryu for performing explicit-precision fixed and
scientific formatting of float and double. For explicit-precision
formatting of long double we fall back to using printf.  Hexadecimal
formatting for float, double and long double is implemented from
scratch.

The supported long double binary formats are binary64, binary80 (x86
80-bit extended precision), binary128 and ibm128.

Much of the complexity of the implementation is in computing the exact
output length before handing it off to Ryu (which doesn't do bounds
checking).  In some cases it's hard to compute the output length
beforehand, so in these cases we instead compute an upper bound on the
output length and use a sufficiently-sized intermediate buffer only if
necessary.

Another source of complexity is in the general-with-precision formatting
mode, where we need to do zero-trimming of the string returned by Ryu,
and where we also take care to avoid having to format the number through
Ryu a second time when the general formatting mode resolves to fixed
(which we determine by doing a scientific formatting first and
inspecting the scientific exponent).  We avoid going through Ryu twice
by instead transforming the scientific form to the corresponding fixed
form via in-place string manipulation.

This implementation is non-conforming in a couple of ways:

1. For the shortest hexadecimal formatting, we currently follow the
  Microsoft implementation's decision to be consistent with the
  output of printf's '%a' specifier at the expense of sometimes not
  printing the shortest representation.  For example, the shortest hex
  form for the number 1.08p+0 is 2.1p-1, but we output the former
  instead of the latter, as does printf.

2. The Ryu routine generic_binary_to_decimal that we use for performing
  shortest formatting for large floating point types is implemented
  using the __int128 type, but some targets with a large long double
  type lack __int128 (e.g. i686), so we can't perform shortest
  formatting of long double on such targets through Ryu.  As a
  temporary stopgap this patch makes the long double to_chars overloads
  just dispatch to the double overloads on these targets, which means
  we lose precision in the output.  (We could potentially fix this by
  writing a specialized version of Ryu's generic_binary_to_decimal
  routine that uses uint64_t instead of __int128.)  [Though I wonder if
  there's a better way to work around the lack of __int128 on i686
  specifically?]

3. Our shortest formatting for __ibm128 doesn't guarantee the round-trip
  property if the difference between the high- and low-order exponent
  is large.  This is because we treat __ibm128 as if it has a
  contiguous 105-bit mantissa by merging the mantissas of the high-
  and low-order parts (using code extracted from glibc), so we
  potentially lose precision from the low-order part.  This seems to be
  consistent with how glibc printf formats __ibm128.

libstdc++-v3/ChangeLog:

        * config/abi/pre/gnu.ver: Add new exports.
        * include/std/charconv (to_chars): Declare the floating-point
        overloads for float, double and long double.
        * src/c++17/Makefile.am (sources): Add floating_to_chars.cc.
        * src/c++17/Makefile.in: Regenerate.
        * src/c++17/floating_to_chars.cc: New file.
        (to_chars): Define for float, double and long double.
        * testsuite/20_util/to_chars/long_double.cc: New test.

Sorry it took so long to review, this is OK for trunk.

The patch needs some minor changes to rebase it on the current trunk:
The linker script has additions since you send this patch, so the
context in the patch is wrong and it doesn't apply, and in <charconv>
the first line of context in the patch needs to have 'noexcept' added.
That rebase should be easy though.

I'll look at adding __float128 support for powerpc64le.


Reply via email to