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