0

This is more like a math problem but i think here is the rights place to ask and it might be usefull to someone.

Here is what the problem is asking for:

Given two real numbers (rA and rB) find the smallest two natural numbers (nA and nB) so that

rA / rB = nA / nB

For those who did not understand properly, the problem is asking for an Irreducible fraction that is equal to some given real number.

My problem arrives when it becomes a precision problem and it takes a lot of time to find those numbers (Ex: rA = 665.32 and rB = 875.1) and i dont even know if there is such a combination of natural number to match the problem.

I also implemented a little KeyPress so that you can still check how much nA and nB got without making your console full.

The most efficient way i came up is:

#include <iostream>
#include <windows.h> // just for GetAsyncKeyState()

int main()
{
    /** The two natural numbers*/
    unsigned long long nA=1;
    unsigned long long nB=1;

    /** The two real numbers*/
    double rA;
    double rB;

    std::cin >> rA >> rB;

    bool bPairFound = false;

    /** The maximum of which nA or nB could go. */
    /** If the value is set to 0, the algorithm will stop when nA or nB overflows */
    #define NUMBER_LIMIT 0x0

    if ((double) nA / nB == rA / rB) bPairFound = true;

    while(bPairFound == false)
    {
        if ((double) nA / nB > rA / rB) nB++;
        if ((double) nA / nB < rA / rB) nA++;
        if ((double) nA / nB == rA / rB) bPairFound = true;

        /** A little keyPress that will show you how much nA and nB got. */
        /** Press space while the program is running. */
        if (GetAsyncKeyState(VK_SPACE) & 0x8000)
            std::cout << "nA: "<<nA<<"   nB: " << nB << "  ---> "<< (double) nA / nB << "   " << rA / rB << std::endl;

        if (nA <= NUMBER_LIMIT || nB <= NUMBER_LIMIT) break;
    }

    if (bPairFound == false) std::cout << "No pair could be found in the set limit." << std::endl;
    if (bPairFound == true) std::cout << "nA: "<<nA<<"   nB: " << nB << std::endl;
    return 0;
}

My questions are:

  1. Can i make this algorithm more efficient?

  2. How can i set the precision of the comparrison to 6 digits?

  3. Is there any way to determine from the start if there is such a pair inside unsigned long long range?

EDIT: Here are some more examples that take too much time to solve or are unsolvable.

rA = 1426.33 rB = 12.7

rA = 764342.33 rB = 98.02001

rA = 1.0001 rB = 1.010001

Community
  • 1
  • 1
Hydra Zerg
  • 47
  • 5
  • 2
    Are your decimal points values fixed? Since the user has to manually types these values in, I would think so. If so, you could just eliminate the decimals to start and then reduce. For example, for `rA = 1426.33 rB = 12.7`, you could multiply by 100 on both to get `142633` and `1270` respectively. Then do standard reduction on `142633/1270` (which coincidentally, is the irreducible fraction). – selbie May 18 '18 at 00:35
  • Is the input of the algorithm supposed to be two numbers with a finite decimal representation, or two numbers with a finite binary representation? The code and examples seem inconsistent on this – harold May 18 '18 at 00:37
  • @harold the two numbers taken at input have a finite decimal representation. (though i dont quite understand what do you need that for) – Hydra Zerg May 18 '18 at 00:39
  • @HydraZerg that means you can write them as fractions already (integer divided by a power of ten) and then you have two fractions that you can divide and simplify. It also means that using `double` is a whole heap of trouble. – harold May 18 '18 at 00:40
  • Now i see it. I can't believe i haven't seen this solution.... – Hydra Zerg May 18 '18 at 00:42
  • @selbie But still why doesen't my code see that combination as valid? I have just tested my code and it checks this combination, but seems to consider it `false`. Any idea why ? – Hydra Zerg May 18 '18 at 00:48
  • @HydraZerg *Any idea why ?* -- Do not compare doubles for equality and expect them to be equal. – PaulMcKenzie May 18 '18 at 01:11

2 Answers2

4

Other than precision issues(a), you can do this most efficiently simply by ensuring the numbers are integers and then dividing them both by the greatest common divisor.

Specifically, pseudo-code such as:

tA = rA
tB = rB
while tA != int(tA) or tB != int(tB):
    tA = tA * 10
    tB = tB * 10
gcd = calcGcd(tA, tB)
nA = tA / gcd
nB = tB / gcd

GCD implementations should be fairly easy to find here on Stack Overflow.

In fact, here's one I prepared earlier :-)


(a) Precision issues can be solved by using an arbitrary-precision arithmetic library such as MPIR.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • I believe that @paxdiablo answer might be more efficient. I have not tried it myself, but it probably is better. – Nitro May 18 '18 at 01:37
1

I believe this would be more efficient.

while(bPairFound == false)
{
    double left=(double)nA*rB;
    double right=(double)nB*rA;

    if(left>right){
         double quotient=left/right;
         unsigned long long prevB=nB;
         nB*=quotient;
         if(prevB==nB){
              nB++;
         }
    }else if(right>left){
         int quotient=right/left;
         unsigned long long prevA=nA;
         nA*=quotient;
         if(prevA==nA){
              nA++;
         }
    }else{
         bPairFound = true;
    }
    /** A little keyPress that will show you how much nA and nB got. */
    /** Press space while the program is running. */
    if (GetAsyncKeyState(VK_SPACE) & 0x8000)
        std::cout << "nA: "<<nA<<"   nB: " << nB << "  ---> "<< (double) nA / nB << "   " << rA / rB << std::endl;

    if (nA <= NUMBER_LIMIT || nB <= NUMBER_LIMIT) break;
}

Since you are increasing the number by the quotient, you will skip quite a few steps in the early process. Also this implementation has more multiplications and less divisions. Since multiplication takes a lower toll, this method will be more efficient.

I hope this helps.

edit 1: I found a way to add precision to the system

double left=(double)nA*rB;
double right=(double)nB*rA;
double quotient=left/right;
unsigned long long test;
if(quotient>=1){
   test=quotient*1000000;
}else{
   test=100000/quotient;
}
if(test==1000000){
    bPairFound = true;
}else if(left>right){
    unsigned long long prevB=nB;
    nB*=quotient;
    if(prevB==nB){
        nB++;
    }
}else if(right>left){
    unsigned long long prevA=nA;
    nA*=1/quotient;
    if(prevA==nA){
        nA++;
    }
}
Nitro
  • 1,063
  • 1
  • 7
  • 17
  • This still suffers from having to rely on two doubles being exactly equal. – PaulMcKenzie May 18 '18 at 01:15
  • @PaulMcKenzie: You do have a point. I didn't think of that. But this implementation does get to the solution faster than the previous one. While coming to a solution, I ended up just focusing on the first question. – Nitro May 18 '18 at 01:22