about summary refs log tree commit diff
path: root/stdlib/tst-strtod-underflow.c
diff options
context:
space:
mode:
Diffstat (limited to 'stdlib/tst-strtod-underflow.c')
-rw-r--r--stdlib/tst-strtod-underflow.c225
1 files changed, 225 insertions, 0 deletions
diff --git a/stdlib/tst-strtod-underflow.c b/stdlib/tst-strtod-underflow.c
new file mode 100644
index 0000000000..892ef158ac
--- /dev/null
+++ b/stdlib/tst-strtod-underflow.c
@@ -0,0 +1,225 @@
+/* Test for strtod handling of arguments that may cause floating-point
+   underflow.
+   Copyright (C) 2012 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 = 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"