确定整数的平方根是否为整数的最快方法

我正在寻找确定long值是否是完美平方(即,其平方根是另一个整数)的最快方法:

  1. 通过使用内置的Math.sqrt()函数,我已经完成了简单的方法,但是我想知道是否有一种方法可以通过将自己限制在仅整数域中来更快地完成操作。
  2. 维护查找表是不切实际的(因为大约有2 31.5个整数,其平方小于2 63 )。

这是我现在做的非常简单明了的方法:

public final static boolean isPerfectSquare(long n)
{
  if (n < 0)
    return false;

  long tst = (long)(Math.sqrt(n) + 0.5);
  return tst*tst == n;
}

注意:我在许多Project Euler问题中都使用了此功能。因此,没有其他人将不得不维护此代码。这种微优化实际上可能会有所作为,因为挑战的一部分是在不到一分钟的时间内完成每种算法,并且在某些问题中,需要数百万次调用此函数。


我已经尝试过不同的解决方案:

  • 经过详尽的测试后,我发现没有必要在Math.sqrt()的结果中加上0.5 ,至少在我的机器上没有。
  • 快速反平方根更快,但是对于n> = 410881,它给出的结果不正确。但是,如BobbyShaftoe所建议,我们可以对n <410881使用FISR hack。
  • 牛顿的方法比Math.sqrt()慢很多。 这可能是因为Math.sqrt()使用类似于牛顿方法的方法,但是在硬件中实现,因此它比Java快得多。 同样,牛顿法仍然需要使用双精度。
  • 一种经过改进的牛顿方法,其中使用了一些技巧,以便仅涉及整数数学,因此需要一些技巧来避免溢出(我希望此函数与所有正的64位带符号整数一起使用),但它仍然比Math.sqrt()Math.sqrt()
  • 二进制印章甚至更慢。 这是有道理的,因为二进制印章平均需要16次通过才能找到64位数字的平方根。
  • 根据John的测试,在C ++中使用or语句比使用switch更快,但是在Java和C#中, orswitch之间似乎没有区别。
  • 我还尝试制作一个查找表(作为64个布尔值的私有静态数组)。 然后,我会说if(lookup[(int)(n&0x3F)]) { test } else return false;而不是switch或or语句, if(lookup[(int)(n&0x3F)]) { test } else return false; 。 令我惊讶的是,这慢了一点。 这是因为数组边界是在Java中检查的

#1楼

“我正在寻找确定长值是否是完美平方(即,其平方根是另一个整数)的最快方法。”

答案令人印象深刻,但我看不到一个简单的检查:

检查长整数右边的第一个数字是否是集合(0,1,4,5,6,9)的成员。 如果不是,则不可能是“完美的正方形”。

例如。

4567-不可能是完美的正方形。


#2楼

我对该线程中的几种算法进行了自己的分析,并得出了一些新结果。 您可以在此答案的编辑历史记录中看到那些较旧的结果,但是由于我犯了一个错误,它们并不准确,并且浪费了时间来分析几种不太接近的算法。 但是,从几个不同的答案中吸取教训,我现在有两种算法可以压碎该线程的“赢家”。 这是我与其他人不同的核心工作:

// This is faster because a number is divisible by 2^4 or more only 6% of the time
// and more than that a vanishingly small percentage.
while((x & 0x3) == 0) x >>= 2;
// This is effectively the same as the switch-case statement used in the original
// answer. 
if((x & 0x7) != 1) return false;

但是,这种简单的行(通常在大多数情况下添加一条或两条非常快的指令)大大简化了将switch-case语句简化为一个if语句的过程。 但是,如果许多测试数字具有显着的2的幂,它可能会增加运行时间。

以下算法如下:

  • 互联网 -Kip发布的答案
  • 杜伦(Durron) -我的修改后的答案以单次通过答案为基础
  • DurronTwo-我的修改后的答案使用了两次通过答案(@JohnnyHeggheim),并进行了其他一些小的修改。

如果数字是使用Math.abs(java.util.Random.nextLong())生成的,这是一个示例运行时

 0% Scenario{vm=java, trial=0, benchmark=Internet} 39673.40 ns; ?=378.78 ns @ 3 trials
33% Scenario{vm=java, trial=0, benchmark=Durron} 37785.75 ns; ?=478.86 ns @ 10 trials
67% Scenario{vm=java, trial=0, benchmark=DurronTwo} 35978.10 ns; ?=734.10 ns @ 10 trials

benchmark   us linear runtime
 Internet 39.7 ==============================
   Durron 37.8 ============================
DurronTwo 36.0 ===========================

vm: java
trial: 0

如果仅在前一百万个long上运行,则这是一个示例运行时:

 0% Scenario{vm=java, trial=0, benchmark=Internet} 2933380.84 ns; ?=56939.84 ns @ 10 trials
33% Scenario{vm=java, trial=0, benchmark=Durron} 2243266.81 ns; ?=50537.62 ns @ 10 trials
67% Scenario{vm=java, trial=0, benchmark=DurronTwo} 3159227.68 ns; ?=10766.22 ns @ 3 trials

