理解strictMath java库

时间:2016-10-27 20:30:47

标签: java math bits sqrt

我感到无聊并决定在不引用任何Math.java函数的情况下重新设计平方根函数。我已经达到了这一点:

package sqrt;
public class SquareRoot {

public static void main(String[] args) {
    System.out.println(sqrtOf(8));

}

public static double sqrtOf(double n){
    double x = log(n,2);
    return powerOf(2, x/2);
}

public static double log(double n, double base)
{
    return (Math.log(n)/Math.log(base));
}

public static double powerOf(double x, double y) {
    return powerOf(e(),y *  log(x, e()));
}   

public static int factorial(int n){
    if(n <= 1){
        return 1;
    }else{
        return n * factorial((n-1));
    }
}

public static double e(){
    return 1/factorial(1);
}
public static double e(int precision){
    return 1/factorial(precision);
}

}

正如你可能已经看到的那样,我在powerOf()函数中找到了无限回忆自己的观点。我可以替换它并使用Math.exp(y * log(x,e()),所以我潜入了Math源代码,看看它是如何处理我的问题的,导致了追逐。

public static double exp(double a) {
     return StrictMath.exp(a); // default impl. delegates to StrictMath
 }

导致:

public static double exp(double x)
{
 if (x != x)
   return x;
 if (x > EXP_LIMIT_H)
   return Double.POSITIVE_INFINITY;
 if (x < EXP_LIMIT_L)
   return 0;

 // Argument reduction.
 double hi;
 double lo;
 int k;
 double t = abs(x);
 if (t > 0.5 * LN2)
   {
     if (t < 1.5 * LN2)
       {
         hi = t - LN2_H;
         lo = LN2_L;
         k = 1;
       }
     else
       {
         k = (int) (INV_LN2 * t + 0.5);
         hi = t - k * LN2_H;
         lo = k * LN2_L;
       }
     if (x < 0)
       {
         hi = -hi;
         lo = -lo;
         k = -k;
       }
     x = hi - lo;
   }
 else if (t < 1 / TWO_28)
   return 1;
 else
   lo = hi = k = 0;

// Now x is in primary range.
 t = x * x;
 double c = x - t * (P1 + t * (P2 + t * (P3 + t * (P4 + t * P5))));
 if (k == 0)
   return 1 - (x * c / (c - 2) - x);
 double y = 1 - (lo - x * c / (2 - c) - hi);
 return scale(y, k);

}

引用的值:

 LN2 = 0.6931471805599453, // Long bits 0x3fe62e42fefa39efL.
 LN2_H = 0.6931471803691238, // Long bits 0x3fe62e42fee00000L.
 LN2_L = 1.9082149292705877e-10, // Long bits 0x3dea39ef35793c76L.
 INV_LN2 = 1.4426950408889634, // Long bits 0x3ff71547652b82feL.
 INV_LN2_H = 1.4426950216293335, // Long bits 0x3ff7154760000000L.
 INV_LN2_L = 1.9259629911266175e-8; // Long bits 0x3e54ae0bf85ddf44L.
 P1 = 0.16666666666666602, // Long bits 0x3fc555555555553eL.
 P2 = -2.7777777777015593e-3, // Long bits 0xbf66c16c16bebd93L.
 P3 = 6.613756321437934e-5, // Long bits 0x3f11566aaf25de2cL.
 P4 = -1.6533902205465252e-6, // Long bits 0xbebbbd41c5d26bf1L.
 P5 = 4.1381367970572385e-8, // Long bits 0x3e66376972bea4d0L.
 TWO_28 = 0x10000000, // Long bits 0x41b0000000000000L

这是我开始迷路的地方。但我可以做一些假设,到目前为止,答案开始被估计。然后我发现自己在这里:

private static double scale(double x, int n)
{
  if (Configuration.DEBUG && abs(n) >= 2048)
    throw new InternalError("Assertion failure");
  if (x == 0 || x == Double.NEGATIVE_INFINITY
      || ! (x < Double.POSITIVE_INFINITY) || n == 0)
    return x;
  long bits = Double.doubleToLongBits(x);
  int exp = (int) (bits >> 52) & 0x7ff;
  if (exp == 0) // Subnormal x.
    {
      x *= TWO_54;
      exp = ((int) (Double.doubleToLongBits(x) >> 52) & 0x7ff) - 54;
    }
  exp += n;
  if (exp > 0x7fe) // Overflow.
    return Double.POSITIVE_INFINITY * x;
  if (exp > 0) // Normal.
    return Double.longBitsToDouble((bits & 0x800fffffffffffffL)
                                   | ((long) exp << 52));
  if (exp <= -54)
    return 0 * x; // Underflow.
  exp += 54; // Subnormal result.
  x = Double.longBitsToDouble((bits & 0x800fffffffffffffL)
                              | ((long) exp << 52));
  return x * (1 / TWO_54);
}


 TWO_54 = 0x40000000000000L

虽然我是,我会说,非常理解数学和编程,但我发现自己处于两者的弗兰肯斯坦怪物组合中。我注意到了对比特的内在切换(我几乎没有经验),我希望有人可以向我解释“引擎盖下”正在发生的过程。特别是我丢失的地方是来自病房的exp()方法中的“现在x在主要范围内”以及被引用的值真正代表的是什么。我要求有人帮助我不仅了解方法本身,还要了解他们如何得出答案。随意根据需要进行深入研究。

编辑: 如果有人可以制作这个标签:“strictMath”那将是伟大的。我相信它的大小和由它衍生的数学库证明了它的存在。

1 个答案:

答案 0 :(得分:3)

到指数函数:

会发生什么

exp(x) = 2^k * exp(x-k*log(2))

被用于肯定x。使用一些法术来为大x获得更一致的结果,其中减少x-k*log(2)会引入取消错误。

在简化x上,使用了区间0.5..1.5上最小化最小误差的有理逼近,请参阅Pade近似等。这是基于对称公式

exp(x) = exp(x/2)/exp(-x/2) = (c(x²)+x)/(c(x²)-x)

(请注意,代码中的cx+c(x)-2)。使用泰勒级数时,c(x*x)=x*coth(x/2)的近似值基于

c(u)=2 + 1/6*u - 1/360*u^2 + 1/15120*u^3 - 1/604800*u^4 + 1/23950080*u^5 - 691/653837184000*u^6

scale(x,n)函数通过直接操作x*2^n浮点格式的位汇编中的指数来实现乘法double

计算平方根

要计算平方根,直接计算它们会更有利。首先通过

减少近似参数的间隔
sqrt(x)=2^k*sqrt(x/4^k)

可以通过直接操作double的位格式再次有效地完成。

x缩小到0.5..2.0间隔之后,可以使用

形式的公式
u = (x-1)/(x+1)

y = (c(u*u)+u) / (c(u*u)-u)

基于

sqrt(x)=sqrt(1+u)/sqrt(1-u)

c(v) = 1+sqrt(1-v) = 2 - 1/2*v - 1/8*v^2 - 1/16*v^3 - 5/128*v^4 - 7/256*v^5 - 21/1024*v^6 - 33/2048*v^7 - ...

在没有位操作的程序中,这看起来像

double my_sqrt(double x) {
    double c,u,v,y,scale=1;
    int k=0;
    if(x<0) return NaN;
    while(x>2  ) { x/=4; scale *=2; k++; }
    while(x<0.5) { x*=4; scale /=2; k--; }
    // rational approximation of sqrt
    u = (x-1)/(x+1); 
    v = u*u;
    c = 2 - v/2*(1 + v/4*(1 + v/2));
    y = 1 + 2*u/(c-u); // = (c+u)/(c-u);
    // one Halley iteration
    y = y*(1+8*x/(3*(3*y*y+x))) // = y*(y*y+3*x)/(3*y*y+x)
    // reconstruct original scale
    return y*scale;
}

可以用两个牛顿步骤替换哈雷步骤,或者 在c中使用更好的均匀近似可以用一个牛顿步骤替换哈雷步骤,或者......