Bug 29193 - sincos produces a different output than sin/cos
Summary: sincos produces a different output than sin/cos
Status: RESOLVED FIXED
Alias: None
Product: glibc
Classification: Unclassified
Component: math (show other bugs)
Version: 2.31
: P2 normal
Target Milestone: 2.36
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2022-05-27 16:53 UTC by Giuseppe D'Angelo
Modified: 2022-06-02 09:29 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:
fweimer: security-


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Giuseppe D'Angelo 2022-05-27 16:53:51 UTC
Hello,

Please consider this reduced testcase:

    #define _GNU_SOURCE
    #include <math.h>
    #include <stdio.h>

    double degreesToRadians(double degrees)
    {
        return degrees * (M_PI / 180);
    }

    double __attribute__ ((noinline)) a(double rad)
    {
        return sin(rad);
    }

    double __attribute__ ((noinline)) b(double rad)
    {
        double s;
        double c;
        sincos(rad, &s, &c);
        if (c > 100)
            return 0.0;
        return s;
    }

    int main() 
    {
        double d = 297;
        double rad = degreesToRadians(d);

        printf("%a\n%a\n", a(rad), b(rad));
    }


Godbolt: https://c.godbolt.org/z/z45GGWEEK

Under GCC 12.1 -O2 -fno-builtin, the output is:

-0x1.c83201d3d2c6ep-1
-0x1.c83201d3d2c6dp-1


In other words, sincos seems to be introducing ~1ULP of error when compared to calling sin/cos separately. 

This is annoying, because GCC has a specific optimization where it replaces nearby sin/cos calls with a call to sincos (slightly modified example here: https://c.godbolt.org/z/rYEhrcPcx ). However, by doing so, it actually changes the results. As sincos is not documented to be any different from sin+cos, the optimization is correct, in principle. (If, on the other hand, sincos could generate different results than sin/cos, then GCC must stop employing such optimization without -ffast-math or similar flags).
Comment 1 Andreas Schwab 2022-05-27 19:33:09 UTC
Which specific optimized sin function are you using?  With __sin_avx I cannot reproduce that.  Note that sincos always uses a non-optimized sin/cos computation.
Comment 2 Andreas Schwab 2022-05-27 20:05:13 UTC
I cannot reproduce that with __sin_sse2 either.
Comment 3 Giuseppe D'Angelo 2022-05-27 22:50:33 UTC
Hello,

I'm actually not sure; running gdb on the binary and stepping through instructions leads me to __sin_fma:

(gdb)  bt
#0  __sin_fma (x=5.1836278784231586) at ../sysdeps/ieee754/dbl-64/s_sin.c:202
#1  0x00005555555550d9 in main () at test.c:30

This is on Ubuntu 20.04 on a Broadwell CPU.
Comment 4 Giuseppe D'Angelo 2022-05-28 09:03:01 UTC
As a data point, if I run the testcase under 

GLIBC_TUNABLES=glibc.cpu.hwcaps=-AVX2_Usable 

then I get identical results:

-0x1.c83201d3d2c6dp-1
-0x1.c83201d3d2c6dp-1

With that environment variable, I end up into __sin_avx.
Comment 5 Giuseppe D'Angelo 2022-05-31 11:37:43 UTC
Hello,

In a nutshell, there are a few things crossing each other:

1) `__sin_fma` returns slightly different results than `__sin_avx` or `__sin_sse2`. I don't think that this is a problem at all, but I might be wrong, I just lack the necessary knowledge about what glibc specifically promises here.

2) `sincos` seems to be consistent with `__sin_sse2`, and therefore inconsistent with `__sin_fma`. Is this inconsistency OK? Should it be documented?

3) In general, this opens the question: within the very same "environment" (same process, CPU, libm, fpenv, etc.), is there any guarantee that `sin(x)` always  yields the very same result for identical values of `x`? Is `sin(x)` meant to be perfectly reproducible? If yes, should GCC stop turning pairs of `sin(x)` and `cos(x)` calls into one call to `sincos`?
Comment 6 Andreas Schwab 2022-06-01 08:32:30 UTC
Fixed in 2.36.
Comment 7 Giuseppe D'Angelo 2022-06-02 09:29:51 UTC
Thank you very much!