What is Cardano Triplet ?
If a set of any three positive integers, let's say a, b and c satisfies the condition
cbrt(a + b(sqrt(c)) + cbrt(a - b(sqrt(c)) == 1
Explanation.
if sum of Cubic Root of a + (b * square root of c) and Cubic root of a - (b * square root of c) equals 1 then (a, b, c) is said to be a Cardano triplet.
cbrt represents Cubic Root and sqrt means Square Root.
A integer n will be given, so the numbers a, b and c that we take when added should be lesser than or equal to n.
In short a + b + c <= n.
Constraint : n <= 2^31 -1.
Problem
I've already done something which finds out the correct triplets but when the value of n is greater than 1000 the program runs forever.
public static void cardanoTriplets(long n) {
DecimalFormat decimalFormat = new DecimalFormat("#.###");
long numberOfPairs = 0;
for (long a = 0; a <= n; a++) {
for (long b = 0; b <= n; b++) {
for (long c = 0; c <= (n - a - b); c++) {
if ((a + b + c) == n) {
double val = b * Math.sqrt(c);
double LHS = Double.parseDouble(decimalFormat.format(Math.cbrt(a + val)));
double RHS = Double.parseDouble(decimalFormat.format(Math.cbrt(a - val)));
double addedVal = LHS + RHS;
//System.out.println("RHS and LHS -: ( " + RHS + " , " + LHS + " )");
if (addedVal == 1.0d) {
numberOfPairs++;
//System.out.println(a);
//System.out.println(b);
//System.out.println(c + "\n");
}
}
}
}
}
System.out.println(numberOfPairs);
}
Results
When I pass the value of n as 8, on average the time taken to find the cardano triplet is 31ms and sometimes as low as 16ms. The result was accurate and the result is just one and the triplet is (2, 1, 5).
But when I pass the value of n as 1000, it increases to about 1015ms and the result are not as accurate. It misses out almost 19 triplets. Total number of triplets are 149 for n == 1000.
When the value of n > 1000, let's say 5000, it took 29271ms which is 29 seconds approx and the triplets found are 3364.
Is there any way to reduce time taken to a reasonable amount like less than 5 seconds ?
If so how ?
My Device Specs :
Processor : AMD Ryzen 5 3500U Quad Core
RAM : 8 GB
IDE used : IntelliJ IDEA v2021.2.3 (Community Edition)
Thank you :)
This is a number-theoretical problem; using an imprecise floating point is obviously wrong.
The correct solution requires some math insight. Cardano's name is a great hint.
The expression
cbrt(a + b(sqrt(c)) + cbrt(a - b(sqrt(c))
describes a root of a certain cubic equation. Specifically, the roots of an equation
x^3 + px - q = 0
are
cbrt(q/2 + sqrt((q/2)^2 + (p/3)^3)) + cbrt(q/2) - sqrt(q/2)^2 + (p/3)^3))
Comparing with your problem statement, conclude that a = q/2, and c*b^2 = (q/2)^2 + (p/3)^3
Since a is an integer, q must be even, and since b, c are also integers, p must be divisible by 3. Therefore we are interested in the equations
x^3 + 3ux - 2a = 0
having 1 as a root. That narrows the problem down to searching u, v such that 1 + 3u - 2a = 0. Here u^3 + a^2 = b^2*c. Notice that u must be odd.
All these observations lead to a (pseudo)code:
for u in range(1, n, 2)
a = (1 + 3u)/2
t = u^3 + a^2
find the largest b such that b^2 divides t
c = t / b^2
if a + b + c < n
they are a Cardano triplet
Your first problem, is the loop-in-loop-in-loop what will take 1.000.000.000 rounds for n=1000.
As you know already that n = a + b + c, you can take one loop out. the c-loop
and rewrite as:
for (long a = 0; a <= n; a++) {
for (long b = 0; b <= (n - a); b++) {
long c = n - a - b;
so you go from n * n * n -> n * n
If the equation is n => a + b + c (as in your problem statement), you can use:
for (long a = 0; a <= n; a++) {
for (long b = 0; b <= (n - a); b++) {
for (long c = 0; c <= (n - a - b); c++) {
Second, you are doing a format to a decimal and then convert back to double where as the Math.cbrt gives already a double. I would suggest not doing so.
The problem of "missing 19 triplets" is related to the point above. You only accept 1.0d as the correct answer, there in the previous step you did formatting on the doubles (most likely giving rounding issues). Even if you would take out the formatting, I believe it is better to allow for a bit more rounding error..
something like:
if (0.999 < addedVal && addedVal < 1.001)
However, I have no idea on the math of this equation as there must be a reason why you say there are 149 triplets.. Depending on the rounding for sure you have different answers... I believe there is something like mathemathical proof the triplets are 1.
Last what you can do: I believe the calculation of the Math.cbrt is not that fast. You are repeating this a lot. You can keep track of your calculation by placing the result of the Math,cbrt in a HashSet. The Key is the input and the Value the result of the Math.cbrt.
So first check if you have the Key already in the HashSet, if not calculate the cbrt and place it, if already available us it..
I have written some java code to determine the sum of what three cubes give you a given number. (Sorry I'm really not sure how to explain what it does, the code is rather simple though.) And the problem is, when searching for numbers between -100 and 100 the code runs very quick. But when searching a bigger area, (thousands or ten-thousands etc) It starts to run very slow, even when looking for small numbers. What I am asking are for ways to optimize it, and maybe ways to have it search more systematically, starting at small integers and working its way up to bigger ones, rather than how it does it now which is to pick random ones and check them with the answer.
Here is the code:
public class test{
public static void main(String[] args)
{
int want = -69;
double test;
boolean found = false;
int answer;
while(found == false)
{
int max = 100;
int min = max / 2;
int a = (int) (Math.random()*max - min);
int b = (int) (Math.random()*max - min);
int c = (int) (Math.random()*max - min);
test = Math.pow(a, 3) + Math.pow(b, 3) + Math.pow(c, 3);
answer = (int) test;
if(answer == want) {
found = true;
System.out.println(a + " " + b + " " + c);
System.out.println(answer);
}
}
}
}
This is generally not an easy problem. A more systematic way of doing this would be to test every possible combination of a,b and c within a given range of values for a,b and c. Like this:
public class Main{
static boolean foundSolution = false;
public static void main(String []args){
int limit = 10;
boolean[][][] alreadyChecked = new boolean[limit*2+1][limit*2+1][limit*2+1];
foundSolution = false;
printCubesOfNum(-69,limit,0,0,0,alreadyChecked);
}
public static void printCubesOfNum(int answer,int limit, int a, int b, int c,boolean[][][] alreadyChecked) {
if(!foundSolution && !alreadyChecked[a+limit][b+limit][c+limit] && a < limit && a > -limit && b < limit && b > -limit && c < limit && c > -limit) {
alreadyChecked[a+limit][b+limit][c+limit] = true;
int test = (int)Math.pow(a, 3) + (int)Math.pow(b, 3) + (int)Math.pow(c, 3);
if(test == answer) {
System.out.println(a + " " + b + " " + c);
System.out.println(answer);
foundSolution = true;
}else{
printCubesOfNum(answer,limit,a+1,b,c,alreadyChecked);
printCubesOfNum(answer,limit,a,b+1,c,alreadyChecked);
printCubesOfNum(answer,limit,a,b,c+1,alreadyChecked);
printCubesOfNum(answer,limit,a-1,b,c,alreadyChecked);
printCubesOfNum(answer,limit,a,b-1,c,alreadyChecked);
printCubesOfNum(answer,limit,a,b,c-1,alreadyChecked);
}
}
}
}
Note that this code stops after it found a solution. There can be multiple solutions and there can also be no solutions.
You can check out a similar question on mathoverflow here.
What I am asking are for […] maybe ways to have it search more systematically, starting at small integers and working its way up to bigger ones, […]
Instead of a while-loop, you might consider using nested for-loops:
for (int a = min; a < max; ++a) {
for (int b = min; b < max; ++b) {
for (int c = min; c < max; ++c) {
int answer = (int) (Math.pow(a, 3) + Math.pow(b, 3) + Math.pow(c, 3));
if (answer == want) {
System.out.println(a + " " + b + " " + c);
System.out.println(answer);
return;
}
}
}
}
But you don't actually need to try each possible value for c; rather, you know that c3 = want − a3 − b3; so you can just calculate that directly, and see if it works out:
for (int a = min; a < max; ++a) {
for (int b = min; b < max; ++b) {
// Note: the '0.1' is to be robust if there's some roundoff error:
int c = (int) (0.1 + Math.pow(want - Math.pow(a, 3) - Math.pow(b, 3), 1.0/3.0));
int answer = (int) (Math.pow(a, 3) + Math.pow(b, 3) + Math.pow(c, 3));
if (answer == want && c >= min && c <= max) {
System.out.println(a + " " + b + " " + c);
System.out.println(answer);
return;
}
}
}
And for that matter, the symmetry of the problem (the commutative law of addition) means that we only need to try values of b that are at least equal to a:
for (int b = a; b < max; ++b) {
Another optimization is to avoid Math.pow(..., 3), which is generally much less efficient than ... * ... * ... (because Math.pow has to support non-integer exponents, so it works by taking a logarithm and an exponentiation):
int c = (int) (0.1 + Math.pow(want - a * a * a - b * b * b, 1.0/3.0));
int answer = a * a * a + b * b * b + c * c * c;
Yet another optimization is to restrict the range of b, in the inner for-loop, to only include values that would put c in the range of [min, max): we know that if b is too small, then c would have to be too big, and vice versa:
int minB = (int) (0.1 + Math.pow(want - a * a * a - max * max * max, 1.0/3.0));
if (minB < a) {
minB = a;
}
int maxB = (int) (0.1 + Math.pow(want - a * a * a - min * min * min, 1.0/3.0)) + 1;
if (maxB > max) {
maxB = max;
}
for (int b = minB; b < maxB; ++b) {
Of course, these sorts of optimizations can only take you so far. If you want to search a large range of integers, that will necessary take a lot of time. But the above should still be a massive improvement over what you have now.
I have a question regarding the CountDiv problem in Codility.
The problem given is: Write a function:
class Solution { public int solution(int A, int B, int K); }
that, given three integers A, B and K, returns the number of integers within the range [A..B] that are divisible by K, i.e.:
{ i : A ≤ i ≤ B, i mod K = 0 }
My code:
class Solution {
public int solution(int A, int B, int K) {
int start=0;
if (B<A || K==0 || K>B )
return 0;
else if (K<A)
start = K * ( A/K +1);
else if (K<=B)
start = K;
return (B-start+1)/K+ 1;
}
}
I don't get why I'm wrong, specially with this test case:
extreme_ifempty
A = 10, B = 10, K in {5,7,20}
WRONG ANSWER
got 1 expected 0
if K =5 then with i=10 A<=i<=B and i%k =0 so why should I have 0? Problem statement.
This is the O(1) solution, which passed the test
int solution(int A, int B, int K) {
int b = B/K;
int a = (A > 0 ? (A - 1)/K: 0);
if(A == 0){
b++;
}
return b - a;
}
Explanation: Number of integer in the range [1 .. X] that divisible by K is X/K. So, within the range [A .. B], the result is B/K - (A - 1)/K
In case A is 0, as 0 is divisible by any positive number, we need to count it in.
Java solution with O(1) and 100% in codility, adding some test cases with solutions for those who want to try and not see others solutions:
// Test cases
// [1,1,1] = 1
// [0,99,2] = 50
// [0, 100, 3] = 34
// [11,345,17] = 20
// [10,10,5] = 1
// [3, 6, 2] = 2
// [6,11,2] = 3
// [16,29,7] = 2
// [1,2,1] = 2
public int solution(int A, int B, int K) {
int offsetForLeftRange = 0;
if ( A % K == 0) { ++offsetForLeftRange; }
return (B/K) - (A /K) + offsetForLeftRange;
}
The way to solve this problem is by Prefix Sums as this is part of that section in Codility.
https://codility.com/programmers/lessons/3/
https://codility.com/media/train/3-PrefixSums.pdf
Using this technique one can subtract the count of integers between 0 and A that are divisible by K (A/K+1) from the the count of integers between 0 and B that are divisible by K (B/K+1).
Remember that A is inclusive so if it is divisible then include that as part of the result.
Below is my solution:
class Solution {
public int solution(int A, int B, int K) {
int b = (B/K) + 1; // From 0 to B the integers divisible by K
int a = (A/K) + 1; // From 0 to A the integers divisible by K
if (A%K == 0) { // "A" is inclusive; if divisible by K then
--a; // remove 1 from "a"
}
return b-a; // return integers in range
}
}
return A==B ? (A%K==0 ? 1:0) : 1+((B-A)/K)*K /K;
Well it is a completely illegible oneliner but i posted it just because i can ;-)
complete java code here:
package countDiv;
public class Solution {
/**
* First observe that
* <li> the amount of numbers n in [A..B] that are divisible by K is the same as the amount of numbers n between [0..B-A]
* they are not the same numbes of course, but the question is a range question.
* Now because we have as a starting point the zero, it saves a lot of code.
* <li> For that matter, also A=-1000 and B=-100 would work
*
* <li> Next, consider the corner cases.
* The case where A==B is a special one:
* there is just one number inside and it either is divisible by K or not, so return a 1 or a 0.
* <li> if K==1 then the result is all the numbers between and including the borders.
* <p/>
* So the algorithm simplifies to
* <pre>
* int D = B-A; //11-5=6
* if(D==0) return B%K==0 ? 1:0;
* int last = (D/K)*K; //6
* int parts = last/K; //3
* return 1+parts;//+1 because the left part (the 0) is always divisible by any K>=1.
* </pre>
*
* #param A : A>=1
* #param B : 1<=A<=B<=2000000000
* #param K : K>=1
*/
private static int countDiv(int A, int B, int K) {
return A==B ? A%K==0 ? 1:0 : 1+((B-A)/K)*K /K;
}
public static void main(String[] args) {
{
int a=10; int b=10; int k=5; int result=1;
System.out.println( a + "..." + b + "/" + k + " = " + countDiv(a,b,k) + (result!=countDiv(a,b,k) ? " WRONG" :" (OK)" ));
}
{
int a=10; int b=10; int k=7; int result=0;
System.out.println( a + "..." + b + "/" + k + " = " + countDiv(a,b,k) + (result!=countDiv(a,b,k) ? " WRONG" :" (OK)" ));
}
{
int a=6; int b=11; int k=2; int result=3;
System.out.println( a + "..." + b + "/" + k + " = " + countDiv(a,b,k) + (result!=countDiv(a,b,k) ? " WRONG" :" (OK)" ));
}
{
int a=6; int b=2000000000; int k=1; int result=b-a+1;
System.out.println( a + "..." + b + "/" + k + " = " + countDiv(a,b,k) + (result!=countDiv(a,b,k) ? " WRONG" :" (OK)" ));
}
}
}//~countDiv
I think the answers above don't provide enough logical explanation to why each solution works (the math behind the solution) so I am posting my solution here.
The idea is to use the arithmetic sequence here. If we have first divisible number (>= A) and last divisible number (<= B) we have an arithmetic sequence with distance K. Now all we have to do is find the total number of terms in the range [newA, newB] which are total divisible numbers in range [newA, newB]
first term (a1) = newA
last/n-th term (an) = newB
distance (d) = K
Sn = a1 + (a1+K) + (a1 + 2k) + (a1 + 3k) + ... + (a1 + (n-1)K)
`n` in the above equation is what we are interested in finding. We know that
n-th term = an = a1 + (n-1)K
as an = newB, a1 = newA so
newB = newA + (n-1)K
newB = newA + nK - K
nK = newB - newA + K
n = (newB - newA + K) / K
Now that we have above formula so just apply it in code.
fun countDiv(A: Int, B: Int, K: Int): Int {
//NOTE: each divisible number has to be in range [A, B] and we can not exceed this range
//find the first divisible (by k) number after A (greater than A but less than B to stay in range)
var newA = A
while (newA % K != 0 && newA < B)
newA++
//find the first divisible (by k) number before B (less than B but greater than A to stay in range)
var newB = B
while (newB % K != 0 && newB > newA)
newB--
//now that we have final new range ([newA, newB]), verify that both newA and newB are not equal
//because in that case there can be only number (newA or newB as both are equal) and we can just check
//if that number is divisible or not
if (newA == newB) {
return (newA % K == 0).toInt()
}
//Now that both newA and newB are divisible by K (a complete arithmetic sequence)
//we can calculate total divisions by using arithmetic sequence with following params
//a1 = newA, an = newB, d = K
// we know that n-th term (an) can also be calculated using following formula
//an = a1 + (n - 1)d
//n (total terms in sequence with distance d=K) is what we are interested in finding, put all values
//newB = newA + (n - 1)K
//re-arrange -> n = (newB - newA + K) / K
//Note: convert calculation to Long to avoid integer overflow otherwise result will be incorrect
val result = ((newB - newA + K.toLong()) / K.toDouble()).toInt()
return result
}
I hope this helps someone. FYI, codility solution with 100% score
Simple solution in Python:
def solution(A, B, K):
count = 0
if A % K == 0:
count += 1
count += int((B / K) - int(A / K))
return count
explanation:
B/K is the total numbers divisible by K [1..B]
A/K is the total numbers divisible by K [1..A]
The subtracts gives the total numbers divisible by K [A..B]
if A%K == 0, then we need to add it as well.
This is my 100/100 solution:
https://codility.com/demo/results/trainingRQDSFJ-CMR/
class Solution {
public int solution(int A, int B, int K) {
return (B==0) ? 1 : B/K + ( (A==0) ? 1 : (-1)*(A-1)/K);
}
}
Key aspects of this solution:
If A=1, then the number of divisors are found in B/K.
If A=0, then the number of divisors are found in B/K plus 1.
If B=0, then there is just one i%K=0, i.e. zero itself.
Here is my simple solution, with 100%
https://app.codility.com/demo/results/trainingQ5XMG7-8UY/
public int solution(int A, int B, int K) {
while (A % K != 0) {
++A;
}
while (B % K != 0) {
--B;
}
return (B - A) / K + 1;
}
Python 3 one line solution with score 100%
from math import ceil, floor
def solution(A, B, K):
return floor(B / K) - ceil(A / K) + 1
This works with O(1) Test link
using System;
class Solution
{
public int solution(int A, int B, int K)
{
int value = (B/K)-(A/K);
if(A%K == 0)
{
value=value+1;
}
return value;
}
}
I'm not sure what are you trying to do in your code, but simpler way would be to use modulo operator (%).
public int solution(int A, int B, int K)
{
int noOfDivisors = 0;
if(B < A || K == 0 || K > B )
return 0;
for(int i = A; i <= B; i++)
{
if((i % K) == 0)
{
noOfDivisors++;
}
}
return noOfDivisors;
}
If I understood the question correctly I believe this is the solution:
public static int solution(int A, int B, int K) {
int count = 0;
if(K == 0) {
return (-1);
}
if(K > B) {
return 0;
}
for(int i = A; i <= B; ++i) {
if((i % K) == 0) {
++count;
}
}
return count;
}
returning -1 is due to an illegal operation (division by zero)
int solution(int A, int B, int K) {
int tmp=(A%K==0?1:0);
int x1=A/K-tmp ;
int x2=B/K;
return x2-x1;
}
100/100 - another variation of the solution, based on Pham Trung's idea
class Solution {
public int solution(int A, int B, int K) {
int numOfDivs = A > 0 ? (B / K - ((A - 1) / K)) : ((B / K) + 1);
return numOfDivs;
}
}
class Solution {
public int solution(int A, int B, int K) {
int a = A/K, b = B/K;
if (A/K == 0)
b++;
return b - a;
}
}
This passes the test.
It's similar to "how many numbers from 2 to 5". We all know it's (5 - 2 + 1). The reason we add 1 at the end is that the first number 2 counts.
After A/K, B/K, this problem becomes the same one above. Here we need to decide if A counts in this problem. Only if A%K == 0, it counts then we need to add 1 to the result b - a (the same with b+1).
Here's my solution, two lines of Java code.
public int solution(int A, int B, int K) {
int a = (A == 0) ? -1 : (A - 1) / K;
return B / K - a;
}
The thought is simple.
a refers to how many numbers are divisible in [1..A-1]
B / K refers to how many numbers are divisible in [1..B]
0 is divisible by any integer so if A is 0, you should add one to the answer.
Here is my solution and got 100%
public int solution(int A, int B, int K) {
int count = B/K - A/K;
if(A%K == 0) {
count++;
}
return count;
}
B/K will give you the total numbers divisible by K [1..B]
A/K will give you the total numbers divisible by K [1..A]
then subtract, this will give you the total numbers divisible by K [A..B]
check A%K == 0, if true, then + 1 to the count
Another O(1) solution which got 100% in the test.
int solution(int A, int B, int K) {
if (A%K)
A = A+ (K-A%K);
if (A>B)
return 0;
return (B-A)/K+1;
}
This is my 100/100 solution:
public int solution1(int A, int B, int K) {
return A == 0 ? B / K - A / K + 1 : (B) / K - (A - 1) / K;
}
0 is divisible by any integer so if A is 0, you should add one to the answer.
This is the O(1) solution, ( There is no check required for the divisility of a)
public static int countDiv(int a, int b, int k) {
double l1 = (double)a / k;
double l = -1 * Math.floor(-1 * l1);
double h1 = (double) b / k;
double h = Math.floor(h1);
Double diff = h-l+1;
return diff.intValue();
}
There is a lot of great answers, but I think this one has some elegance in it, also gives 100% on codility.
public int solution(int a, int b, int k) {
return Math.floorDiv(b, k) - Math.floorDiv(a-1, k);
}
Explanation: Number of integers in the range [1 .. B] that divisible by K is B/K. Range [A .. B] can be transformed to [1 .. B] - [1 .. A) (notice that round bracket after A means that A does not belong to that range). That gives as a result B/K - (A-1)/K. Math.floorDiv is used to divide numbers and skip remaining decimal parts.
I will show my code in go :)
func CountDiv(a int, b int, k int) int {
count := int(math.Floor(float64(b/k)) - math.Floor(float64(a/k)));
if (math.Mod(float64(a), float64(k)) == 0) {
count++
}
return count
}
The total score is 100%
If someone is still interested in this exercise, I share my Python solution (100% in Codility)
def solution(A, B, K):
if not (B-A)%K:
res = int((B-A)/K)
else:
res = int(B/K) - int(A/K)
return res + (not A%K)
int divB = B / K;
int divA = A / K;
if(A % K != 0) {
divA++;
}
return (divB - divA) + 1;
passed 100% in codelity
My 100% score solution with one line code in python:
def solution(A, B, K):
# write your code in Python 3.6
return int(B/K) - int(A/K) + (A%K==0)
pass
int solution(int A, int B, int K)
{
// write your code in C++14 (g++ 6.2.0)
int counter = 0;
if (A == B)
A % K == 0 ? counter++ : 0;
else
{
counter = (B - A) / K;
if (A % K == 0) counter++;
else if (B % K == 0) counter++;
else if ((counter*K + K) > A && (counter*K + K) < B) counter++;
}
return counter;
}
Assumptions:
A and B are integers within the range [0..2,000,000,000];
K is an integer within the range [1..2,000,000,000];
A ≤ B.
int from = A+(K-A%K)%K;
if (from > B) {
return 0;
}
return (B-from)/K + 1;
Could anyone explain to me why the algorithm below is an error-free integer factorization method that always return a non-trivial factor of N.
I know how weird this sounds, but I designed this method 2 years ago and still don't understand the mathematical logic behind it, which is making it difficult for me to improve it. It's so simple that it involves only addition and subtraction.
public static long factorX( long N )
{
long x=0, y=0;
long b = (long)(Math.sqrt(N));
long a = b*(b+1)-N;
if( a==b ) return a;
while ( a!= 0 )
{
a-= ( 2+2*x++ - y);
if( a<0 ) { a+= (x+b+1); y++; }
}
return ( x+b+1 );
}
It seems that the above method actually finds a solution by iteration to the diophantine equation:
f(x,y) = a - x(x+1) + (x+b+1)y
where b = floor( sqrt(N) ) and a = b(b+1) - N
that is, when a = 0, f(x,y) = 0 and (x+b+1) is a factor of N.
Example: N = 8509
b = 92, a = 47
f(34,9) = 47 - 34(34+1) + 9(34+92+1) = 0
and so x+b+1 = 127 is a factor of N.
Rewriting the method:
public static long factorX(long N)
{
long x=1, y=0, f=1;
long b = (long)(Math.sqrt(N));
long a = b*(b+1)-N;
if( a==b ) return a;
while( f != 0 )
{
f = a - x*(x+1) + (x+b+1)*y;
if( f < 0 ) y++;
x++;
}
return x+b+1;
}
I'd really appreciate any suggestions on how to improve this method.
Here's a list of 10 18-digit random semiprimes:
349752871155505651 = 666524689 x 524741059 in 322 ms
259160452058194903 = 598230151 x 433211953 in 404 ms
339850094323758691 = 764567807 x 444499613 in 1037 ms
244246972999490723 = 606170657 x 402934339 in 560 ms
285622950750261931 = 576888113 x 495109787 in 174 ms
191975635567268981 = 463688299 x 414018719 in 101 ms
207216185150553571 = 628978741 x 329448631 in 1029 ms
224869951114090657 = 675730721 x 332780417 in 1165 ms
315886983148626037 = 590221057 x 535201141 in 110 ms
810807767237895131 = 957028363 x 847213937 in 226 ms
469066333624309021 = 863917189 x 542952889 in 914 ms
OK, I used Matlab to see what was going here. Here is the result for N=100000:
You are increasing x on each iteration, and the funny pattern of a variable is strongly related with the remainder N % x+b+1 (as you can see in the gray line of the plot, a + (N % (x+b+1)) - x = floor(sqrt(N))).
Thus, I think you are just finding the first factor larger than sqrt(N) by simple iteration, but with a rather obscure criterion to decide it is really a factor :D
(Sorry for the half-answer... I have to leave, I will maybe continue later).
Here is the matlab code, just in case you want it to test by yourself:
clear all
close all
N = int64(100000);
histx = [];
histDiffA = [];
histy = [];
hista = [];
histMod = [];
histb = [];
x=int64(0);
y=int64(0);
b = int64(floor(sqrt(double(N))));
a = int64(b*(b+1)-N);
if( a==b )
factor = a;
else
while ( a ~= 0 )
a = a - ( 2+2*x - y);
histDiffA(end+1) = ( 2+2*x - y);
x = x+1;
if( a<0 )
a = a + (x+b+1);
y = y+1;
end
hista(end+1) = a;
histb(end+1) = b;
histx(end+1) = x;
histy(end+1) = y;
histMod(end+1) = mod(N,(x+b+1));
end
factor = x+b+1;
end
figure('Name', 'Values');
hold on
plot(hista,'-or')
plot(hista+histMod-histx,'--*', 'Color', [0.7 0.7 0.7])
plot(histb,'-ob')
plot(histx,'-*g')
plot(histy,'-*y')
legend({'a', 'a+mod(N,x+b+1)-x', 'b', 'x', 'y'}); % 'Input',
hold off
fprintf( 'factor is %d \n', factor );
Your method is a variant of trial multiplication of (n-a)*(n+b), where n=floor(sqrt(N)) and b==1.
The algorithm then iterates a-- / b++ until the difference of the (n-a)*(n+b) - N == 0.
The partial differences (in respect of a and b) are in proportion to 2b and 2a respectively. Thus no true multiplication are necessary.
The complexity is a linear function of |a| or |b| -- the more "square" N is, the faster the method converges. In summary, there are much faster methods, one of the easiest to understand being the quadratic residue sieve.
Pardon my c#, I don't know Java.
Stepping x and y by 2 increases algorithm speed.
using System.Numerics; // needed for BigInteger
/* Methods ************************************************************/
private static BigInteger sfactor(BigInteger k) // factor odd integers
{
BigInteger x, y;
int flag;
x = y = iSqrt(k); // Integer Square Root
if (x % 2 == 0) { x -= 1; y += 1; } // if even make x & y odd
do
{
flag = BigInteger.Compare((x*y), k);
if (flag > 0) x -= 2;
y += 2;
} while(flag != 0);
return x; // return x
} // end of sfactor()
// Finds the integer square root of a positive number
private static BigInteger iSqrt(BigInteger num)
{
if (0 == num) { return 0; } // Avoid zero divide
BigInteger n = (num / 2) + 1; // Initial estimate, never low
BigInteger n1 = (n + (num / n)) >> 1; // right shift to divide by 2
while (n1 < n)
{
n = n1;
n1 = (n + (num / n)) >> 1; // right shift to divide by 2
}
return n;
} // end iSqrt()