1

I am trying to test a simulink block with its c++ code, the simulink block contains some algebratic, trigonometric functions and integrators. In my test procedure, a random number generator is used form the simulink block input and both input and outpus are recorded into mat file (using MatIO) which will be read by C++ code and the outputs are compared with C++ calculate ones. for signals containing only algebraic functions the results are exact and the difference is zero, for paths which contains the trigonometric functions the difference is about 10e-16. The matlab community claim they are correct and glibc isn't.

Recently i discovered the output values of trigonometric functions implemented in glibc isn't equal to values produced in matlabs, according to old questions 1 2 3 and my experiments the differences related 1ulp> accuracy of glibc. for most of the block this 10e-16 error doesn't sense much, but in the output of the integrator the 10e-16 accumulated more and more and the final error of the integrator will be about 1e-3 which is a bit high and isn't acceptable for that kind of block.

After lot of research about the problem, i decided to use other approaches for calculating sin/cos functions than those provided in glibc.

I implemented these apporaches,

1- taylor series with long double variables and -O2 (which force using x87 FPU with its 80bit floating point arithmetic)

2- taylor series with GNU quadmath library (128bit precision)

3- MPFR library (128 bit)

4- CRLibm (correctly rounded libm)

5- Sun's LibMCR ( just like CRLibm )

6- X86 FSIN/FCOS with different rounding modes

7- Java.lang.math through JNI ( as i think matlab uses )

8- fdlibm ( according to one the blogpost i have seen )

9- openlibm

10- calling matlab function through mex/matlab engine

Non of the experiments above except last one couldn't generate values equal to matlab. i tested all of those libraries and approaches for wide range of inputs, some of them like libmcr and fdlibm will produce NAN value for some of the inputs (looks like they doesn't have good range checkings) and the rest of them producing values with error of 10e-16 and higher. Only the last one produces correct values compared to matlab as was expeceted, but calling matlab function isn't efficent and much slower than native implementations.

also i supprised why the MPFR and taylor series with long double and quadmath are getting in error.

This is taylor series with long double variables (80bit precision) and should be compiled with -O2 which prevents storing values from FPU stack into registers (80bit to 64bit = precision loss), also before doing any calculations the rounding mode of x87 will be set to nearest

typedef long double dt_double;

inline void setFPUModes(){
    unsigned int mode = 0b0000111111111111;
    asm(

    "fldcw %0;"
    :  : "m"(mode));
}
inline dt_double factorial(int x)  //calculates the factorial
{
    dt_double fact = 1;   
    for (; x >= 1 ; x--)
        fact = x * fact;
    return fact;
}

inline dt_double power(dt_double x, dt_double n) //calculates the power of x
{
    dt_double output = 1;
    while (n > 0)
    {
        output = (x * output);
        n--;
    }
    return output;
}

inline double sin(double x) noexcept  //value of sine by Taylors series
{
    setFPUModes();

    dt_double result = x;

    for (int y = 1 ; y != 44; y++)
    {
        int k = (2 * y) + 1;
        dt_double a = (y%2) ? -1.0 : 1.0;
        dt_double c = factorial(k);
        dt_double b = power(x, k);

        result = result + (a * b) / c;
    }
    return result;
}

the taylor series approach tested with all four rounding modes of x87, the best one has error of 10e-16

This is the X87 fpu one

double sin(double x) noexcept
{
    double d;
    unsigned int mode = 0b0000111111111111;
    asm(
    "finit;"
    "fldcw %2;"
    "fldl %1;"
    "fsin;"
    "fstpl %0" :
    "+m"(d) : "m"(x), "m"(mode)
      );

    return d;
}

also the x87 fpu code isn't more accurate than previous one

Here is the code for MPFR

 double sin(double x) noexcept{
    mpfr_set_default_prec(128);
    mpfr_set_default_rounding_mode(MPFR_RNDN);
    mpfr_t t;
    mpfr_init2(t, 128);
    mpfr_set_d(t, x, MPFR_RNDN);

    mpfr_t y;
    mpfr_init2(y, 128);
    mpfr_sin(y, t, MPFR_RNDN);

    double d = mpfr_get_d(y, MPFR_RNDN);

    mpfr_clear(t);
    mpfr_clear(y);

    return d;
}

I can't understand why the MPFR version didn't work as expected

also the codes for all other approaches i have tested is same and all of them have errors compared to matlab.

all the codes are tested for wide range of numbers, and i found simple cases which they fail. for example :

in matlab following code produces 0x3fe1b071cef86fbe but in these apporoches i got 0x3fe1b071cef86fbf ( difference in last bit )

format hex;
sin(0.5857069572718263)
ans = 0x3fe1b071cef86fbe

To be clear about the question, As i described above, this one bit inaccuracy is important when it fed into the integrator and i am looking for a solution to get values exactly same as the matlab one. any ideas?

