public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
From: Jakub Jelinek <jakub@redhat.com>
To: Joseph Myers <joseph@codesourcery.com>,
	Aldy Hernandez <aldyh@redhat.com>
Cc: GCC patches <gcc-patches@gcc.gnu.org>,
	Andrew MacLeod <amacleod@redhat.com>
Subject: Re: [PATCH] [range-ops] Implement sqrt.
Date: Wed, 16 Nov 2022 21:32:56 +0100	[thread overview]
Message-ID: <Y3VI+Jn294KcUInD@tucnak> (raw)
In-Reply-To: <6150f7fd-5a57-c138-f65e-8dc3bf13d11a@codesourcery.com>

[-- Attachment #1: Type: text/plain, Size: 2850 bytes --]

On Mon, Nov 14, 2022 at 09:55:29PM +0000, Joseph Myers wrote:
> On Sun, 13 Nov 2022, Jakub Jelinek via Gcc-patches wrote:
> 
> > So, I wonder if we don't need to add a target hook where targets will be
> > able to provide upper bound on error for floating point functions for
> > different floating point modes and some way to signal unknown accuracy/can't
> > be trusted, in which case we would give up or return just the range for
> > VARYING.
> 
> Note that the figures given in the glibc manual are purely empirical 
> (largest errors observed for inputs in the glibc testsuite on a system 
> that was then used to update the libm-test-ulps files); they don't 
> constitute any kind of guarantee about either the current implementation 
> or the API, nor are they formally verified, nor do they come from 
> exhaustive testing (though worst cases from exhaustive testing for float 
> may have been added to the glibc testsuite in some cases).  (I think the 
> only functions known to give huge errors for some inputs, outside of any 
> IBM long double issues, are the Bessel functions and cpow functions.  But 
> even if other functions don't have huge errors, and some 
> architecture-specific implementations might have issues, there are 
> certainly some cases where errors can exceed the 9ulp threshold on what 
> the libm tests will accept in libm-test-ulps files, which are thus 
> considered glibc bugs.  (That's 9ulp from the correctly rounded value, 
> computed in ulp of that value.  For IBM long double it's 16ulp instead, 
> treating the format as having a fixed 106 bits of precision.  Both figures 
> are empirical ones chosen based on what bounds sufficed for most libm 
> functions some years ago; ideally, with better implementations of some 
> functions we could probably bring those numbers down.))

I know I can't get guarantees without formal proofs and even ulps from
reported errors are better than randomized testing.
But I think at least for non-glibc we want to be able to get a rough idea
of the usual error range in ulps.

This is what I came up with so far (link with
gcc -o ulp-tester{,.c} -O2 -lmpfr -lm
), it still doesn't verify that functions are always within the mathematical
range of results ([-0.0, Inf] for sqrt, [-1.0, 1.0] for sin/cos etc.), guess
that would be useful and verify the program actually does what is intended.
One can supply just one argument (number of tests, first 46 aren't really
random) or two, in the latter case the second should be upward, downward or
towardzero to use non-default rounding mode.
The idea is that we'd collect ballpark estimates for roundtonearest and
then estimates for the other 3 rounding modes, the former would be used
without -frounding-math, max over all 4 rounding modes for -frounding-math
as gcc will compute using mpfr always in round to nearest.

	Jakub

[-- Attachment #2: ulp-tester.c --]
[-- Type: text/plain, Size: 6603 bytes --]

#ifdef THIS_TYPE
static THIS_TYPE
THIS_FUNC (ulp) (THIS_TYPE val)
{
  if (__builtin_isnormal (val))
    return THIS_FUNC (ldexp) (THIS_LIT (1.0), THIS_FUNC (ilogb) (val) - THIS_MANT_DIG + 1);
  else
    return THIS_FUNC (ldexp) (THIS_LIT (1.0), THIS_MIN_EXP - THIS_MANT_DIG);
}

static void
THIS_FUNC (test) (THIS_TYPE (*fn) (THIS_TYPE),
		  int (*mpfr_fn) (mpfr_ptr, mpfr_srcptr, mpfr_rnd_t),
		  const char *name,
		  unsigned long count)
{
  char buf1[256], buf2[256], buf3[256];
  mpfr_set_default_prec (THIS_MANT_DIG);
  mpfr_t v;
  mpfr_init2 (v, THIS_MANT_DIG);
  THIS_TYPE max_ulp = THIS_LIT (0.0);
  volatile THIS_TYPE val = THIS_LIT (0.0);
  int m = 0;
  for (unsigned long i = 0; i < count; ++i)
    {
      if (m == 0)
	m = 1;
      else if (m <= 10)
	{
	  val = THIS_FUNC (nextafter) (val, THIS_LIT (1.0));
	  if ((m == 1 && val == THIS_MIN) || m > 1)
	    ++m;
	}
      else if (m == 11)
	{
	  val = THIS_LIT (1.0);
	  for (int j = 0; j < 10; j++)
	    val = THIS_FUNC (nextafter) (val, THIS_LIT (0.0));
	  ++m;
	}
      else if (m <= 32)
	{
	  val = THIS_FUNC (nextafter) (val, THIS_LIT (1.0));
	  ++m;
	}
      else if (m == 33)
	{
	  val = THIS_MAX;
	  for (int j = 0; j < 10; j++)
	    val = THIS_FUNC (nextafter) (val, THIS_LIT (0.0));
	  ++m;
	}
      else if (m <= 45)
	{
	  val = THIS_FUNC (nextafter) (val, THIS_LIT (1.0));
	  ++m;
	}
      else
	val = THIS_FUNC (get_rand) ();
      if (__builtin_isnan (val))
	continue;
      THIS_TYPE given = fn (val);
      THIS_MPFR_SET (v, val, MPFR_RNDN);
      mpfr_fn (v, v, MPFR_RNDN);
      THIS_TYPE expected = THIS_MPFR_GET (v, MPFR_RNDN);
      if ((!__builtin_isnan (given)) != (!__builtin_isnan (expected))
	  || __builtin_isinf (given) != __builtin_isinf (expected))
	{
	  THIS_SNPRINTF (buf1, val);
	  THIS_SNPRINTF (buf2, given);
	  THIS_SNPRINTF (buf3, expected);
	  printf ("%s (%s) = %s rather than %s\n", name, buf1, buf2, buf3);
	}
      else if (!__builtin_isnan (given) && !__builtin_isinf (given))
	{
	  THIS_TYPE this_ulp
	    = THIS_FUNC (fabs) (given - expected) / THIS_FUNC (ulp) (expected);
	  if (this_ulp > max_ulp)
	    max_ulp = this_ulp;
	}
    }
  printf ("%s max error %.1fulp\n", name, (float) max_ulp);
}

#undef THIS_TYPE
#undef THIS_LIT
#undef THIS_FUNC
#undef THIS_MIN_EXP
#undef THIS_MANT_DIG
#undef THIS_MIN
#undef THIS_MAX
#undef THIS_MPFR_SET
#undef THIS_MPFR_GET
#undef THIS_SNPRINTF
#else
#define _GNU_SOURCE 1
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <fenv.h>

#if defined(__FLT128_DIG__) && defined(__GLIBC_PREREQ)
#if __GLIBC_PREREQ (2, 26)
#define TEST_FLT128
#define MPFR_WANT_FLOAT128
#endif
#endif

#include <gmp.h>
#include <mpfr.h>

static long rand_n;
static int rand_c;

static uint32_t
get_rand32 (void)
{
  uint32_t ret = 0;
  if (rand_c == 0)
    {
      ret = random () & 0x7fffffff;
      rand_c = 31;
    }
  else
    ret = rand_n & (((uint32_t) 1 << rand_c) - 1);
  ret <<= (32 - rand_c);
  rand_n = random ();
  ret |= rand_n & (((uint32_t) 1 << (32 - rand_c)) - 1);
  rand_n >>= (32 - rand_c);
  rand_c = 31 - (32 - rand_c);
  return ret;
}

static uint64_t
get_rand64 (void)
{
  return (((uint64_t) get_rand32 ()) << 32) | get_rand32 ();
}

static float
get_randf (void)
{
  uint32_t i = get_rand32 ();
  float f;
  memcpy (&f, &i, sizeof (f));
  return f;
}

static double
get_rand (void)
{
  uint64_t i = get_rand64 ();
  double d;
  memcpy (&d, &i, sizeof (d));
  return d;
}

static long double
get_randl (void)
{
  long double ld;
  uint64_t i = get_rand64 ();
  memcpy (&ld, &i, sizeof (i));
  if (sizeof (long double) == 12)
    {
      uint32_t j = get_rand32 ();
      memcpy ((char *) &ld + 8, &j, sizeof (j));
    }
  else if (sizeof (long double) == 16)
    {
      i = get_rand64 ();
      memcpy ((char *) &ld + 8, &i, sizeof (i));
    }
  return ld;
}

#ifdef TEST_FLT128
static long double
get_randf128 (void)
{
  _Float128 f128;
  uint64_t i = get_rand64 ();
  memcpy (&f128, &i, sizeof (i));
  i = get_rand64 ();
  memcpy ((char *) &f128 + 8, &i, sizeof (i));
  return f128;
}
#endif

#define THIS_TYPE float
#define THIS_LIT(v) v##f
#define THIS_FUNC(v) v##f
#define THIS_MIN_EXP __FLT_MIN_EXP__
#define THIS_MANT_DIG __FLT_MANT_DIG__
#define THIS_MIN __FLT_MIN__
#define THIS_MAX __FLT_MAX__
#define THIS_MPFR_SET mpfr_set_flt
#define THIS_MPFR_GET mpfr_get_flt
#define THIS_SNPRINTF(buf, x) snprintf ((buf), sizeof (buf), "%a", (x));
#include "ulp-tester.c"

#define THIS_TYPE double
#define THIS_LIT(v) v
#define THIS_FUNC(v) v
#define THIS_MIN_EXP __DBL_MIN_EXP__
#define THIS_MANT_DIG __DBL_MANT_DIG__
#define THIS_MIN __DBL_MIN__
#define THIS_MAX __DBL_MAX__
#define THIS_MPFR_SET mpfr_set_d
#define THIS_MPFR_GET mpfr_get_d
#define THIS_SNPRINTF(buf, x) snprintf ((buf), sizeof (buf), "%a", (x));
#include "ulp-tester.c"

#define THIS_TYPE long double
#define THIS_LIT(v) v##L
#define THIS_FUNC(v) v##l
#define THIS_MIN_EXP __LDBL_MIN_EXP__
#define THIS_MANT_DIG __LDBL_MANT_DIG__
#define THIS_MIN __LDBL_MIN__
#define THIS_MAX __LDBL_MAX__
#define THIS_MPFR_SET mpfr_set_ld
#define THIS_MPFR_GET mpfr_get_ld
#define THIS_SNPRINTF(buf, x) snprintf ((buf), sizeof (buf), "%La", (x));
#include "ulp-tester.c"

#ifdef TEST_FLT128
#define THIS_TYPE _Float128
#define THIS_LIT(v) v##F128
#define THIS_FUNC(v) v##f128
#define THIS_MIN_EXP __FLT128_MIN_EXP__
#define THIS_MANT_DIG __FLT128_MANT_DIG__
#define THIS_MIN __FLT128_MIN__
#define THIS_MAX __FLT128_MAX__
#define THIS_MPFR_SET mpfr_set_float128
#define THIS_MPFR_GET mpfr_get_float128
#define THIS_SNPRINTF(buf, x) strfromf128 ((buf), sizeof (buf), "%a", (x));
#include "ulp-tester.c"
#else
#define testf128(fn, mpfr_fn, name, count) do { } while (0)
#endif

int
main (int argc, const char **argv)
{
  const char *arg;
  char *endptr;
  (void) argc;
  if (argc <= 1)
    arg = "";
  else
    arg = argv[1];
  unsigned long count = strtoul (arg, &endptr, 10);
  if (endptr == arg)
    {
      fprintf (stderr, "ulp-tester number_of_iterations rnd\n");
      return 1;
    }
  const char *rnd = "tonearest";
  if (argc >= 3)
    rnd = argv[2];
  if (strcmp (rnd, "upward") == 0)
    fesetround (FE_UPWARD);
  else if (strcmp (rnd, "downward") == 0)
    fesetround (FE_DOWNWARD);
  else if (strcmp (rnd, "towardzero") == 0)
    fesetround (FE_TOWARDZERO);
#define TESTS(fn) \
  testf (fn##f, mpfr_##fn, #fn "f", count);		\
  test (fn, mpfr_##fn, #fn, count);			\
  testl (fn##l, mpfr_##fn, #fn "l", count);		\
  testf128 (fn##f128, mpfr_##fn, #fn "f128", count)
  TESTS (sqrt);
  TESTS (sin);
  TESTS (cos);
  TESTS (exp10);
}
#endif

  reply	other threads:[~2022-11-16 20:33 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-11-13 20:05 Aldy Hernandez
2022-11-13 20:39 ` Jakub Jelinek
2022-11-14  7:45   ` Aldy Hernandez
2022-11-14 14:30     ` Jeff Law
2022-11-14 14:35       ` Jakub Jelinek
2022-11-14 14:48         ` Jeff Law
2022-11-14 15:01         ` Aldy Hernandez
2022-11-14 21:55   ` Joseph Myers
2022-11-16 20:32     ` Jakub Jelinek [this message]
2022-11-17 16:40       ` Aldy Hernandez
2022-11-17 16:48         ` Aldy Hernandez
2022-11-17 17:42           ` Aldy Hernandez
2022-11-17 18:59         ` Joseph Myers
2022-11-17 19:37           ` Jakub Jelinek
2022-11-17 20:43             ` Joseph Myers
2022-11-18  8:39             ` Richard Biener
2022-11-18 10:37               ` Aldy Hernandez
2022-11-18 10:44                 ` Jakub Jelinek
2022-11-18 11:20                   ` Aldy Hernandez
2022-11-18 11:57                     ` Aldy Hernandez
2022-11-18 12:14                   ` Richard Biener

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=Y3VI+Jn294KcUInD@tucnak \
    --to=jakub@redhat.com \
    --cc=aldyh@redhat.com \
    --cc=amacleod@redhat.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=joseph@codesourcery.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).