| /* Test for strtod handling of arguments that may cause floating-point |
| underflow. |
| Copyright (C) 2012-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 <errno.h> |
| #include <fenv.h> |
| #include <float.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <tininess.h> |
| |
| enum underflow_case |
| { |
| /* Result is exact or outside the subnormal range. */ |
| UNDERFLOW_NONE, |
| /* Result has magnitude at most half way between the largest |
| subnormal value and the smallest positive normal value, and is |
| not exact, so underflows in all rounding modes and independent |
| of how tininess is detected. */ |
| UNDERFLOW_ALWAYS, |
| /* Result is positive, with magnitude larger than half way between |
| the largest subnormal value and the least positive normal |
| value, but would underflow when rounded to nearest to normal |
| precision, so underflows after rounding in all modes except |
| rounding upward. */ |
| UNDERFLOW_EXCEPT_UPWARD, |
| /* Likewise, for a negative result, underflowing after rounding |
| except when rounding downward. */ |
| UNDERFLOW_EXCEPT_DOWNWARD, |
| /* Result is positive, with magnitude at least three quarters of |
| the way from the largest subnormal value to the smallest |
| positive normal value, so underflows after rounding only when |
| rounding downward or toward zero. */ |
| UNDERFLOW_ONLY_DOWNWARD_ZERO, |
| /* Likewise, for a negative result, underflowing after rounding |
| only when rounding upward or toward zero. */ |
| UNDERFLOW_ONLY_UPWARD_ZERO, |
| }; |
| |
| struct test |
| { |
| const char *s; |
| enum underflow_case c; |
| }; |
| |
| static const struct test tests[] = |
| { |
| { "0x1p-1022", UNDERFLOW_NONE }, |
| { "-0x1p-1022", UNDERFLOW_NONE }, |
| { "0x0p-10000000000000000000000000", UNDERFLOW_NONE }, |
| { "-0x0p-10000000000000000000000000", UNDERFLOW_NONE }, |
| { "0x1p-10000000000000000000000000", UNDERFLOW_ALWAYS }, |
| { "-0x1p-10000000000000000000000000", UNDERFLOW_ALWAYS }, |
| { "0x1.000000000000000000001p-1022", UNDERFLOW_NONE }, |
| { "-0x1.000000000000000000001p-1022", UNDERFLOW_NONE }, |
| { "0x1p-1075", UNDERFLOW_ALWAYS }, |
| { "-0x1p-1075", UNDERFLOW_ALWAYS }, |
| { "0x1p-1023", UNDERFLOW_NONE }, |
| { "-0x1p-1023", UNDERFLOW_NONE }, |
| { "0x1p-1074", UNDERFLOW_NONE }, |
| { "-0x1p-1074", UNDERFLOW_NONE }, |
| { "0x1.ffffffffffffep-1023", UNDERFLOW_NONE }, |
| { "-0x1.ffffffffffffep-1023", UNDERFLOW_NONE }, |
| { "0x1.fffffffffffffp-1023", UNDERFLOW_ALWAYS }, |
| { "-0x1.fffffffffffffp-1023", UNDERFLOW_ALWAYS }, |
| { "0x1.fffffffffffff0001p-1023", UNDERFLOW_EXCEPT_UPWARD }, |
| { "-0x1.fffffffffffff0001p-1023", UNDERFLOW_EXCEPT_DOWNWARD }, |
| { "0x1.fffffffffffff7fffp-1023", UNDERFLOW_EXCEPT_UPWARD }, |
| { "-0x1.fffffffffffff7fffp-1023", UNDERFLOW_EXCEPT_DOWNWARD }, |
| { "0x1.fffffffffffff8p-1023", UNDERFLOW_ONLY_DOWNWARD_ZERO }, |
| { "-0x1.fffffffffffff8p-1023", UNDERFLOW_ONLY_UPWARD_ZERO }, |
| { "0x1.fffffffffffffffffp-1023", UNDERFLOW_ONLY_DOWNWARD_ZERO }, |
| { "-0x1.fffffffffffffffffp-1023", UNDERFLOW_ONLY_UPWARD_ZERO }, |
| }; |
| |
| /* Return whether to expect underflow from a particular testcase, in a |
| given rounding mode. */ |
| |
| static bool |
| expect_underflow (enum underflow_case c, int rm) |
| { |
| if (c == UNDERFLOW_NONE) |
| return false; |
| if (c == UNDERFLOW_ALWAYS) |
| return true; |
| if (TININESS_AFTER_ROUNDING) |
| { |
| switch (rm) |
| { |
| #ifdef FE_DOWNWARD |
| case FE_DOWNWARD: |
| return (c == UNDERFLOW_EXCEPT_UPWARD |
| || c == UNDERFLOW_ONLY_DOWNWARD_ZERO); |
| #endif |
| |
| #ifdef FE_TOWARDZERO |
| case FE_TOWARDZERO: |
| return true; |
| #endif |
| |
| #ifdef FE_UPWARD |
| case FE_UPWARD: |
| return (c == UNDERFLOW_EXCEPT_DOWNWARD |
| || c == UNDERFLOW_ONLY_UPWARD_ZERO); |
| #endif |
| |
| default: |
| return (c == UNDERFLOW_EXCEPT_UPWARD |
| || c == UNDERFLOW_EXCEPT_DOWNWARD); |
| } |
| } |
| else |
| return true; |
| } |
| |
| static bool support_underflow_exception = false; |
| volatile double d = DBL_MIN; |
| volatile double dd; |
| |
| static int |
| test_in_one_mode (const char *s, enum underflow_case c, int rm, |
| const char *mode_name) |
| { |
| int result = 0; |
| feclearexcept (FE_ALL_EXCEPT); |
| errno = 0; |
| double d = strtod (s, NULL); |
| int got_errno = errno; |
| #ifdef FE_UNDERFLOW |
| bool got_fe_underflow = fetestexcept (FE_UNDERFLOW) != 0; |
| #else |
| bool got_fe_underflow = false; |
| #endif |
| printf ("strtod (%s) (%s) returned %a, errno = %d, %sunderflow exception\n", |
| s, mode_name, d, got_errno, got_fe_underflow ? "" : "no "); |
| bool this_expect_underflow = expect_underflow (c, rm); |
| if (got_errno != 0 && got_errno != ERANGE) |
| { |
| puts ("FAIL: errno neither 0 nor ERANGE"); |
| result = 1; |
| } |
| else if (this_expect_underflow != (errno == ERANGE)) |
| { |
| puts ("FAIL: underflow from errno differs from expectations"); |
| result = 1; |
| } |
| if (support_underflow_exception && got_fe_underflow != this_expect_underflow) |
| { |
| puts ("FAIL: underflow from exceptions differs from expectations"); |
| result = 1; |
| } |
| return result; |
| } |
| |
| static int |
| do_test (void) |
| { |
| int save_round_mode __attribute__ ((unused)) = fegetround (); |
| int result = 0; |
| #ifdef FE_TONEAREST |
| const int fe_tonearest = FE_TONEAREST; |
| #else |
| const int fe_tonearest = 0; |
| # if defined FE_DOWNWARD || defined FE_TOWARDZERO || defined FE_UPWARD |
| # error "FE_TONEAREST not defined, but another rounding mode is" |
| # endif |
| #endif |
| #ifdef FE_UNDERFLOW |
| feclearexcept (FE_ALL_EXCEPT); |
| dd = d * d; |
| if (fetestexcept (FE_UNDERFLOW)) |
| support_underflow_exception = true; |
| else |
| puts ("underflow exception not supported at runtime, only testing errno"); |
| #endif |
| for (size_t i = 0; i < sizeof (tests) / sizeof (tests[0]); i++) |
| { |
| result |= test_in_one_mode (tests[i].s, tests[i].c, fe_tonearest, |
| "default rounding mode"); |
| #ifdef FE_DOWNWARD |
| if (!fesetround (FE_DOWNWARD)) |
| { |
| result |= test_in_one_mode (tests[i].s, tests[i].c, FE_DOWNWARD, |
| "FE_DOWNWARD"); |
| fesetround (save_round_mode); |
| } |
| #endif |
| #ifdef FE_TOWARDZERO |
| if (!fesetround (FE_TOWARDZERO)) |
| { |
| result |= test_in_one_mode (tests[i].s, tests[i].c, FE_TOWARDZERO, |
| "FE_TOWARDZERO"); |
| fesetround (save_round_mode); |
| } |
| #endif |
| #ifdef FE_UPWARD |
| if (!fesetround (FE_UPWARD)) |
| { |
| result |= test_in_one_mode (tests[i].s, tests[i].c, FE_UPWARD, |
| "FE_UPWARD"); |
| fesetround (save_round_mode); |
| } |
| #endif |
| } |
| return result; |
| } |
| |
| #define TEST_FUNCTION do_test () |
| #include "../test-skeleton.c" |