Update1:

1 Ulp error doesn't affect the alogorithm output at all, but it prevents verfication with matlab results specially in the output of integrators.

As @John Bollinger said, the errors doesn't accumulate in direct path with multiple arithmetic blocks, but not when feed into the discrete integrator

Update2: I counted the number of inequal results for all of the approaches above, clearly openlibm will produce less inequal values compared to matlab ones, but it isn't zero.

e.jahandar
  • 1,715
  • 12
  • 30
  • 1
    I think you're talking about *trigonometric* functions. That's the correct generic term for sine, cosine, *etc*. in English, not "triangular" functions. – John Bollinger Sep 07 '18 at 20:53
  • 1
    did you compare the results to the precise math tables (including the mathlab ones). Who knows - maybe mathlab has the worst precision? If not your deliberations are useless. – 0___________ Sep 07 '18 at 21:13
  • I don't think there's any reasonable expectation that you can get the Matlab `sin` function to exactly match a C++ implementation, unless you have access to the Matlab source code. Occasionally being off by 1 ULP is the best you can expect. – user3386109 Sep 07 '18 at 21:25
  • @user3386109 1ulp accuracy isn't real problem, the real problem is integrator output which accumulates the 1ulp accuracy and it will be 1e-3 ! – e.jahandar Sep 07 '18 at 21:27
  • @P__J__ i didn't find such tables with that precision, do know them? – e.jahandar Sep 07 '18 at 21:28
  • And yet you said that *"calling matlab function through mex/matlab engine"* works. – user3386109 Sep 07 '18 at 21:40
  • 2
    A resolution consistent with what you have written and the links you refer to (given a cursory inspection, not yet a thorough study), is that (a) the results obtained by MPFR, CRLibm, and several of the others you refer to are correct (produce correctly rounded results or at least results closer to the correct values than Matlab) and that Matlab is wrong, and the Matlab community may be incorrect to claim Matlab’s results are correct when they differ from CRLibm. – Eric Postpischil Sep 07 '18 at 21:44
  • 1
    At these levels, it is preferable to measure errors in ULP or a hexadecimal or binary floating-pont notation, rather than “10e16.” – Eric Postpischil Sep 07 '18 at 21:45
  • I would be skeptical of “the Taylor series approach tested with all four rounding modes.” Merely setting a directed rounding mode and evaluating a Taylor series is not likely to produce an informative result, as the same directed mode for terms that are added versus others that are subtracted or for expressions versus those in denominators is likely to have undesired effects. – Eric Postpischil Sep 07 '18 at 21:48
  • 4
    (1) Don't consider the MATLAB results correct. It uses a limited precision like the other methods. (2) If a difference in the last bit affects your output, you are doing it wrong. Either change your algorithm, or use higher-precision arithmetic. – Cris Luengo Sep 07 '18 at 21:53
  • At the end, you say you are looking for a solution to replicate the Matlab values. The methods used to approximate trigonometric functions and similar functions yield customized polynomials and calculations. Although they are guided by things that are unique given certain parameters (such as the minimax polynomial—the polynomial with the smallest maximum of error from a reference function over an interval), the parameters are adjustable (*e.g.*, how do we divide a function into intervals when approximating it). This makes the different implementations individualized… – Eric Postpischil Sep 07 '18 at 21:57
  • … with the consequence that perfect replication may be possible only by exactly reproducing the specific numbers and calculations used within Matlab. – Eric Postpischil Sep 07 '18 at 21:58
  • 4
    Using your example of 0.5857069572718263, rounding to nearest IEEE-754 basic 64-bit binary floating-point, calculating the sine to 1000 digits with Maple 10, and rounding the result to 64-bit floating-point, I get the same value you got with non-Matlab approaches, +0x1.1B071CEF86FBFp-1, and not the …FBEp-1 value that Matlab gets. I believe Matlab is wrong. – Eric Postpischil Sep 07 '18 at 22:02
  • 1
    Note, too, that the accumulation of errors you describe sounds excessive for IEEE-754 Round-to-nearest, ties-to-even mode (the recommended default). You need to remember that errors in one direction are as likely as errors in the opposite direction, so errors tend to average out, especially for additive operations with operands of the same magnitude. – John Bollinger Sep 07 '18 at 22:08
  • The printed tables that I have take 23 pages with 2700 entries for angles evenly spaced from 0 to PI/4. Unfortunately, the output values have only 5 significant digits. But I expect that there *is* a published list of highly precise values for selected angles, and that an electronic version of same also exists. – user3386109 Sep 07 '18 at 23:01
  • 1
    @user3386109: Of course there is a table for “selected” values. Several. I just wrote one. Although, of course, I did not select many values. But the real issue is that P__J__’s suggestion of consulting tables is pointless. The tables would have to be generated somehow, and the software and techniques listed in the question already serve that purpose. Several of them are well-known reputable packages, and it is unlikely they all have errors while Matlab does not. *E.g.*, CRlibm was the subject of mathematical proofs. – Eric Postpischil Sep 07 '18 at 23:19
  • @EricPostpischil as you experimented and my findings the matlab maybe wrong, is there any reference available which could be used to verify matlab wrong calculations?! as you said, i am measuring the accuracy in ULP not with decimal numbers – e.jahandar Sep 08 '18 at 07:08
  • @JohnBollinger see updated question – e.jahandar Sep 08 '18 at 07:08
  • 1
    @e.jahandar: The reference would be calculating values from original mathematics, such as Taylor series with consideration for the error bounds. I generally consider reputable extended-precision mathematical software reliable. – Eric Postpischil Sep 08 '18 at 14:24
  • 1
    @e.jahandar the other likely candidate is that Matlab is using the functions provided with the Intel compiler. These are not open source, but you may qualify to use them under https://software.intel.com/en-us/qualify-for-free-software – Simon Byrne Sep 10 '18 at 18:33

