Java 为什么hypot()函数这么慢?

Java 为什么hypot()函数这么慢?,java,c++,hypotenuse,Java,C++,Hypotenuse,我用C++hypot()和JavaMath.hypot做了一些测试。它们似乎都比sqrt(a*a+b*b)慢得多。这是因为精度更高吗?计算斜边斜边函数使用的方法是什么?令人惊讶的是,我在文档中找不到任何性能差的迹象。这不是一个简单的sqrt函数。您应该检查此链接以了解算法的实现: 它有while循环来检查收敛性 /* Slower but safer algorithm due to Moler and Morrison. Never produces any interme

我用
C++
hypot()
Java
Math.hypot
做了一些测试。它们似乎都比
sqrt(a*a+b*b)
慢得多。这是因为精度更高吗?计算斜边
斜边
函数使用的方法是什么?令人惊讶的是,我在文档中找不到任何性能差的迹象。

这不是一个简单的sqrt函数。您应该检查此链接以了解算法的实现:

它有while循环来检查收敛性

/* Slower but safer algorithm due to Moler and Morrison.  Never
         produces any intermediate result greater than roughly the
         larger of X and Y.  Should converge to machine-precision
         accuracy in 3 iterations.  */

      double r = ratio*ratio, t, s, p = abig, q = asmall;

      do {
        t = 4. + r;
        if (t == 4.)
          break;
        s = r / t;
        p += 2. * s * p;
        q *= s;
        r = (q / p) * (q / p);
      } while (1);
编辑(从J.M更新):


这是Moler-Morrison的原版论文,由于Dubrule,这是一篇不错的后续文章。

这里是一个更快的实现,其结果也更接近java.lang.Math.Hypto: (注意:对于Delorie的实现,需要添加NaN和+-无限输入的处理)

