double my_sinh(double x)
,我遇到了角案。当
液体正弦x
在709.782 ...到710.475 ...范围,
如何计算exp(x)/2
??
和余弦
cosh(x)
在exp(x)/2
(或the)时都很好地近似(或取决于x >= 20
的类型详细信息)。
common双重,最大的有限值
x
约为1.798 ...x10308.。
有限的DBL_MAX
和
x
的最积极的是大约710.475 ....
有限的最积极的sinh(x)
大约为709.782 ....
由于溢出,以下
cosh(x)
可能为时已晚,无法实现有限的结果。 Codode可以使用较宽的精度/范围,例如
x
exp(x)
不确定比exp(710.0)
/2
更好,而且我看上去只有一个仅解决方案。
long double
考虑到额外的宽long double
double
。
如何计算exp(x)/2? 要避免溢出
double
但是ex/2在#include <assert.h>
#include <math.h>
#include <stdio.h>
double my_sinh_f1(double x) {
assert(x >= 709); // Only concerned about large values for now.
return (double) (expl(x)/2); // For now, call a long double function.
}
/*
* Over a range of values:
* Compute the difference between f(x) and higher precision sinhl.
* Scale difference to the unit-in-the-last-place (ULP) of the double result.
* Return the worst case ULP.
*/
double test_sinh(double (f)(double), double x0, double x1, unsigned n) {
double error_max = 0.0;
double dx = (x1 - x0)/n;
for (double x = x0; x <= x1; x += dx) {
long double y0_as_ld = sinhl(x);
double y0 = (double) y0_as_ld;
double ulp = y0 > y0_as_ld ? y0 - nextafter(y0,0) : nextafter(y0,INFINITY) - y0;
double y1 = f(x);
double error = (double) ((y1 - y0_as_ld)/ulp);
error = fabs(error);
if (error > error_max) {
error_max = error;
}
}
return error_max;
}
int main(void) {
double (*f)(double) = my_sinh_f1;
double x0= log(DBL_MAX);
double x1 = asinh(DBL_MAX);
unsigned n = 1000000007; //1000003; // Some prime
printf("x0:%.3f, x1:%.3f n:%10u error:%g(ulp)\n", x0, x1, n, test_sinh(f, x0, x1, n));
}
范围内,请与精心选择的sinhl()
(exp(x)
)一起使用以最大程度地生成错误。使用数学身份:
e(x -v)x
/2 =e
*e
v/2
我们可以选择常数v =loge
(2),(或v == 0.693 ...)然后
ex
/2 =e(x- log(2))*2/2
double
Instead,让我们选择一个至少是logexp(x - v)*exp(v)/2
我们将取得成功的是消除
magnitude。v
,但ULP错误跳至495。 这是因为将减法中的误差乘以0x1.62e4307c58800p-1
函数的
e(2)的double my_sinh_f2(double x) {
assert(x >= 709);
static const double v = 0.69314718055994530941; // log(2)
static const double scale = 2.0/2;
return exp(x - v)*scale;
}
x0:709.783, x1:710.476 n:1000000007 error:495.895(ulp)
或long double
或x
exp(x)
。
v
x
,ULP误差已减少到1.792。 最终错误是next_power_of_2(709)/pow(2,53)
(希望很小,例如
V到a(约0.5)和乘法误差(另一个0.5)。
1024/9,007,199,254,740,992
我们可以做得更好吗?
如果扩展的精度值的扩展值是形式的
ulp = 1024.0/pow(2,53);
v = ceil(log(2)/ulp)*ulp; // 0.69314718056000401...
?
与。 EV1是2.0000000000001174 ...或以十六进制的精度为
double my_sinh_f3(double x) {
assert(x >= 709);
// Note 10 LSbits are zero -----------vvv
static const double v = 0x1.62e42fefa3c00p-1; // 0.69314718056000401...; // ceil(log(2)*pow(2,43))/pow(2,43)
static const double scale = 2.0000000000001174152150690680826/2; // exp(v)/2
return exp(x - v)*scale;
}
,但只有显着的(53个二进制数字)或
exp()
用作
double
常数,而剩余的x0:709.783, x1:710.476 n:1000000007 error:1.79199(ulp)
是形成exp(v2)
常数的误差。
我们可以选择一个偏移0x1.000000xxxxxxx000...p+1
,以便其e
v在其< 1.0) plus the conversion of e
v1 = 0.69314718056000401...
表示中的错误要小得多。 然后,用作一个0x1.0000000000108654...p+1
的值将为0x1.0000000000109p+1
,它比
double
的接近1000倍以上是
-0x0.0000000000000346...p+1
scale
的值进行测试,并且是709的ULP的倍数(因此,缩写是准确的),一个值的值是:v
导致:double
与
double
精度相比,它的零位为零:
0x1.0000000000xxxp+1
.exp(v2)/2
so,对于此0x1.0000000000109p+1
偏移,没有发生减法误差,并且使用的exp(v1
比我们先前的尝试更接近ev。
Boo-yah!log(2)
我们的1.00781(ULP)如何与库函数进行比较? 下面的报告我们几乎要好得多1.0 ULP。
v == 0.69314719694034466
图库的further测试scale == 0x1.000000465a709000p+1/2
指示大约至double
左右,其ULP非常好:0.6,然后在这个问题的范围内将ULP跳至1.920。 我怀疑我的海湾合作委员会图书馆scale == 0x1.000000465a709p+1/2
会从类似的不断变化中受益。
注:
double my_sinh_f4(double x) {
assert(x >= 709);
// Note 10 LSbits are zero -----------vvv
static const double v = 0x1.62e4307c58800p-1; // 0.69314719694034466;
static const double scale = 0x1.000000465a709000p+1/2; // exp(v)/2
return exp(x - v)*scale;
}
:v
的类似调查导致:
scale
'scale = 0x1.005a780p+0f; sinhf()
x0:709.783, x1:710.476 n:1000000007 error:1.00781(ulp).
用作64位扩展精度的参考函数。 更深入的分析将使用Proven参考功能。 compiler注释:
sinh()