1 Answers1

5

My guess is that Matlab is using code originally based on FDLIBM. I was able to get the same results with Julia (which uses openlibm): you could try using that, or musl, which I believe uses the same code as well.

The closest double/IEEE binary64 to 0.5857069572718263 is

0.5857069572718263117394599248655140399932861328125

(which has bit pattern 0x3fe2be1c8450b590)

The sin of this is

0.55278864311139114312806521962078480744570117018100444956428008387067038680572587...

The two closest double/IEEE binary64 to this are

a) 0.5527886431113910870038807843229733407497406005859375 (0x3fe1b071cef86fbe), which has error of 0.5055 ulps

b) 0.55278864311139119802618324683862738311290740966796875 (0x3fe1b071cef86fbf), which has error of 0.4945 ulps

FDLIBM is only guaranteed to be correct to <1 ulp, so either would be acceptable, and happens to return (a). crlibm is correctly rounded, and glibc provides a tighter guarantee of 0.55 ulps, so both will return (b).

Simon Byrne
  • 7,694
  • 1
  • 26
  • 50
  • It could be desirable to compare a large number of samples between Matlib and fdlibm/Julia/musl. Especially if they could also be compared to crlibm. – Eric Postpischil Sep 07 '18 at 22:51
  • Agreed, but I no longer have access to Matlab. It would be great if someone is able to try it. – Simon Byrne Sep 07 '18 at 22:53
  • 5
    Bottom line is that the Matlab `sin` function and the C++ implementations differ by a ulp, and the system that the OP is simulating compounds that into a 1e-3 difference. So it seems like the OP needs to work on the system design, rather than trying to get the simulations to match. – user3386109 Sep 07 '18 at 23:34
  • @SimonByrne could you describe your experiments more? are you using Julia or openlibm (though Julia used openlibm) – e.jahandar Sep 08 '18 at 07:15
  • @user3386109 I described the real problem in updated question. it isn't design error, the discrete integrator accumulates the error and in long running tests the difference between simulink output and C++ output gets high – e.jahandar Sep 08 '18 at 07:17
  • 2
    Are you *sure* that glibc does guaranteed correct rounding of trigonometric functions? That would surprise me greatly; guaranteed correct rounding is expensive and complicated compared to an extended precision solution that almost always gives correctly rounded results in practice. – Mark Dickinson Sep 08 '18 at 07:33
  • BTW, not that it matters much, but the `sin` value you show should end with `8057258...` rather than `8057252...`. – Mark Dickinson Sep 08 '18 at 08:31
  • @SimonByrne see updated question, i counted the number of inequal values and openlibm is clearly winner, but for about 1/1000 values the problem is still exists – e.jahandar Sep 08 '18 at 14:17
  • 1
    What is your source for the statement that “glibc and fdlibm are correctly rounded”? In [the fdlibm source for `cos`](http://www.netlib.org/fdlibm/s_cos.c), it says “TRIG(x) returns trig(x) nearly rounded”. – Eric Postpischil Sep 08 '18 at 14:21
  • @EricPostpischil you're correct: I meant crlibm, not fdlibm (I have now fixed it). glibc I thought was correctly rounded, but it looks like I'm wrong: https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/ieee754/dbl-64/s_sin.c;h=db1687edd50d1a56340422ab747f2935187f185d;hb=HEAD – Simon Byrne Sep 09 '18 at 00:18
  • @MarkDickinson You're correct: it is more accurate, but not quite correctly rounded. I have updated the answer. – Simon Byrne Sep 10 '18 at 18:22
  • 1
    @e.jahandar I used Julia (which in turn calls openlibm). – Simon Byrne Sep 10 '18 at 18:22