簡體   English   中英

與MATLAB比較獲得三角函數的正確值

[英]Getting correct values for trigonometric functions compared with matlab

我正在嘗試使用C ++代碼測試simulink塊,該simulink塊包含一些代數,三角函數和積分器。 在我的測試過程中,從simulink塊輸入中使用了一個隨機數生成器,並將輸入和輸出都記錄到Mat文件中(使用MatIO),該文件將由C ++代碼讀取,並將輸出與C ++計算值進行比較。 對於僅包含代數函數的信號,結果是精確的,且差為零;對於包含三角函數的路徑,其差約為10e-16。 Matlab社區聲稱它們是正確的,而glibc不是。

最近,我發現glibc中實現的三角函數的輸出值不等於在matlabs中產生的值,根據舊問題1 2 3 ,我的實驗中,差異涉及1ulp> glibc的准確性。 對於大多數塊而言,這個10e-16誤差並沒有多大意義,但是在積分器的輸出中,10e-16越來越多地累積,積分器的最終誤差將約為1e-3,這有點高,並且對於此類阻止是不可接受的。

經過大量研究,我決定使用glibc中提供的其他方法來計算sin / cos函數。

我實施了這些方法

1-taylor級數,具有長雙變量和-O2(使用x87 FPU及其80位浮點算法強制使用)

2- taylor系列,帶有GNU Quadmath庫(128位精度)

3- MPFR庫(128位)

4- CRLibm(正確舍入的libm)

5- Sun的LibMCR(就像CRLibm一樣)

6- X86 FSIN / FCOS具有不同的舍入模式

通過JNI的7- Java.lang.math(我認為Matlab使用)

8- fdlibm(根據我所見的博客文章之一)

9- openlibm

10-通過mex / matlab引擎調用matlab函數

除了最后一個實驗以外,上述所有實驗均無法產生與matlab相等的值。 我測試了所有這些庫和方法的輸入范圍,其中某些庫(例如libmcr和fdlibm)將為某些輸入生成NAN值(看起來它們沒有良好的范圍檢查),其余的則生成帶有錯誤為10e-16及更高。 與最后提到的matlab相比,只有最后一個產生正確的值,但是調用matlab函數效率不高,並且比本地實現慢得多。

我也驚訝為什么長雙倍和四邊形的MPFR和taylor系列出現錯誤。

這是具有長雙變量(80位精度)的泰勒級數,應使用-O2進行編譯,以防止將FPU堆棧中的值存儲到寄存器中(80位至64位=精度損失),並且在進行任何計算之前,還將設置x87的舍入模式到最近

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;
}

使用x87的所有四種舍入模式對taylor系列方法進行了測試,最好的一種具有10e-16的誤差

這是X87 fpu之一

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;
}

x87 fpu代碼也沒有比上一個更准確

這是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;
}

我不明白為什么MPFR版本無法正常工作

我測試過的所有其他方法的代碼也相同,並且與matlab相比,所有方法都有錯誤。

所有代碼都經過了廣泛的數字測試,我發現它們失敗的簡單情況。 例如 :

在matlab中,以下代碼產生0x3fe1b071cef86fbe,但在這些方法中,我得到了0x3fe1b071cef86fbf(最后一位的差)

format hex;
sin(0.5857069572718263)
ans = 0x3fe1b071cef86fbe

要清楚地解決這個問題,正如我上面所描述的,當將這一點輸入到積分器中時,這一點的准確性很重要,我正在尋找一種解決方案,以獲取與Matlab完全相同的值。 有任何想法嗎?

更新1:

1 Ulp錯誤根本不影響算法的輸出,但是它特別是在積分器的輸出中阻止了matlab結果的驗證。

正如@John Bollinger所說,錯誤不會在具有多個算術塊的直接路徑中累積,但不會在饋入離散積分器時累積

Update2:我計算了以上所有方法的不相等結果的數量,顯然,與matlab相比,openlibm產生的不相等值更少,但不為零。

我的猜測是Matlab使用的是最初基於FDLIBM的代碼。 我可以使用Julia(使用openlibm )獲得相同的結果:您可以嘗試使用musl ,也可以嘗試使用musl ,我相信它也使用相同的代碼。

最接近的double / IEEE binary64至0.5857069572718263是

0.5857069572718263117394599248655140399932861328125

(位模式為0x3fe2be1c8450b590

sin

0.55278864311139114312806521962078480744570117018100444956428008387067067680572587 ...

與此最接近的兩個double / IEEE binary64是

一個)0.5527886431113910870038807843229733407497406005859375( 0x3fe1b071cef86fbe ),其具有0.5055誤差ULPS

b)0.55278864311139119802618324683862738311290740966796875( 0x3fe1b071cef86fbf ),其誤差為0.4945 ulps

FDLIBM僅保證正確至<1 ulp,因此任何一個都可以接受,並且碰巧返回(a)。 crlibm正確取整,並且glibc提供更嚴格的0.55 ulps 保證 ,因此兩者都將返回(b)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM