Fix scanf rounding of negative floating-point numbers (bug 23280).

As reported in bug 23280, scanf functions produce incorrectly rounded
result for floating-point formats in FE_UPWARD and FE_DOWNWARD modes,
because they pass the input with sign removed to strtod functions, and
then negate the result if there was a '-' at the start of the input.

This patch fixes this by arranging for the sign to be passed to strtod
rather than scanf doing the negation itself.  In turn, keeping the
sign around in the buffer being built up for strtod requires updating
places that examine char_buffer_size (&charbuf) to allow for the sign
being there as an extra character.

Tested for x86_64.

	[BZ #23280]
	* stdio-common/vfscanf.c (_IO_vfscanf_internal): Pass sign of
	floating-point number to strtod functions rather than possibly
	negating result of those functions.
	* stdio-common/tst-scanf-round.c: New file.
	* stdio-common/Makefile (tests): Add tst-scanf-round.
	($(objpfx)tst-scanf-round): Depend on $(libm).
This commit is contained in:
Joseph Myers 2018-06-19 11:52:18 +00:00
parent 152fbfb3ad
commit 646c2833ee
4 changed files with 81 additions and 17 deletions

View File

@ -1,3 +1,13 @@
2018-06-19 Joseph Myers <joseph@codesourcery.com>
[BZ #23280]
* stdio-common/vfscanf.c (_IO_vfscanf_internal): Pass sign of
floating-point number to strtod functions rather than possibly
negating result of those functions.
* stdio-common/tst-scanf-round.c: New file.
* stdio-common/Makefile (tests): Add tst-scanf-round.
($(objpfx)tst-scanf-round): Depend on $(libm).
2018-06-18 Samuel Thibault <samuel.thibault@ens-lyon.org> 2018-06-18 Samuel Thibault <samuel.thibault@ens-lyon.org>
* sysdeps/mach/hurd/localplt.data: Move to... * sysdeps/mach/hurd/localplt.data: Move to...

View File

@ -61,6 +61,7 @@ tests := tstscanf test_rdwr test-popen tstgetln test-fseek \
tst-printf-bz18872 tst-vfprintf-width-prec tst-fmemopen4 \ tst-printf-bz18872 tst-vfprintf-width-prec tst-fmemopen4 \
tst-vfprintf-user-type \ tst-vfprintf-user-type \
tst-vfprintf-mbs-prec \ tst-vfprintf-mbs-prec \
tst-scanf-round \
test-srcs = tst-unbputc tst-printf test-srcs = tst-unbputc tst-printf
@ -158,3 +159,4 @@ $(objpfx)tst-setvbuf1-cmp.out: tst-setvbuf1.expect $(objpfx)tst-setvbuf1.out
$(evaluate-test) $(evaluate-test)
$(objpfx)tst-printf-round: $(libm) $(objpfx)tst-printf-round: $(libm)
$(objpfx)tst-scanf-round: $(libm)

View File

@ -0,0 +1,51 @@
/* Test for correct rounding of negative floating-point numbers by scanf
(bug 23280).
Copyright (C) 2018 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
<http://www.gnu.org/licenses/>. */
#include <fenv.h>
#include <stdio.h>
#include <stdlib.h>
#include <support/check.h>
static int
do_test (void)
{
#ifdef FE_DOWNWARD
if (fesetround (FE_DOWNWARD) == 0)
{
double a = strtod ("-0.1", NULL);
double b = 0;
int r = sscanf ("-0.1", "%lf", &b);
TEST_VERIFY (r == 1);
TEST_VERIFY (a == b);
}
#endif
#ifdef FE_UPWARD
if (fesetround (FE_UPWARD) == 0)
{
double a = strtod ("-0.1", NULL);
double b = 0;
int r = sscanf ("-0.1", "%lf", &b);
TEST_VERIFY (r == 1);
TEST_VERIFY (a == b);
}
#endif
return 0;
}
#include <support/test-driver.c>

View File

@ -292,7 +292,7 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr,
/* Errno of last failed inchar call. */ /* Errno of last failed inchar call. */
int inchar_errno = 0; int inchar_errno = 0;
/* Status for reading F-P nums. */ /* Status for reading F-P nums. */
char got_digit, got_dot, got_e, negative; char got_digit, got_dot, got_e, got_sign;
/* If a [...] is a [^...]. */ /* If a [...] is a [^...]. */
CHAR_T not_in; CHAR_T not_in;
#define exp_char not_in #define exp_char not_in
@ -1914,20 +1914,19 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr,
if (__glibc_unlikely (c == EOF)) if (__glibc_unlikely (c == EOF))
input_error (); input_error ();
got_digit = got_dot = got_e = 0; got_digit = got_dot = got_e = got_sign = 0;
/* Check for a sign. */ /* Check for a sign. */
if (c == L_('-') || c == L_('+')) if (c == L_('-') || c == L_('+'))
{ {
negative = c == L_('-'); got_sign = 1;
char_buffer_add (&charbuf, c);
if (__glibc_unlikely (width == 0 || inchar () == EOF)) if (__glibc_unlikely (width == 0 || inchar () == EOF))
/* EOF is only an input error before we read any chars. */ /* EOF is only an input error before we read any chars. */
conv_error (); conv_error ();
if (width > 0) if (width > 0)
--width; --width;
} }
else
negative = 0;
/* Take care for the special arguments "nan" and "inf". */ /* Take care for the special arguments "nan" and "inf". */
if (TOLOWER (c) == L_('n')) if (TOLOWER (c) == L_('n'))
@ -2176,7 +2175,7 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr,
digits with ASCII letters. */ digits with ASCII letters. */
&& !(flags & HEXA_FLOAT) && !(flags & HEXA_FLOAT)
/* Minimum requirement. */ /* Minimum requirement. */
&& (char_buffer_size (&charbuf) == 0 || got_dot) && (char_buffer_size (&charbuf) == got_sign || got_dot)
&& (map = __wctrans ("to_inpunct")) != NULL) && (map = __wctrans ("to_inpunct")) != NULL)
{ {
/* Reget the first character. */ /* Reget the first character. */
@ -2195,8 +2194,8 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr,
for localized FP numbers, then we may have localized for localized FP numbers, then we may have localized
digits. Note, we test GOT_DOT above. */ digits. Note, we test GOT_DOT above. */
#ifdef COMPILE_WSCANF #ifdef COMPILE_WSCANF
if (char_buffer_size (&charbuf) == 0 if (char_buffer_size (&charbuf) == got_sign
|| (char_buffer_size (&charbuf) == 1 || (char_buffer_size (&charbuf) == got_sign + 1
&& wcdigits[11] == decimal)) && wcdigits[11] == decimal))
#else #else
char mbdigits[12][MB_LEN_MAX + 1]; char mbdigits[12][MB_LEN_MAX + 1];
@ -2204,13 +2203,13 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr,
mbstate_t state; mbstate_t state;
memset (&state, '\0', sizeof (state)); memset (&state, '\0', sizeof (state));
bool match_so_far = char_buffer_size (&charbuf) == 0; bool match_so_far = char_buffer_size (&charbuf) == got_sign;
size_t mblen = __wcrtomb (mbdigits[11], wcdigits[11], &state); size_t mblen = __wcrtomb (mbdigits[11], wcdigits[11], &state);
if (mblen != (size_t) -1) if (mblen != (size_t) -1)
{ {
mbdigits[11][mblen] = '\0'; mbdigits[11][mblen] = '\0';
match_so_far |= match_so_far |=
(char_buffer_size (&charbuf) == strlen (decimal) (char_buffer_size (&charbuf) == strlen (decimal) + got_sign
&& strcmp (decimal, mbdigits[11]) == 0); && strcmp (decimal, mbdigits[11]) == 0);
} }
else else
@ -2220,7 +2219,8 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr,
from a file. */ from a file. */
if (decimal_len <= MB_LEN_MAX) if (decimal_len <= MB_LEN_MAX)
{ {
match_so_far |= char_buffer_size (&charbuf) == decimal_len; match_so_far |= (char_buffer_size (&charbuf)
== decimal_len + got_sign);
memcpy (mbdigits[11], decimal, decimal_len + 1); memcpy (mbdigits[11], decimal, decimal_len + 1);
} }
else else
@ -2284,7 +2284,7 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr,
if (got_e && charbuf.current[-1] == exp_char if (got_e && charbuf.current[-1] == exp_char
&& (c == L_('-') || c == L_('+'))) && (c == L_('-') || c == L_('+')))
char_buffer_add (&charbuf, c); char_buffer_add (&charbuf, c);
else if (char_buffer_size (&charbuf) > 0 && !got_e else if (char_buffer_size (&charbuf) > got_sign && !got_e
&& (CHAR_T) TOLOWER (c) == exp_char) && (CHAR_T) TOLOWER (c) == exp_char)
{ {
char_buffer_add (&charbuf, exp_char); char_buffer_add (&charbuf, exp_char);
@ -2408,9 +2408,10 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr,
/* Have we read any character? If we try to read a number /* Have we read any character? If we try to read a number
in hexadecimal notation and we have read only the `0x' in hexadecimal notation and we have read only the `0x'
prefix this is an error. */ prefix this is an error. */
if (__glibc_unlikely (char_buffer_size (&charbuf) == 0 if (__glibc_unlikely (char_buffer_size (&charbuf) == got_sign
|| ((flags & HEXA_FLOAT) || ((flags & HEXA_FLOAT)
&& char_buffer_size (&charbuf) == 2))) && (char_buffer_size (&charbuf)
== 2 + got_sign))))
conv_error (); conv_error ();
scan_float: scan_float:
@ -2427,21 +2428,21 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr,
long double d = __strtold_internal long double d = __strtold_internal
(char_buffer_start (&charbuf), &tw, flags & GROUP); (char_buffer_start (&charbuf), &tw, flags & GROUP);
if (!(flags & SUPPRESS) && tw != char_buffer_start (&charbuf)) if (!(flags & SUPPRESS) && tw != char_buffer_start (&charbuf))
*ARG (long double *) = negative ? -d : d; *ARG (long double *) = d;
} }
else if (flags & (LONG | LONGDBL)) else if (flags & (LONG | LONGDBL))
{ {
double d = __strtod_internal double d = __strtod_internal
(char_buffer_start (&charbuf), &tw, flags & GROUP); (char_buffer_start (&charbuf), &tw, flags & GROUP);
if (!(flags & SUPPRESS) && tw != char_buffer_start (&charbuf)) if (!(flags & SUPPRESS) && tw != char_buffer_start (&charbuf))
*ARG (double *) = negative ? -d : d; *ARG (double *) = d;
} }
else else
{ {
float d = __strtof_internal float d = __strtof_internal
(char_buffer_start (&charbuf), &tw, flags & GROUP); (char_buffer_start (&charbuf), &tw, flags & GROUP);
if (!(flags & SUPPRESS) && tw != char_buffer_start (&charbuf)) if (!(flags & SUPPRESS) && tw != char_buffer_start (&charbuf))
*ARG (float *) = negative ? -d : d; *ARG (float *) = d;
} }
if (__glibc_unlikely (tw == char_buffer_start (&charbuf))) if (__glibc_unlikely (tw == char_buffer_start (&charbuf)))