(root)/
coreutils-9.4/
lib/
ftoastr.c
       1  /* floating point to accurate string
       2  
       3     Copyright (C) 2010-2023 Free Software Foundation, Inc.
       4  
       5     This program is free software: you can redistribute it and/or modify
       6     it under the terms of the GNU General Public License as published by
       7     the Free Software Foundation, either version 3 of the License, or
       8     (at your option) any later version.
       9  
      10     This program is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13     GNU General Public License for more details.
      14  
      15     You should have received a copy of the GNU General Public License
      16     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      17  
      18  /* Written by Paul Eggert.  */
      19  
      20  /* This code can misbehave on some buggy or older platforms, when
      21     operating on arguments on floating types other than 'double', or
      22     when given unusual combinations of options.  Gnulib's
      23     snprintf-posix module works around many of these problems.
      24  
      25     This code relies on sprintf, strtod, etc. operating accurately;
      26     otherwise, the resulting strings could be inaccurate or too long.  */
      27  
      28  #include <config.h>
      29  
      30  #include "ftoastr.h"
      31  
      32  #include <float.h>
      33  #include <stdio.h>
      34  #include <stdlib.h>
      35  
      36  #ifdef C_LOCALE
      37  # include "c-snprintf.h"
      38  # include "c-strtod.h"
      39  # define PREFIX(name) c_ ## name
      40  #else
      41  # define PREFIX(name) name
      42  #endif
      43  
      44  #if LENGTH == 3
      45  # define FLOAT long double
      46  # define FLOAT_DIG LDBL_DIG
      47  # define FLOAT_MIN LDBL_MIN
      48  # define FLOAT_PREC_BOUND _GL_LDBL_PREC_BOUND
      49  # define FTOASTR PREFIX (ldtoastr)
      50  # define PROMOTED_FLOAT long double
      51  # define STRTOF PREFIX (strtold)
      52  #elif LENGTH == 2
      53  # define FLOAT double
      54  # define FLOAT_DIG DBL_DIG
      55  # define FLOAT_MIN DBL_MIN
      56  # define FLOAT_PREC_BOUND _GL_DBL_PREC_BOUND
      57  # define FTOASTR PREFIX (dtoastr)
      58  # define PROMOTED_FLOAT double
      59  #else
      60  # define LENGTH 1
      61  # define FLOAT float
      62  # define FLOAT_DIG FLT_DIG
      63  # define FLOAT_MIN FLT_MIN
      64  # define FLOAT_PREC_BOUND _GL_FLT_PREC_BOUND
      65  # define FTOASTR PREFIX (ftoastr)
      66  # define PROMOTED_FLOAT double
      67  # if HAVE_STRTOF
      68  #  define STRTOF strtof
      69  # endif
      70  #endif
      71  
      72  /* On pre-C99 hosts, approximate strtof with strtod.  This
      73     may generate one or two extra digits, but that's better than not
      74     working at all.  */
      75  #ifndef STRTOF
      76  # define STRTOF PREFIX (strtod)
      77  #endif
      78  
      79  /* On hosts where it's not known that snprintf works, use sprintf to
      80     implement the subset needed here.  Typically BUFSIZE is big enough
      81     and there's little or no performance hit.  */
      82  #ifdef C_LOCALE
      83  # undef snprintf
      84  # define snprintf c_snprintf
      85  #elif ! GNULIB_SNPRINTF
      86  # undef snprintf
      87  # define snprintf ftoastr_snprintf
      88  static int
      89  ftoastr_snprintf (char *buf, size_t bufsize, char const *format,
      90                    int width, int prec, FLOAT x)
      91  {
      92    PROMOTED_FLOAT promoted_x = x;
      93    char width_0_buffer[LENGTH == 1 ? FLT_BUFSIZE_BOUND
      94                        : LENGTH == 2 ? DBL_BUFSIZE_BOUND
      95                        : LDBL_BUFSIZE_BOUND];
      96    int n = width;
      97    if (bufsize < sizeof width_0_buffer)
      98      {
      99        n = sprintf (width_0_buffer, format, 0, prec, promoted_x);
     100        if (n < 0)
     101          return n;
     102        if (n < width)
     103          n = width;
     104      }
     105    if (n < bufsize)
     106      n = sprintf (buf, format, width, prec, promoted_x);
     107    return n;
     108  }
     109  #endif
     110  
     111  int
     112  FTOASTR (char *buf, size_t bufsize, int flags, int width, FLOAT x)
     113  {
     114    /* The following method is simple but slow.
     115       For ideas about speeding things up, please see:
     116  
     117       Andrysco M, Jhala R, Lerner S. Printing floating-point numbers:
     118       a faster, always correct method. ACM SIGPLAN notices - POPL '16.
     119       2016;51(1):555-67 <https://doi.org/10.1145/2914770.2837654>; draft at
     120       <https://cseweb.ucsd.edu/~lerner/papers/fp-printing-popl16.pdf>.  */
     121  
     122    PROMOTED_FLOAT promoted_x = x;
     123    char format[sizeof "%-+ 0*.*Lg"];
     124    FLOAT abs_x = x < 0 ? -x : x;
     125    int prec;
     126  
     127    char *p = format;
     128    *p++ = '%';
     129  
     130    /* Support flags that generate output parsable by strtof.  */
     131    *p = '-'; p += (flags & FTOASTR_LEFT_JUSTIFY  ) != 0;
     132    *p = '+'; p += (flags & FTOASTR_ALWAYS_SIGNED ) != 0;
     133    *p = ' '; p += (flags & FTOASTR_SPACE_POSITIVE) != 0;
     134    *p = '0'; p += (flags & FTOASTR_ZERO_PAD      ) != 0;
     135  
     136    *p++ = '*';
     137    *p++ = '.';
     138    *p++ = '*';
     139    *p = 'L'; p += 2 < LENGTH;
     140    *p++ = flags & FTOASTR_UPPER_E ? 'G' : 'g';
     141    *p = '\0';
     142  
     143    for (prec = abs_x < FLOAT_MIN ? 1 : FLOAT_DIG; ; prec++)
     144      {
     145        int n = snprintf (buf, bufsize, format, width, prec, promoted_x);
     146        if (n < 0
     147            || FLOAT_PREC_BOUND <= prec
     148            || (n < bufsize && STRTOF (buf, NULL) == x))
     149          return n;
     150      }
     151  }