11

I am trying to write a function in Java that will return the number of factors a specific number has.

The following restrictions should be taken into account.

  1. It should be done with BigInteger
  2. Storing the previous generated numbers are not allowed, thus more processing and less memory.(You can not use "Sieve of Atkin" like in this)
  3. Negative numbers can be ignored.

This is what I have so far, but it is extremely slow.

public static int getNumberOfFactors(BigInteger number) {
    // If the number is 1
    int numberOfFactors = 1;

    if (number.compareTo(BigInteger.ONE) <= 0)  {
        return numberOfFactors;
    }

    BigInteger boundry = number.divide(new BigInteger("2"));
    BigInteger counter = new BigInteger("2");

    while (counter.compareTo(boundry) <= 0) {
        if (number.mod(counter).compareTo(BigInteger.ZERO) == 0) {
            numberOfFactors++;
        }

        counter = counter.add(BigInteger.ONE);
    }

    // For the number it self
    numberOfFactors++;

    return numberOfFactors;
}
Community
  • 1
  • 1
Reg
  • 10,717
  • 6
  • 37
  • 54
  • This also fails if there are repeated factors e.g. 2*2*2 will return 2 (1 for 2 and one for 4) – Peter Lawrey Apr 13 '12 at 10:17
  • 1
    @PeterLawrey Not true - he is iterating all the factors, never does division. – Boris Strandjev Apr 13 '12 at 10:19
  • 4
    Factorization can be so slow that it is used in certain cryptographic algorithms. – 9000 Apr 13 '12 at 10:22
  • It's called [Euler's totient function](http://en.wikipedia.org/wiki/Euler's_totient_function), 9000 is right it is not easy problem. – Betlista Apr 13 '12 at 11:34
  • @BorisStrandjev He iterates all the factors only once, but will include multiples of that factor. BTW: Your solution handles both of these correctly. ;) – Peter Lawrey Apr 13 '12 at 12:06
  • 1
    @PeterLawrey The question never said it should only return the number of prime factors. I'd assume from the way the question is worded that the number of factors for 8 should return 4 (1, 2, 4, 8) – patros Apr 13 '12 at 12:53
  • Adding "prime" to the question completely changes the question and invalidates the existing answers, so I'm going to rollback your edit. If you want to find the number of prime factors, you can modify one of the existing answers. If that doesn't work, you can ask a new question. – Teepeemm May 12 '16 at 13:36

3 Answers3

17

I can propose faster solution, though I have a feeling that it will not be fast enough yet. Your solution runs in O(n) and mine will work in O(sqrt(n)).

I am going to use the fact that if n = xi1p1 * xi2p2 * xi3p3 * ... xikpk is the prime factorization of n (i.e. xij are all distinct primes) then n has (p1 + 1) * (p2 + 1) * ... * (pk + 1) factors in total.

Now here goes the solution:

BigInteger x = new BigInteger("2");
long totalFactors = 1;
while (x.multiply(x).compareTo(number) <= 0) {
    int power = 0;
    while (number.mod(x).equals(BigInteger.ZERO)) {
        power++;
        number = number.divide(x);
    }
    totalFactors *= (power + 1);
    x = x.add(BigInteger.ONE);
}
if (!number.equals(BigInteger.ONE)) {
    totalFactors *= 2;
}
System.out.println("The total number of factors is: " + totalFactors);

This can be further optimized if you consider the case of 2 separately and then have the step for x equal to 2 not 1 (iterating only the odd numbers).

Also note that in my code I modify number, you might find it more suitable to keep number and have another variable equal to number to iterate over.

I suppose that this code will run reasonably fast for numbers not greater than 264.

EDIT I will add the measures of reasonably fast to the answer for completeness. As it can be seen in the comments below I made several measurements on the performance of the proposed algorithm for the test case 1000000072, which was proposed by Betlista:

  • If the algorithm is used as is the time taken is 57 seconds on my machine.
  • If I consider only the odd numbers the time is reduced to 28 seconds
  • If I change the check for the end condition of the while to comparing with the square root of number which I find using binary search the time taken reduces to 22 second.
  • Finally when I tried switching all the BigIntegers with long the time was reduced to 2 seconds. As the proposed algorithm will not run fast enough for number larger than the range of long it might make sense to switch the implementation to long
Boris Strandjev
  • 46,145
  • 15
  • 108
  • 135
  • reasonably fast? How long that code runs for `100000007^2` on your computer? – Betlista Apr 13 '12 at 11:29
  • 2
    @Betlista with the current solution 57 seconds, when I iterated over only the odd numbers - 28 seconds. When I added a binary search for the square root (so that I avoid multiplications) I reduced it to 22. Probably you are right that in extreme cases it is not that reasonably fast as I claim it to be, but I can't think of better solutions. – Boris Strandjev Apr 13 '12 at 11:50
  • 1
    @Betlista btw replacing all big integers with long reduced the time to 3 seconds. regretfully the `BigInteger` are quite of restriction here. – Boris Strandjev Apr 13 '12 at 11:58
  • To speed this up slightly, add ONE the first time and add TWO after that. i.e 2,3,5,7,.... This halves the run time. – Peter Lawrey Apr 13 '12 at 12:07
  • A "total" is usually a sum, not a product. Why are you multiplying the number of factors? – Peter Lawrey Apr 13 '12 at 12:10
  • @PeterLawrey You should have read a bit more closely - I have mentioned the possible improvement if I iterate over only the odd numbers. – Boris Strandjev Apr 13 '12 at 12:13
  • 1
    It looks like this would return 4 for 9... aren't you counting the square root twice as a factor? – patros Apr 13 '12 at 12:58
  • @patros I don't get your point. I tried with 9. I get 3 as expected.I don't think I count it twice. I just count the factor as of power two, which adds multiple of 3 to the number of total factors. Please look at the explanation and code once more. If there is still something unclear I can try to clarify it. – Boris Strandjev Apr 13 '12 at 18:32
  • @BorisStrandjev You're right. Glossed over it too quickly, and assumed you were counting all factors <= sqrt(n) and multiplying by 2. It's more clever than that. – patros Apr 13 '12 at 19:05
  • +1 for `while (x.multiply(x).compareTo(number) <= 0) {` Checking that the square of the factor is less than the number we're finding divisors for is a neat way of not having to compute the sqrt of the number. You benefit from the sqrt(n) limit on factors without having to compute the sqrt, genius. Sped my solution up 20%. – Dan Temple Dec 27 '13 at 10:27
