On Wed, Apr 26, 2023 at 04:10:32PM +0000, Michael Matz wrote: > On Wed, 26 Apr 2023, Jakub Jelinek via Gcc-patches wrote: > > > For glibc I've gathered data from: > > 4) using attached ulp-tester.c (how to invoke in file comment; tested > > both x86_64, ppc64, ppc64le 50M pseudo-random values in all 4 rounding > > modes, plus on x86_64 float/double sin/cos using libmvec - see > > attached libmvec-wrapper.c as well) > > That ulp-tester.c file as attached here is not testing what you think it > does. (1) It doesn't compile as it doesn't #define the TESTS macro in the
Oops, (1) was from a last minute change. Initially (what was posted already back in November) it had no libmvec support, then I wanted to try to add libmvec-wrapper.so as a real wrapper which would implement sinf/sin/cosf/cos using the libmvec APIs. Unfortunately it turns out the libmvec APIs in certain corner cases call the scalar functions and then it obviously crashed due to endless recursion; I guess I could have some recursion counter and use dlsym RTLD_NEXT, but just chose to use different names and quickly adjusted the tester and only tested it with the libmvec stuff. > !LIBMVEC_TEST case, and (2) it almost never progresses 'm', the status > variable used before the random numbers start, to beyond 1: you start with > nextafter(0.0, 1.0), which is the smallest subnormal number (with a ERANGE > error, but that's ignored), and you test for equality with THIS_MIN, the > smallest normal (!) number, until you start incrementing 'm'. Oops, you're right. Obviously trying to go through all denormals exhaustively is a bad idea, attaching adjusted ulp-tester.c, which does that only for 8192 smallest denormals, then just power of two denormals until minimum normalized etc. Also, I've adjusted it such that for the non-random values it tests both positive and negative values. And, for IBM double double it now attempts to construct a valid IBM double double random value rather than sometimes invalid. With this, results are on x86_64 are still sqrtf sqrt sqrtl sqrtf128 tonearest 0 0 0 0 upward 0 0 0 0 downward 0 0 0 0 towardzero 0 0 0 0 sinf sin sinl sinf128 tonearest 1 1 1 1 upward 1 1 3 3 downward 1 1 3 3 towardzero 1 1 2 2 libmvec near 2 3-4 cosf cos cosl cosf128 tonearest 1 1 1 1 upward 1 1 3 3 downward 1 1 3 3 towardzero 1 1 2 2 libmvec near 2 3-4 On ppc64 sqrtf sqrt sqrtl tonearest 0 0 2.5 upward 0 0 2 downward 0 0 2 towardzero 0 0 3 sinf sin sinl tonearest 1 0 inf upward 3 1 170141112472035618950840819900504604672 downward 2 1 5285505939519009108193147419208187904 towardzero 1 1 170141102330830817125005607926878961664 cosf cos cosl tonearest 1 0 83814836763238928169050593715445301248 upward 2 1 83563811520779333270048610559903924224 downward 3 1 34088889209772606301573232603422523392 towardzero 2 1 34088889209772606301573232603422523392 On ppc64le sqrtf sqrt sqrtl tonearest 0 0 2.5 upward 0 0 2 downward 0 0 2 towardzero 0 0 3 sinf sin sinl tonearest 1 0 inf upward 1 1 170141112472035618950840819900504604672 downward 1 1 5285505939519009108193147419208187904 towardzero 1 1 170141102330830817125005607926878961664 cosf cos cosl tonearest 1 0 83814836763238928169050593715445301248 upward 2 1 83563811520779333270048610559903924224 downward 3 1 34088889209772606301573232603422523392 towardzero 2 1 34088889209772606301573232603422523392 I guess I'll need to look at the IBM double double sinl/cosl results, either it is some bug in my tester or the libm functions are useless. But appart from the MODE_COMPOSITE_P cases, I think all the numbers are within what the patch returns. Even the sqrtl tonearest IBM double double case is larger than the libm ulps (2.5 vs. 1). Jakub
/* gcc -g -O2 -o ulp-tester ulp-tester.c -lmpfr -lm for i in tonearest upward downward towardzero; do ( ./ulp-tester 50000000 $i > /tmp/$i 2>&1 & ); done gcc -g -O2 -DLIBMVEC_TEST -o ulp-tester2 ulp-tester.c ./libmvec-wrapper.so -lmpfr -lm for j in b c d e; do for i in tonearest upward downward towardzero; do ( LIBMVEC_LEVEL=$j ./ulp-tester2 50000000 $i > /tmp/${i}-${j} 2>&1 & ); done; done */ #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 == 1) { val = THIS_FUNC (nextafter) (val, THIS_LIT (1.0)); if (val == THIS_MIN) m = 3; else if (i == 16384) ++m; } else if (m == 2) { val = THIS_LIT (2.0) * val; if (val >= THIS_MIN) ++m; } else if (m <= 10) { val = THIS_FUNC (nextafter) (val, THIS_LIT (1.0)); ++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; volatile THIS_TYPE val2 = val; do { THIS_TYPE given = fn (val2); THIS_MPFR_SET (v, val2, mrnd); mpfr_fn (v, v, mrnd); THIS_TYPE expected = THIS_MPFR_GET (v, mrnd); if ((!__builtin_isnan (given)) != (!__builtin_isnan (expected)) || __builtin_isinf (given) != __builtin_isinf (expected)) { THIS_SNPRINTF (buf1, val2); 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; } if (m <= 45 && !__builtin_signbit (val2)) { val2 = -val; ++i; } else break; } while (1); } 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; mpfr_rnd_t mrnd; 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) { #if __LDBL_MANT_DIG__ == 106 double d1, d2; memcpy (&d1, &i, sizeof (d1)); uint64_t j = get_rand64 (); if (((j >> 52) & 0x7ff) > ((i >> 52) & 0x7ff) - 52) j = (j & 0x800fffffffffffffULL) | (((((i >> 52) & 0x7ff) - 54) + ((j >> 52) & 3)) << 52); memcpy (&d2, &j, sizeof (d2)); ld = d1; ld += d2; #else i = get_rand64 (); memcpy ((char *) &ld + 8, &i, sizeof (i)); #endif } 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]; mrnd = MPFR_RNDN; if (strcmp (rnd, "upward") == 0) { fesetround (FE_UPWARD); mrnd = MPFR_RNDU; } else if (strcmp (rnd, "downward") == 0) { fesetround (FE_DOWNWARD); mrnd = MPFR_RNDD; } else if (strcmp (rnd, "towardzero") == 0) { fesetround (FE_TOWARDZERO); mrnd = MPFR_RNDZ; } #ifdef LIBMVEC_TEST extern float wsinf (float); extern float wcosf (float); extern double wsin (double); extern double wcos (double); #define TESTS(fn) \ testf (w##fn##f, mpfr_##fn, #fn "f", count); \ test (w##fn, mpfr_##fn, #fn, count); TESTS (sin); TESTS (cos); #else #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 } #endif