benchmark   ms linear runtime
 Internet 2.93 ===========================
   Durron 2.24 =====================
DurronTwo 3.16 ==============================

vm: java
trial: 0

如您所见, DurronTwo在大型输入上表现更好,因为它非常频繁地使用魔术,但是与第一种算法和Math.sqrt相比,它变得DurronTwo ,因为数字小得多。 同时,较简单的Durron是一个巨大的赢家,因为在前100万个数字中,它Durron将其除以4。

这是Durron

public final static boolean isPerfectSquareDurron(long n) {
    if(n < 0) return false;
    if(n == 0) return true;

    long x = n;
    // This is faster because a number is divisible by 16 only 6% of the time
    // and more than that a vanishingly small percentage.
    while((x & 0x3) == 0) x >>= 2;
    // This is effectively the same as the switch-case statement used in the original
    // answer. 
    if((x & 0x7) == 1) {

        long sqrt;
        if(x < 410881L)
        {
            int i;
            float x2, y;

            x2 = x * 0.5F;
            y  = x;
            i  = Float.floatToRawIntBits(y);
            i  = 0x5f3759df - ( i >> 1 );
            y  = Float.intBitsToFloat(i);
            y  = y * ( 1.5F - ( x2 * y * y ) );

            sqrt = (long)(1.0F/y);
        } else {
            sqrt = (long) Math.sqrt(x);
        }
        return sqrt*sqrt == x;
    }
    return false;
}

DurronTwo

public final static boolean isPerfectSquareDurronTwo(long n) {
    if(n < 0) return false;
    // Needed to prevent infinite loop
    if(n == 0) return true;

    long x = n;
    while((x & 0x3) == 0) x >>= 2;
    if((x & 0x7) == 1) {
        long sqrt;
        if (x < 41529141369L) {
            int i;
            float x2, y;

            x2 = x * 0.5F;
            y = x;
            i = Float.floatToRawIntBits(y);
            //using the magic number from 
            //http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf
            //since it more accurate
            i = 0x5f375a86 - (i >> 1);
            y = Float.intBitsToFloat(i);
            y = y * (1.5F - (x2 * y * y));
            y = y * (1.5F - (x2 * y * y)); //Newton iteration, more accurate
            sqrt = (long) ((1.0F/y) + 0.2);
        } else {
            //Carmack hack gives incorrect answer for n >= 41529141369.
            sqrt = (long) Math.sqrt(x);
        }
        return sqrt*sqrt == x;
    }
    return false;
}

我的基准测试工具:(需要Google caliper 0.1-rc5)

public class SquareRootBenchmark {
    public static class Benchmark1 extends SimpleBenchmark {
        private static final int ARRAY_SIZE = 10000;
        long[] trials = new long[ARRAY_SIZE];

        @Override
        protected void setUp() throws Exception {
            Random r = new Random();
            for (int i = 0; i < ARRAY_SIZE; i++) {
                trials[i] = Math.abs(r.nextLong());
            }
        }


        public int timeInternet(int reps) {
            int trues = 0;
            for(int i = 0; i < reps; i++) {
                for(int j = 0; j < ARRAY_SIZE; j++) {
                    if(SquareRootAlgs.isPerfectSquareInternet(trials[j])) trues++;
                }
            }

            return trues;   
        }

        public int timeDurron(int reps) {
            int trues = 0;
            for(int i = 0; i < reps; i++) {
                for(int j = 0; j < ARRAY_SIZE; j++) {
                    if(SquareRootAlgs.isPerfectSquareDurron(trials[j])) trues++;
                }
            }

            return trues;   
        }

        public int timeDurronTwo(int reps) {
            int trues = 0;
            for(int i = 0; i < reps; i++) {
                for(int j = 0; j < ARRAY_SIZE; j++) {
                    if(SquareRootAlgs.isPerfectSquareDurronTwo(trials[j])) trues++;
                }
            }

            return trues;   
        }
    }

    public static void main(String... args) {
        Runner.main(Benchmark1.class, args);
    }
}

更新:我做了一个新的算法,在某些情况下更快,而在另一些情况下则很慢,我根据不同的输入获得了不同的基准。 如果我们计算模0xFFFFFF = 3 x 3 x 5 x 7 x 13 x 17 x 241 ,则可以消除97.82%不能为平方的数字。 可以用5个按位运算在一行中完成(某种程度上):

if (!goodLookupSquares[(int) ((n & 0xFFFFFFl) + ((n >> 24) & 0xFFFFFFl) + (n >> 48))]) return false;

所得索引为1)残基,2)残基+ 0xFFFFFF ,或3)残基+ 0x1FFFFFE 。 当然,我们需要有一个残差查找表,其模为0xFFFFFF ,大约为3mb文件(在这种情况下,存储为ascii文本十进制数字,不是最佳值,但显然可以通过ByteBuffer改进,依此类推。)但这是预先计算的结果没关系, 您可以在这里找到文件 (或自己生成文件 ):

public final static boolean isPerfectSq
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值