1

Some improvements:

  1. You only need to check up to sqrt(n), not n/2. That makes your algorithm O(sqrt(n)) instead of O(n).
  2. You only need to check odd numbers after checking 2, which should double the speed.
  3. Although you can't use previous numbers, you can construct a sieve with known primes and a little storage: 2, 3 are prime, so only need to check (for example) 11,13,17,19,23 and not 12,14,15,16,18. Thus you can store a pattern of deltas from 3: [+2,+4], repeat every 6:
var deltas = [2,4];
var period = 6;
var val = 3;
var i=0;
while(val<sqrt(n)) {
    var idx = i%deltas.length; // i modulo num deltas
    val += deltas[idx];
    count += isFactor(n,val);
    // if reached end of deltas, add period
    if(idx == deltas.length-1) {
        val += period - deltas[idx];
    }
    ++i;
}

Once you have this result, you obviously have to add 2 and/or 3 if they are factors.

I worked the above pattern out when I was bored at school. You can work out the pattern for any list of primes, but there is a law of diminishing returns; each prime you add increases the period, and hugely increases the length of the list of deltas. So for a long list of known primes, you get an extremely long list of deltas and only a minor improvement in speed. However, do test whether a speed up is worth it.

Since it merely knocks out a known fraction of the values (2/3rds using the 2-value delta shown), this is stil O(sqrt(n)).

Combining the sieve with the sqrt bound, you should get a speedup of 4/(3*sqrt(n)).

[Edit: was adding the period to the last value, not period-lastdelta. Thanks @Betlista]

Phil H
  • 19,928
  • 7
  • 68
  • 105
  • I think that your code is not working fine at least for n = 9 or n = 49. The idea about period could be "skip numbers ending with one of 0, 2, 4, 5, 6, 8", so deltas are 4, 2, 2, 2 starting from 3 - checking 4 of 10 numbers... – Betlista Apr 13 '12 at 12:16
  • 9 is divisible by 3, so the check for 3 should be done outside the loop. 49 is divisible by 7, and 7 is picked as the +4 delta from 3 in the first iteration. 49 will also be hit by the +4 delta on the 8th iteration. – Phil H Apr 13 '12 at 12:22
  • deltas[0] is 2, so you are checking 5, 9, 11, 15... (skipping primes 7 and 13) How the 49 can be hit if there is `val < sqrt(n)` condition ? Initialization for count is missing, but I guess it's 2 – Betlista Apr 13 '12 at 12:33
  • @Betlista: Thanks for checking that, I wasn't adding the right thing at the end of the list. Instead of adding 6, I should add 6-4 (the final delta). That way you get 5,7, 11,13, 17,19 – Phil H Apr 13 '12 at 12:43
0

The fastest solution proposed by Boris Strandjev has some problem by generating output of large numbers in Java. It is the fastest algorithm for finding number of divisors for very big integer in Java.

Here is my code which will run successfully:

import java.math.BigInteger;
import java.util.Scanner;

class ProductDivisors {

    public static BigInteger modulo=new BigInteger("1000000007");
    public static BigInteger solve=new BigInteger("1");
    public static BigInteger two=new BigInteger("2");
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Scanner sc=new Scanner(System.in);
        int N=sc.nextInt();
        BigInteger prod=new BigInteger("1");
        while(N-->0){
            prod=sc.nextBigInteger();
            solve=solve.multiply(prod);
        }
        BigInteger x = new BigInteger("2");
        BigInteger total = new BigInteger("0");
        BigInteger totalFactors =new BigInteger("1");
        while (x.multiply(x).compareTo(solve) <= 0) {
            int power = 0;
            while (solve.mod(x).equals(BigInteger.ZERO)) {
                power++;
                solve = solve.divide(x);
            }
            total = new BigInteger(""+(power + 1));
            totalFactors=totalFactors.multiply(total);
            x = x.add(BigInteger.ONE);
        }
        if (!(solve.equals(BigInteger.ONE))) {
            totalFactors =totalFactors.multiply(two);
        }
        totalFactors=totalFactors.mod(modulo);
        System.out.println(totalFactors);
    }

}

This code is generally taking array of number as input and thereby multiplying which will produce large numbers. And, after that main code for counting number of divisor (including number is taken as divisor here) is done and given output.

I hope it is efficient way and suggest any errors or addition if needed.

Floern
  • 33,559
  • 24
  • 104
  • 119