private static final double TWO-TWO-POW_450=double.longBitsToDouble(0x5c100000000000l);
私有静态final double TWO_POW_N450=double.longBitsToDouble(0x23d000000000000l);
专用静态最终double TWO_POW_750=double.longBitsToDouble(0x6ED0000000000000L);
专用静态最终double TWO_POW_N750=double.longBitsToDouble(0x1110000000000000L);
公共静态双降压(双x,双y){
x=Math.abs(x);
y=Math.abs(y);
if(y=x)){//测试我们是否有一些NaN。
if((x==双正无穷大)| |(y==双正无穷大)){
返回双正无穷大;
}否则{
返回Double.NaN;
}
}
如果(y-x==y){//x太小,无法从y中减去
返回y;
}否则{
双重因素;
如果(x>TWO_POW_450){//2^450

表明对于Moler&Morrison,使用sqrt()的更快实现是二次实现而不是三次实现,具有大致相同的溢出特性。

我发现Math.hypot()的速度非常慢。我发现我可以使用相同的算法编写一个快速的java版本,产生相同的结果。这可以用来代替它

/**
 * <b>hypot</b>
 * @param x
 * @param y
 * @return sqrt(x*x +y*y) without intermediate overflow or underflow. 
 * @Note {@link Math#hypot} is unnecessarily slow.  This returns the identical result to 
 * Math.hypot with reasonable run times (~40 nsec vs. 800 nsec). 
 * <p>The logic for computing z is copied from "Freely Distributable Math Library" 
 * fdlibm's e_hypot.c. This minimizes rounding error to provide 1 ulb accuracy.
 */
public static double hypot(double x, double y) {

    if (Double.isInfinite(x) || Double.isInfinite(y)) return Double.POSITIVE_INFINITY;
    if (Double.isNaN(x) || Double.isNaN(y)) return Double.NaN;

    x = Math.abs(x);
    y = Math.abs(y);

    if (x < y) {
        double d = x;
        x = y;
        y = d;
    }

    int xi = Math.getExponent(x);
    int yi = Math.getExponent(y);

    if (xi > yi + 27) return x;

    int bias = 0;
    if (xi > 510 || xi < -511) {
        bias = xi;
        x = Math.scalb(x, -bias);
        y = Math.scalb(y, -bias);           
    }


    // translated from "Freely Distributable Math Library" e_hypot.c to minimize rounding errors
    double z = 0; 
    if (x > 2*y) { 
        double x1 = Double.longBitsToDouble(Double.doubleToLongBits(x) & 0xffffffff00000000L);
        double x2 = x - x1;
        z = Math.sqrt(x1*x1 + (y*y + x2*(x+x1)));
    } else {
        double t = 2 * x;
        double t1 = Double.longBitsToDouble(Double.doubleToLongBits(t) & 0xffffffff00000000L);
        double t2 = t - t1;
        double y1 = Double.longBitsToDouble(Double.doubleToLongBits(y) & 0xffffffff00000000L);
        double y2 = y - y1;
        double x_y = x - y;
        z = Math.sqrt(t1*y1 + (x_y*x_y + (t1*y2 + t2*y))); // Note: 2*x*y <= x*x + y*y
    }

    if (bias == 0) {
        return z; 
    } else {
        return Math.scalb(z, bias);
    }
}
/**
*海波
*@param x
*@param y
*@return sqrt(x*x+y*y),无中间溢出或下溢。
*@Note{@link Math#hypot}的速度太慢了。这会将相同的结果返回给
*Math.hypot具有合理的运行时间(~40纳秒对800纳秒)。
*计算z的逻辑是从“自由分发数学库”复制的
*fdlibm的e_hypot.c。这将舍入误差降至最低,以提供1 ulb精度。
*/
公共静态双降压(双x,双y){
if(Double.isInfinite(x)| | Double.isInfinite(y))返回Double.POSITIVE_无穷大;
如果(Double.isNaN(x)| Double.isNaN(y))返回Double.NaN;
x=Math.abs(x);
y=Math.abs(y);
if(xyi+27)返回x;
内部偏差=0;
如果(席>510×席<511){
偏倚=席;
x=数学标度b(x,-偏差);
y=数学标度b(y,-偏差);
}
//翻译自“自由分发数学库”e_hypot.c,以最大限度地减少舍入误差
双z=0;
如果(x>2*y){
double x1=double.longBitsToDouble(double.double-tolongbits(x)&0xffffffff00000000L);
双x2=x-x1;
z=数学sqrt(x1*x1+(y*y+x2*(x+x1));
}否则{
双t=2*x;
double t1=double.longBitsToDouble(double.double-tolongbits(t)&0xffffffff00000000L);
双t2=t-t1;
double y1=double.longBitsToDouble(double.double-tolongbits(y)和0xFFFFFF00000000L);
双y2=y-y1;
双x_y=x-y;
z=Math.sqrt(t1*y1+(x_y*x_y+(t1*y2+t2*y));//注意:与原始实现
sqrt(a*a+b*b)相比,2*x*y
hypot()
在中间计算中会产生避免溢出和下溢的开销
。这涉及到缩放操作。在较旧的实现中,缩放可能使用除法,这本身就是一个相当慢的操作。即使是通过直接指数操作实现缩放,在较旧的处理器体系结构上也可能相当慢,因为在整数ALU和浮点单元之间传输数据相当慢数据驱动的分支在各种扩展方法中也很常见;这些方法很难预测

数学库设计人员的一个常见目标是为简单的数学函数(如
hypot())实现可靠的舍入
,即最大误差小于1。与原始解决方案相比,这种精度的提高意味着中间计算必须以高于本机精度的精度进行。经典方法是将操作数拆分为“高”和“低”部分和模拟扩展精度。这增加了分割的开销,增加了浮点运算的数量。最后,ISOC99规范针对<代码>子()(<代码> >(以后被采纳为C++标准)规定了对楠和无穷大的处理,这些自然不会自然地脱离直接计算。

较早的最佳实践的一个代表性示例是中的
\uuuu ieee754\u hypot()
。虽然它声称最大错误为1 ulp,但对任意精度库的快速测试表明,它实际上并没有达到这一目标,因为可以观察到高达1.15 ulp的错误

自从有人提出这个问题以来,处理器体系结构的两大进步使得
hypot()/**
 * <b>hypot</b>
 * @param x
 * @param y
 * @return sqrt(x*x +y*y) without intermediate overflow or underflow. 
 * @Note {@link Math#hypot} is unnecessarily slow.  This returns the identical result to 
 * Math.hypot with reasonable run times (~40 nsec vs. 800 nsec). 
 * <p>The logic for computing z is copied from "Freely Distributable Math Library" 
 * fdlibm's e_hypot.c. This minimizes rounding error to provide 1 ulb accuracy.
 */
public static double hypot(double x, double y) {

    if (Double.isInfinite(x) || Double.isInfinite(y)) return Double.POSITIVE_INFINITY;
    if (Double.isNaN(x) || Double.isNaN(y)) return Double.NaN;

    x = Math.abs(x);
    y = Math.abs(y);

    if (x < y) {
        double d = x;
        x = y;
        y = d;
    }

    int xi = Math.getExponent(x);
    int yi = Math.getExponent(y);

    if (xi > yi + 27) return x;

    int bias = 0;
    if (xi > 510 || xi < -511) {
        bias = xi;
        x = Math.scalb(x, -bias);
        y = Math.scalb(y, -bias);           
    }


    // translated from "Freely Distributable Math Library" e_hypot.c to minimize rounding errors
    double z = 0; 
    if (x > 2*y) { 
        double x1 = Double.longBitsToDouble(Double.doubleToLongBits(x) & 0xffffffff00000000L);
        double x2 = x - x1;
        z = Math.sqrt(x1*x1 + (y*y + x2*(x+x1)));
    } else {
        double t = 2 * x;
        double t1 = Double.longBitsToDouble(Double.doubleToLongBits(t) & 0xffffffff00000000L);
        double t2 = t - t1;
        double y1 = Double.longBitsToDouble(Double.doubleToLongBits(y) & 0xffffffff00000000L);
        double y2 = y - y1;
        double x_y = x - y;
        z = Math.sqrt(t1*y1 + (x_y*x_y + (t1*y2 + t2*y))); // Note: 2*x*y <= x*x + y*y
    }

    if (bias == 0) {
        return z; 
    } else {
        return Math.scalb(z, bias);
    }
}