我感到无聊并决定在不引用任何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”那将是伟大的。我相信它的大小和由它衍生的数学库证明了它的存在。
答案 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)
(请注意,代码中的c
为x+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
中使用更好的均匀近似可以用一个牛顿步骤替换哈雷步骤,或者......