diff options
author | Joseph Myers <joseph@codesourcery.com> | 2012-09-12 23:36:19 +0000 |
---|---|---|
committer | Joseph Myers <joseph@codesourcery.com> | 2012-09-12 23:36:19 +0000 |
commit | 6c9b0f68267cf365d060d4e51e7cb8f61498b875 (patch) | |
tree | 7527c31f9593972fa3781f570b1712ba79d4a556 /stdlib | |
parent | 19fcedd5fcaab4355adf62350224ce53797f0f5a (diff) | |
download | glibc-6c9b0f68267cf365d060d4e51e7cb8f61498b875.zip glibc-6c9b0f68267cf365d060d4e51e7cb8f61498b875.tar.gz glibc-6c9b0f68267cf365d060d4e51e7cb8f61498b875.tar.bz2 |
Make strtod respect the rounding mode (bug 14518).
Diffstat (limited to 'stdlib')
-rw-r--r-- | stdlib/Makefile | 1 | ||||
-rw-r--r-- | stdlib/strtod_l.c | 70 | ||||
-rw-r--r-- | stdlib/tst-strtod-round.c | 133 |
3 files changed, 152 insertions, 52 deletions
diff --git a/stdlib/Makefile b/stdlib/Makefile index dfc5eaf..c730b47 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -150,3 +150,4 @@ else link-libm = $(common-objpfx)math/libm.a endif $(objpfx)bug-getcontext: $(link-libm) +$(objpfx)tst-strtod-round: $(link-libm) diff --git a/stdlib/strtod_l.c b/stdlib/strtod_l.c index ccd117a..95f13e4 100644 --- a/stdlib/strtod_l.c +++ b/stdlib/strtod_l.c @@ -61,6 +61,7 @@ extern unsigned long long int ____strtoull_l_internal (const char *, char **, #include <stdlib.h> #include <string.h> #include <stdint.h> +#include <rounding-mode.h> /* The gmp headers need some configuration frobs. */ #define HAVE_ALLOCA 1 @@ -126,6 +127,8 @@ extern unsigned long long int ____strtoull_l_internal (const char *, char **, #define MIN_EXP PASTE(FLT,_MIN_EXP) #define MAX_10_EXP PASTE(FLT,_MAX_10_EXP) #define MIN_10_EXP PASTE(FLT,_MIN_10_EXP) +#define MAX_VALUE PASTE(FLT,_MAX) +#define MIN_VALUE PASTE(FLT,_MIN) /* Extra macros required to get FLT expanded before the pasting. */ #define PASTE(a,b) PASTE1(a,b) @@ -172,6 +175,34 @@ extern const mp_limb_t _tens_in_limb[MAX_DIG_PER_LIMB + 1]; memcpy (dst, src, (dst##size = src##size) * sizeof (mp_limb_t)) +/* Set errno and return an overflowing value with sign specified by + NEGATIVE. */ +static FLOAT +overflow_value (int negative) +{ + __set_errno (ERANGE); +#if FLT_EVAL_METHOD != 0 + volatile +#endif + FLOAT result = (negative ? -MAX_VALUE : MAX_VALUE) * MAX_VALUE; + return result; +} + + +/* Set errno and return an underflowing value with sign specified by + NEGATIVE. */ +static FLOAT +underflow_value (int negative) +{ + __set_errno (ERANGE); +#if FLT_EVAL_METHOD != 0 + volatile +#endif + FLOAT result = (negative ? -MIN_VALUE : MIN_VALUE) * MIN_VALUE; + return result; +} + + /* Return a floating point number of the needed type according to the given multi-precision number after possible rounding. */ static FLOAT @@ -181,10 +212,7 @@ round_and_return (mp_limb_t *retval, intmax_t exponent, int negative, if (exponent < MIN_EXP - 1) { if (exponent < MIN_EXP - 1 - MANT_DIG) - { - __set_errno (ERANGE); - return negative ? -0.0 : 0.0; - } + return underflow_value (negative); mp_size_t shift = MIN_EXP - 1 - exponent; @@ -237,9 +265,14 @@ round_and_return (mp_limb_t *retval, intmax_t exponent, int negative, if (exponent > MAX_EXP) goto overflow; - if ((round_limb & (((mp_limb_t) 1) << round_bit)) != 0 - && (more_bits || (retval[0] & 1) != 0 - || (round_limb & ((((mp_limb_t) 1) << round_bit) - 1)) != 0)) + int mode = get_rounding_mode (); + + if (round_away (negative, + (retval[0] & 1) != 0, + (round_limb & (((mp_limb_t) 1) << round_bit)) != 0, + (more_bits + || (round_limb & ((((mp_limb_t) 1) << round_bit) - 1)) != 0), + mode)) { mp_limb_t cy = __mpn_add_1 (retval, retval, RETURN_LIMB_SIZE, 1); @@ -263,7 +296,7 @@ round_and_return (mp_limb_t *retval, intmax_t exponent, int negative, if (exponent > MAX_EXP) overflow: - return negative ? -FLOAT_HUGE_VAL : FLOAT_HUGE_VAL; + return overflow_value (negative); return MPN2FLOAT (retval, exponent, negative); } @@ -914,9 +947,9 @@ ____STRTOF_INTERNAL (nptr, endptr, group, loc) else { /* Overflow or underflow. */ - __set_errno (ERANGE); - result = (exp_negative ? (negative ? -0.0 : 0.0) : - negative ? -FLOAT_HUGE_VAL : FLOAT_HUGE_VAL); + result = (exp_negative + ? underflow_value (negative) + : overflow_value (negative)); } /* Accept all following digits as part of the exponent. */ @@ -1112,16 +1145,10 @@ ____STRTOF_INTERNAL (nptr, endptr, group, loc) } if (__builtin_expect (exponent > MAX_10_EXP + 1 - (intmax_t) int_no, 0)) - { - __set_errno (ERANGE); - return negative ? -FLOAT_HUGE_VAL : FLOAT_HUGE_VAL; - } + return overflow_value (negative); if (__builtin_expect (exponent < MIN_10_EXP - (DIG + 1), 0)) - { - __set_errno (ERANGE); - return negative ? -0.0 : 0.0; - } + return underflow_value (negative); if (int_no > 0) { @@ -1182,10 +1209,7 @@ ____STRTOF_INTERNAL (nptr, endptr, group, loc) /* Now we know the exponent of the number in base two. Check it against the maximum possible exponent. */ if (__builtin_expect (bits > MAX_EXP, 0)) - { - __set_errno (ERANGE); - return negative ? -FLOAT_HUGE_VAL : FLOAT_HUGE_VAL; - } + return overflow_value (negative); /* We have already the first BITS bits of the result. Together with the information whether more non-zero bits follow this is enough diff --git a/stdlib/tst-strtod-round.c b/stdlib/tst-strtod-round.c index c6ad126..76385a9 100644 --- a/stdlib/tst-strtod-round.c +++ b/stdlib/tst-strtod-round.c @@ -17,6 +17,7 @@ License along with the GNU C Library; if not, see <http://www.gnu.org/licenses/>. */ +#include <fenv.h> #include <float.h> #include <math.h> #include <stdbool.h> @@ -24,21 +25,32 @@ #include <stdlib.h> #include <string.h> -struct test { - const char *s; +struct test_results { float f; double d; - bool ld_ok; long double ld; }; +struct test { + const char *s; + bool ld_ok; + struct test_results rd, rn, rz, ru; +}; + #if LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024 # define TEST(s, fd, fn, fz, fu, dd, dn, dz, du, ld53d, ld53n, ld53z, ld53u, \ ld64id, ld64in, ld64iz, ld64iu, \ ld64md, ld64mn, ld64mz, ld64mu, \ ld106exact, ld106d, ld106n, ld106z, ld106u, \ ld113d, ld113n, ld113z, ld113u) \ - { s, fn, dn, true, ld53n } + { \ + s, \ + true, \ + { fd, dd, ld53d }, \ + { fn, dn, ld53n }, \ + { fz, dz, ld53z }, \ + { fu, du, ld53u } \ + } #elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 && LDBL_MIN_EXP == -16381 /* This is for the Intel extended float format. */ # define TEST(s, fd, fn, fz, fu, dd, dn, dz, du, ld53d, ld53n, ld53z, ld53u, \ @@ -46,7 +58,14 @@ struct test { ld64md, ld64mn, ld64mz, ld64mu, \ ld106exact, ld106d, ld106n, ld106z, ld106u, \ ld113d, ld113n, ld113z, ld113u) \ - { s, fn, dn, true, ld64in } + { \ + s, \ + true, \ + { fd, dd, ld64id }, \ + { fn, dn, ld64in }, \ + { fz, dz, ld64iz }, \ + { fu, du, ld64iu } \ + } #elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 && LDBL_MIN_EXP == -16382 /* This is for the Motorola extended float format. */ # define TEST(s, fd, fn, fz, fu, dd, dn, dz, du, ld53d, ld53n, ld53z, ld53u, \ @@ -54,21 +73,42 @@ struct test { ld64md, ld64mn, ld64mz, ld64mu, \ ld106exact, ld106d, ld106n, ld106z, ld106u, \ ld113d, ld113n, ld113z, ld113u) \ - { s, fn, dn, true, ld64mn } + { \ + s, \ + true, \ + { fd, dd, ld64md }, \ + { fn, dn, ld64mn }, \ + { fz, dz, ld64mz }, \ + { fu, du, ld64mu } \ + } #elif LDBL_MANT_DIG == 106 && LDBL_MAX_EXP == 1024 # define TEST(s, fd, fn, fz, fu, dd, dn, dz, du, ld53d, ld53n, ld53z, ld53u, \ ld64id, ld64in, ld64iz, ld64iu, \ ld64md, ld64mn, ld64mz, ld64mu, \ ld106exact, ld106d, ld106n, ld106z, ld106u, \ ld113d, ld113n, ld113z, ld113u) \ - { s, fn, dn, ld106exact, ld106n } + { \ + s, \ + ld106exact, \ + { fd, dd, ld106d }, \ + { fn, dn, ld106n }, \ + { fz, dz, ld106z }, \ + { fu, du, ld106u } \ + } #elif LDBL_MANT_DIG == 113 && LDBL_MAX_EXP == 16384 # define TEST(s, fd, fn, fz, fu, dd, dn, dz, du, ld53d, ld53n, ld53z, ld53u, \ ld64id, ld64in, ld64iz, ld64iu, \ ld64md, ld64mn, ld64mz, ld64mu, \ ld106exact, ld106d, ld106n, ld106z, ld106u, \ ld113d, ld113n, ld113z, ld113u) \ - { s, fn, dn, true, ld113n } + { \ + s, \ + true, \ + { fd, dd, ld113d }, \ + { fn, dn, ld113n }, \ + { fz, dz, ld113z }, \ + { fu, du, ld113u } \ + } #else # error "unknown long double format" #endif @@ -6819,38 +6859,73 @@ static const struct test tests[] = { }; static int +test_in_one_mode (const char *s, const struct test_results *expected, + bool ld_ok, const char *mode_name) +{ + int result = 0; + float f = strtof (s, NULL); + double d = strtod (s, NULL); + long double ld = strtold (s, NULL); + if (f != expected->f + || copysignf (1.0f, f) != copysignf (1.0f, expected->f)) + { + printf ("strtof (%s) returned %a not %a (%s)\n", s, f, + expected->f, mode_name); + result = 1; + } + if (d != expected->d + || copysign (1.0, d) != copysign (1.0, expected->d)) + { + printf ("strtod (%s) returned %a not %a (%s)\n", s, d, + expected->d, mode_name); + result = 1; + } + if (ld != expected->ld + || copysignl (1.0L, ld) != copysignl (1.0L, expected->ld)) + { + printf ("strtold (%s) returned %La not %La (%s)\n", s, ld, + expected->ld, mode_name); + if (ld_ok) + result = 1; + else + printf ("ignoring this inexact long double result\n"); + } + return result; +} + +static int do_test (void) { + int save_round_mode = fegetround (); int result = 0; for (size_t i = 0; i < sizeof (tests) / sizeof (tests[0]); i++) { - float f = strtof (tests[i].s, NULL); - double d = strtod (tests[i].s, NULL); - long double ld = strtold (tests[i].s, NULL); - if (f != tests[i].f - || copysignf (1.0f, f) != copysignf (1.0f, tests[i].f)) + result |= test_in_one_mode (tests[i].s, &tests[i].rn, tests[i].ld_ok, + "default rounding mode"); +#ifdef FE_DOWNWARD + if (!fesetround (FE_DOWNWARD)) { - printf ("strtof (%s) returned %a not %a\n", tests[i].s, f, - tests[i].f); - result = 1; + result |= test_in_one_mode (tests[i].s, &tests[i].rd, tests[i].ld_ok, + "FE_DOWNWARD"); + fesetround (save_round_mode); } - if (d != tests[i].d - || copysign (1.0, d) != copysign (1.0, tests[i].d)) +#endif +#ifdef FE_TOWARDZERO + if (!fesetround (FE_TOWARDZERO)) { - printf ("strtod (%s) returned %a not %a\n", tests[i].s, d, - tests[i].d); - result = 1; + result |= test_in_one_mode (tests[i].s, &tests[i].rz, tests[i].ld_ok, + "FE_TOWARDZERO"); + fesetround (save_round_mode); } - if (ld != tests[i].ld - || copysignl (1.0L, ld) != copysignl (1.0L, tests[i].ld)) +#endif +#ifdef FE_UPWARD + if (!fesetround (FE_UPWARD)) { - printf ("strtold (%s) returned %La not %La\n", tests[i].s, ld, - tests[i].ld); - if (tests[i].ld_ok) - result = 1; - else - printf ("ignoring this inexact long double result\n"); + result |= test_in_one_mode (tests[i].s, &tests[i].ru, tests[i].ld_ok, + "FE_UPWARD"); + fesetround (save_round_mode); } +#endif } return result; } |