diff options
author | Joseph Myers <joseph@codesourcery.com> | 2018-06-19 11:52:18 +0000 |
---|---|---|
committer | Joseph Myers <joseph@codesourcery.com> | 2018-06-19 11:52:18 +0000 |
commit | 646c2833ee84aa5ecc7e219f0cc6156e61c371d3 (patch) | |
tree | 1996262927420672f6acf917723309ffecfc7396 /stdio-common/vfscanf.c | |
parent | 152fbfb3ad6387051f97248747394d6798b383ba (diff) | |
download | glibc-646c2833ee84aa5ecc7e219f0cc6156e61c371d3.tar.gz glibc-646c2833ee84aa5ecc7e219f0cc6156e61c371d3.tar.xz glibc-646c2833ee84aa5ecc7e219f0cc6156e61c371d3.zip |
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).
Diffstat (limited to 'stdio-common/vfscanf.c')
-rw-r--r-- | stdio-common/vfscanf.c | 35 |
1 files changed, 18 insertions, 17 deletions
diff --git a/stdio-common/vfscanf.c b/stdio-common/vfscanf.c index 3263268c7e..1ce836a324 100644 --- a/stdio-common/vfscanf.c +++ b/stdio-common/vfscanf.c @@ -292,7 +292,7 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr, /* Errno of last failed inchar call. */ int inchar_errno = 0; /* 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 [^...]. */ CHAR_T 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)) input_error (); - got_digit = got_dot = got_e = 0; + got_digit = got_dot = got_e = got_sign = 0; /* Check for a sign. */ if (c == L_('-') || c == L_('+')) { - negative = c == L_('-'); + got_sign = 1; + char_buffer_add (&charbuf, c); if (__glibc_unlikely (width == 0 || inchar () == EOF)) /* EOF is only an input error before we read any chars. */ conv_error (); if (width > 0) --width; } - else - negative = 0; /* Take care for the special arguments "nan" and "inf". */ if (TOLOWER (c) == L_('n')) @@ -2176,7 +2175,7 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr, digits with ASCII letters. */ && !(flags & HEXA_FLOAT) /* Minimum requirement. */ - && (char_buffer_size (&charbuf) == 0 || got_dot) + && (char_buffer_size (&charbuf) == got_sign || got_dot) && (map = __wctrans ("to_inpunct")) != NULL) { /* 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 digits. Note, we test GOT_DOT above. */ #ifdef COMPILE_WSCANF - if (char_buffer_size (&charbuf) == 0 - || (char_buffer_size (&charbuf) == 1 + if (char_buffer_size (&charbuf) == got_sign + || (char_buffer_size (&charbuf) == got_sign + 1 && wcdigits[11] == decimal)) #else 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; 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); if (mblen != (size_t) -1) { mbdigits[11][mblen] = '\0'; match_so_far |= - (char_buffer_size (&charbuf) == strlen (decimal) + (char_buffer_size (&charbuf) == strlen (decimal) + got_sign && strcmp (decimal, mbdigits[11]) == 0); } else @@ -2220,7 +2219,8 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr, from a file. */ 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); } else @@ -2284,7 +2284,7 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr, if (got_e && charbuf.current[-1] == exp_char && (c == L_('-') || c == L_('+'))) 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_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 in hexadecimal notation and we have read only the `0x' 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) - && char_buffer_size (&charbuf) == 2))) + && (char_buffer_size (&charbuf) + == 2 + got_sign)))) conv_error (); scan_float: @@ -2427,21 +2428,21 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr, long double d = __strtold_internal (char_buffer_start (&charbuf), &tw, flags & GROUP); if (!(flags & SUPPRESS) && tw != char_buffer_start (&charbuf)) - *ARG (long double *) = negative ? -d : d; + *ARG (long double *) = d; } else if (flags & (LONG | LONGDBL)) { double d = __strtod_internal (char_buffer_start (&charbuf), &tw, flags & GROUP); if (!(flags & SUPPRESS) && tw != char_buffer_start (&charbuf)) - *ARG (double *) = negative ? -d : d; + *ARG (double *) = d; } else { float d = __strtof_internal (char_buffer_start (&charbuf), &tw, flags & GROUP); if (!(flags & SUPPRESS) && tw != char_buffer_start (&charbuf)) - *ARG (float *) = negative ? -d : d; + *ARG (float *) = d; } if (__glibc_unlikely (tw == char_buffer_start (&charbuf))) |