泰勒展开式
ln(x+1) =x - x ^ 2 / 2 + x ^ 3 / 3 - x ^ 4 / 4 + …
(|x|<=1,x!=-1)
算法原理
ln(x+1)要求 0<x+1<=2
所以要将一般数作相应变换
ln(x)
分解 x=a*2^b
ln(x)=ln(a)+ln(2^b)=ln(a)+bln(2)
此时a满足 0<a<=2 可以使用级数计算
收敛较慢,可能要循环几百次
代码实现
static final double ln2 = 0.6931471805599453;
static final double MINVALUE = 0.0000000000000001;
public static double lnTaylor(double x) {
if(x<=0) {
return Double.NaN;
}
int pow=0;
while(x >= 1d) {
x /= 2;
pow++;
}
while(x <= 0.5d) {
x *= 2;
pow--;
}
if(Math.abs(x-1)<=MINVALUE) {
return pow*ln2;
}
x=x-1;
double y=1;
int sign=1;
double sum=0;
for(int i=1;Math.abs(y)>MINVALUE;i++) {
y=y*x;
sum +=sign*y/i;
sign=-sign;
}
return pow*ln2+sum;
}
快速收敛级数
|x|<=1,x!=-1, y = (x - 1) / (x + 1)
ln(x)= ln((1+y)/(1-y))
=2 y( 1 + y ^ 2 / 3 + y ^ 4 / 5 + y ^ 6 / 7 + …)
收敛较快,也就几十次。
代码实现
public static double lnTaylor2(double x) {
if(x<=0) {
return Double.NaN;
}
x=(x-1)/(x+1);
double x2=x*x;
double y=1;
double sum=1;
for(int i=3;y>MINVALUE;i+=2) {
y*=x2;
sum+=y/i;
}
return 2*x*sum;
}
测试
double x=10000;
System.out.println("value \t"+x);
double x1 = Math.log(x);
System.out.println("math\t"+x1);
double x2 =lnTaylor(x);
System.out.println("Taylor\t"+x2);
System.out.println( "dif1 \t"+(x1-x2));
double x3 =lnTaylor2(x);
System.out.println("Taylor2\t"+x3);
System.out.println( "dif2 \t"+(x1-x3));
输出
value 10000.0
math 9.210340371976184
Taylor 9.210340371976182
dif1 1.7763568394002505E-15
Taylor2 9.210340371974409
dif2 1.7745804825608502E-12
可见精度是相当高的,误差主要来自浮点运算。
使用对数换低公式求一般对数
loga(b)=ln(b)/ ln(a)
public static double logab(double a,double b) {
//使用快速收敛级数提高效率
return lnTaylor2(b)/lnTaylor2(a);
}
测试
System.out.println("log13(17)");
System.out.println("log \t"+Math.log(17)/Math.log(13));
System.out.println("log \t"+logab(13,17));
输出
log13(17)
log 1.1045884145097404
log 1.1045884145097404