[英]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.