public inbox for libstdc++-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc r14-8315] libstdc++: Fix std::format floating-point alternate forms [PR113512]
@ 2024-01-21 13:35 Jonathan Wakely
  0 siblings, 0 replies; only message in thread
From: Jonathan Wakely @ 2024-01-21 13:35 UTC (permalink / raw)
  To: gcc-cvs, libstdc++-cvs

https://gcc.gnu.org/g:a57439d61937925cec48df6166b2a805ae7054d5

commit r14-8315-ga57439d61937925cec48df6166b2a805ae7054d5
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Sat Jan 20 00:44:12 2024 +0000

    libstdc++: Fix std::format floating-point alternate forms [PR113512]
    
    The logic for handling '#' forms was ... not good. The count of
    significant figures just counted digits, instead of ignoring leading
    zeros. And when moving the result from the stack buffer to a dynamic
    string the exponent could get lost in some cases.
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/113512
            * include/std/format (__formatter_fp::format): Fix logic for
            alternate forms.
            * testsuite/std/format/functions/format.cc: Check buggy cases of
            alternate forms with g presentation type.

Diff:
---
 libstdc++-v3/include/std/format                    | 51 +++++++++++++++-------
 .../testsuite/std/format/functions/format.cc       |  6 +++
 2 files changed, 41 insertions(+), 16 deletions(-)

diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index f4d91517656..0eca8b58bfa 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -1623,6 +1623,7 @@ namespace __format
 		*__p = std::toupper(*__p);
 	    }
 
+	  bool __have_sign = true;
 	  // Add sign for non-negative values.
 	  if (!__builtin_signbit(__v))
 	    {
@@ -1630,56 +1631,73 @@ namespace __format
 		*--__start = '+';
 	      else if (_M_spec._M_sign == _Sign_space)
 		*--__start = ' ';
+	      else
+		__have_sign = false;
 	    }
 
 	  string_view __narrow_str(__start, __res.ptr - __start);
 
-	  // Use alternate form.
+	  // Use alternate form. Ensure decimal point is always present,
+	  // and add trailing zeros (up to precision) for g and G forms.
 	  if (_M_spec._M_alt && __builtin_isfinite(__v))
 	    {
 	      string_view __s = __narrow_str;
-	      size_t __z = 0;
-	      size_t __p;
-	      size_t __d = __s.find('.');
-	      size_t __sigfigs;
-	      if (__d != __s.npos)
+	      size_t __sigfigs; // Number of significant figures.
+	      size_t __z = 0;   // Number of trailing zeros to add.
+	      size_t __p;       // Position of the exponent character (if any).
+	      size_t __d = __s.find('.'); // Position of decimal point.
+	      if (__d != __s.npos) // Found decimal point.
 		{
 		  __p = __s.find(__expc, __d + 1);
 		  if (__p == __s.npos)
 		    __p = __s.size();
-		  __sigfigs = __p - 1;
+
+		  // If presentation type is g or G we might need to add zeros.
+		  if (__trailing_zeros)
+		    {
+		      // Find number of digits after first significant figure.
+		      if (__s[__have_sign] != '0')
+			// A string like "D.D" or "-D.DDD"
+			__sigfigs = __p - __have_sign - 1;
+		      else
+			// A string like "0.D" or "-0.0DD".
+			// Safe to assume there is a non-zero digit, because
+			// otherwise there would be no decimal point.
+			__sigfigs = __p - __s.find_first_not_of('0', __d + 1);
+		    }
 		}
-	      else
+	      else // No decimal point, we need to insert one.
 		{
-		  __p = __s.find(__expc);
+		  __p = __s.find(__expc); // Find the exponent, if present.
 		  if (__p == __s.npos)
 		    __p = __s.size();
 		  __d = __p; // Position where '.' should be inserted.
-		  __sigfigs = __d;
+		  __sigfigs = __d - __have_sign;
 		}
 
 	      if (__trailing_zeros && __prec != 0)
 		{
-		  if (!__format::__is_xdigit(__s[0]))
-		    --__sigfigs;
-		  __z = __prec - __sigfigs; // Number of zeros to insert.
+		  // For g and G presentation types std::to_chars produces
+		  // no more than prec significant figures. Insert this many
+		  // zeros so the result has exactly prec significant figures.
+		  __z = __prec - __sigfigs;
 		}
 
-	      if (size_t __extras = int(__d == __p) + __z)
+	      if (size_t __extras = int(__d == __p) + __z) // How many to add.
 		{
 		  if (__dynbuf.empty() && __extras <= size_t(__end - __res.ptr))
 		    {
+		      // The stack buffer is large enough for the result.
 		      // Move exponent to make space for extra chars.
 		      __builtin_memmove(__start + __p + __extras,
 					__start + __p,
 					__s.size() - __p);
-
 		      if (__d == __p)
 			__start[__p++] = '.';
 		      __builtin_memset(__start + __p, '0', __z);
 		      __narrow_str = {__s.data(), __s.size() + __extras};
 		    }
-		  else
+		  else // Need to switch to the dynamic buffer.
 		    {
 		      __dynbuf.reserve(__s.size() + __extras);
 		      if (__dynbuf.empty())
@@ -1689,6 +1707,7 @@ namespace __format
 			    __dynbuf += '.';
 			  if (__z)
 			    __dynbuf.append(__z, '0');
+			  __dynbuf.append(__s.substr(__p));
 			}
 		      else
 			{
diff --git a/libstdc++-v3/testsuite/std/format/functions/format.cc b/libstdc++-v3/testsuite/std/format/functions/format.cc
index 30c5fc22237..a27fbe74631 100644
--- a/libstdc++-v3/testsuite/std/format/functions/format.cc
+++ b/libstdc++-v3/testsuite/std/format/functions/format.cc
@@ -181,6 +181,12 @@ test_alternate_forms()
   // PR libstdc++/108046
   s = std::format("{0:#.0} {0:#.1} {0:#.0g}", 10.0);
   VERIFY( s == "1.e+01 1.e+01 1.e+01" );
+
+  // PR libstdc++/113512
+  s = std::format("{:#.3g}", 0.025);
+  VERIFY( s == "0.0250" );
+  s = std::format("{:#07.3g}", 0.02);
+  VERIFY( s == "00.0200" );
 }
 
 void

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2024-01-21 13:35 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-01-21 13:35 [gcc r14-8315] libstdc++: Fix std::format floating-point alternate forms [PR113512] Jonathan Wakely

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).