mirror of
https://sourceware.org/git/glibc.git
synced 2024-11-25 06:20:06 +00:00
0cc0033ef1
According to the specification of ISO/IEC TS 18661-1:2014, The strfromd, strfromf, and strfroml functions are equivalent to snprintf(s, n, format, fp) (7.21.6.5), except the format string contains only the character %, an optional precision that does not contain an asterisk *, and one of the conversion specifiers a, A, e, E, f, F, g, or G, which applies to the type (double, float, or long double) indicated by the function suffix (rather than by a length modifier). Use of these functions with any other 20 format string results in undefined behavior. strfromf will convert the arguement with type float to double first. According to the latest version of IEEE754 which is published in 2019, Conversion of a quiet NaN from a narrower format to a wider format in the same radix, and then back to the same narrower format, should not change the quiet NaN payload in any way except to make it canonical. When either an input or result is a NaN, this standard does not interpret the sign of a NaN. However, operations on bit strings—copy, negate, abs, copySign—specify the sign bit of a NaN result, sometimes based upon the sign bit of a NaN operand. The logical predicates totalOrder and isSignMinus are also affected by the sign bit of a NaN operand. For all other operations, this standard does not specify the sign bit of a NaN result, even when there is only one input NaN, or when the NaN is produced from an invalid operation. converting NAN or -NAN with type float to double doesn't need to keep the signbit. As a result, this test case isn't mandatory. The problem is that according to RISC-V ISA manual in chapter 11.3 of riscv-isa-20191213, Except when otherwise stated, if the result of a floating-point operation is NaN, it is the canonical NaN. The canonical NaN has a positive sign and all significand bits clear except the MSB, a.k.a. the quiet bit. For single-precision floating-point, this corresponds to the pattern 0x7fc00000. which means that conversion -NAN from float to double won't keep the signbit. Since glibc ought to be consistent here between types and architectures, this patch adds copysign to fix this problem if the string is NAN. This patch adds two different functions under sysdeps directory to work around the issue. This patch has been tested on x86_64 and riscv64. Resolves: BZ #29501 v2: Change from macros to different inline functions. v3: Add unlikely check to isnan. v4: Fix wrong commit message header. v5: Fix style: add space before parentheses. v6: Add copyright. Signed-off-by: Letu Ren <fantasquex@gmail.com> Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
157 lines
4.9 KiB
C
157 lines
4.9 KiB
C
/* Convert a floating-point number to string.
|
|
Copyright (C) 2016-2022 Free Software Foundation, Inc.
|
|
This file is part of the GNU C Library.
|
|
|
|
The GNU C Library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
The GNU C Library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with the GNU C Library; if not, see
|
|
<https://www.gnu.org/licenses/>. */
|
|
|
|
/* Generic implementation for strfrom functions. The implementation is generic
|
|
for several floating-point types (e.g.: float, double), so that each
|
|
function, such as strfromf and strfroml, share the same code, thus avoiding
|
|
code duplication. */
|
|
|
|
#include <ctype.h>
|
|
#include "../libio/libioP.h"
|
|
#include "../libio/strfile.h"
|
|
#include <printf.h>
|
|
#include <string.h>
|
|
#include <locale/localeinfo.h>
|
|
#include <fix-float-double-convert-nan.h>
|
|
|
|
#define UCHAR_T char
|
|
#define L_(Str) Str
|
|
#define ISDIGIT(Ch) isdigit (Ch)
|
|
#include "stdio-common/printf-parse.h"
|
|
|
|
int
|
|
STRFROM (char *dest, size_t size, const char *format, FLOAT f)
|
|
{
|
|
_IO_strnfile sfile;
|
|
#ifdef _IO_MTSAFE_IO
|
|
sfile.f._sbf._f._lock = NULL;
|
|
#endif
|
|
|
|
int done;
|
|
|
|
/* Single-precision values need to be stored in a double type, because
|
|
__printf_fp_l and __printf_fphex do not accept the float type. */
|
|
union {
|
|
double flt;
|
|
FLOAT value;
|
|
} fpnum;
|
|
const void *fpptr;
|
|
fpptr = &fpnum;
|
|
|
|
/* Variables to control the output format. */
|
|
int precision = -1; /* printf_fp and printf_fphex treat this internally. */
|
|
int specifier;
|
|
struct printf_info info;
|
|
|
|
/* Single-precision values need to be converted into double-precision,
|
|
because __printf_fp and __printf_fphex only accept double and long double
|
|
as the floating-point argument. */
|
|
if (__builtin_types_compatible_p (FLOAT, float))
|
|
fpnum.flt = keep_sign_conversion (f);
|
|
else
|
|
fpnum.value = f;
|
|
|
|
/* Check if the first character in the format string is indeed the '%'
|
|
character. Otherwise, abort. */
|
|
if (*format == '%')
|
|
format++;
|
|
else
|
|
abort ();
|
|
|
|
/* The optional precision specification always starts with a '.'. If such
|
|
character is present, read the precision. */
|
|
if (*format == '.')
|
|
{
|
|
format++;
|
|
|
|
/* Parse the precision. */
|
|
if (ISDIGIT (*format))
|
|
precision = read_int (&format);
|
|
/* If only the period is specified, the precision is taken as zero, as
|
|
described in ISO/IEC 9899:2011, section 7.21.6.1, 4th paragraph, 3rd
|
|
item. */
|
|
else
|
|
precision = 0;
|
|
}
|
|
|
|
/* Now there is only the conversion specifier to be read. */
|
|
switch (*format)
|
|
{
|
|
case 'a':
|
|
case 'A':
|
|
case 'e':
|
|
case 'E':
|
|
case 'f':
|
|
case 'F':
|
|
case 'g':
|
|
case 'G':
|
|
specifier = *format;
|
|
break;
|
|
default:
|
|
abort ();
|
|
}
|
|
|
|
/* The following code to prepare the virtual file has been adapted from the
|
|
function __vsnprintf_internal from libio. */
|
|
|
|
if (size == 0)
|
|
{
|
|
/* When size is zero, nothing is written and dest may be a null pointer.
|
|
This is specified for snprintf in ISO/IEC 9899:2011, Section 7.21.6.5,
|
|
in the second paragraph. Thus, if size is zero, prepare to use the
|
|
overflow buffer right from the start. */
|
|
dest = sfile.overflow_buf;
|
|
size = sizeof (sfile.overflow_buf);
|
|
}
|
|
|
|
/* Prepare the virtual string file. */
|
|
_IO_no_init (&sfile.f._sbf._f, _IO_USER_LOCK, -1, NULL, NULL);
|
|
_IO_JUMPS (&sfile.f._sbf) = &_IO_strn_jumps;
|
|
_IO_str_init_static_internal (&sfile.f, dest, size - 1, dest);
|
|
|
|
/* Prepare the format specification for printf_fp. */
|
|
memset (&info, '\0', sizeof (info));
|
|
|
|
/* The functions strfromd and strfromf pass a floating-point number with
|
|
double precision to printf_fp, whereas strfroml passes a floating-point
|
|
number with long double precision. The following line informs printf_fp
|
|
which type of floating-point number is being passed. */
|
|
info.is_long_double = __builtin_types_compatible_p (FLOAT, long double);
|
|
|
|
/* Similarly, the function strfromf128 passes a floating-point number in
|
|
_Float128 format to printf_fp. */
|
|
#if __HAVE_DISTINCT_FLOAT128
|
|
info.is_binary128 = __builtin_types_compatible_p (FLOAT, _Float128);
|
|
#endif
|
|
|
|
/* Set info according to the format string. */
|
|
info.prec = precision;
|
|
info.spec = specifier;
|
|
|
|
if (info.spec != 'a' && info.spec != 'A')
|
|
done = __printf_fp_l (&sfile.f._sbf._f, _NL_CURRENT_LOCALE, &info, &fpptr);
|
|
else
|
|
done = __printf_fphex (&sfile.f._sbf._f, &info, &fpptr);
|
|
|
|
/* Terminate the string. */
|
|
if (sfile.f._sbf._f._IO_buf_base != sfile.overflow_buf)
|
|
*sfile.f._sbf._f._IO_write_ptr = '\0';
|
|
|
|
return done;
|
|
}
|