2

I wrote a relatively simple code to convert doubles into rational numbers. The code works, and it is guaranteed to find the smallest rational number for a given double; however, it is slower than molasses in January. I spent a day trying various methods to improve it to no avail. Any idea on how to speed it up? The actual algorithm is in the while loop and it's a mere 8 lines.

#include <iostream>
#include <iomanip>
using namespace std;

void rationalize(double number) {
bool isNegative = false; 

if (number == 0.0) { 
    cout << number << ": " << "0/1" << endl; 
    return; 
}   
if (abs(number) < (1.0 / (double) LONG_MAX)) {
    cout << number << " is to small " << endl; 
    return; 
}
if (abs(number) > (double)LONG_MAX) {
    cout << number << " is to big " << endl; 
    return;
}
if (number < 0) {
    isNegative = true;  
    number *= -1; 
}
long numerator = 1;         // at this point, both numerator 
long denominator = 1;       // and denominator must be >= 1
double diff = 1.0 - number; 

//while ((abs(diff) > DBL_EPSILON)  && (numerator >  0) && (denominator > 0)) { 
while ((abs(diff) > FLT_MIN)  && (numerator >  0) && (denominator > 0)) {       
    if (diff > 0) {
        denominator++;
    } else {
        numerator++; 
    }
    diff = ((double) numerator / (double) denominator) - number; 
}   // end while 

if ((numerator <=  0) || (denominator <= 0)) {
    cout << "\nInteger overflow!" << endl; 
    cout << "diff: " << diff <<  ", numerator: " << numerator << "  denominator: " << denominator << endl; 
    return; 
}

if (diff == 0.0) {
    cout << "      Exact result: "; 
    cout << (isNegative ? -numerator : numerator) << "/" << denominator << endl; 
} else if (diff <= FLT_MIN) {
    cout << "Approximate result: "; 
    cout << (isNegative ? -numerator : numerator) << "/" << denominator << endl; 
} else {
    cout << "You've got bugs...  :( " << endl; 
    cout << "diff: " << diff << ",   num:" << numerator << ",  den: " << denominator << endl; 
}

}

int main(void) {
    cout << "\nworking on: (31 / 65537) " << endl; 
    rationalize(4.7301524329767917359659429024826e-4); 
    cout << "\nworking on: (262139 / 2^31-1) " << endl; 
    rationalize(1.220679842504057959888157416083e-4);   
    cout << "\nworking on: (-262147 / 2^31-1) " << endl;  
    rationalize(-1.2207170954070599262635502620896e-4);
    cout << "\nworking on: (1048573 / 2147483647)" << endl; 
    rationalize(4.882798532435111018100339462096e-4); 
    cout << "\nworking on: (-1048583 / 2147483647)" << endl; 
    rationalize(-4.8828450985638634760695805196043e-4); 
    getchar(); 
    return EXIT_SUCCESS; 
}
JSz
  • 21
  • 3
  • I think you're expected to use GCD here. – Cheers and hth. - Alf Feb 27 '17 at 10:01
  • you can't store most rational values in `double`, therefore your solution won't work. All doubles are actually rational values in form `significand/2^(-exp)` – phuclv Feb 27 '17 at 10:05
  • 1
    a) I want to convert doubles to rational numbers not the other way, b) as per my code, the range is restricted to: [1/(2^31 - 1) .. 2^31 - 1] c) IEEE-754 doubles represent only a finite number of doubles d) it doesn't necessarily have to be *exact* as per: if (diff <= FLT_MIN) – JSz Feb 27 '17 at 10:30
  • `FLT_MIN`? That's for `float` not `double`. – MSalters Feb 27 '17 at 11:00
  • 1
    It's just a very small number and it really doesn't matter if it starts with FLT because doubles has much higher resolution. For reference, DBL_EPSILON = 2.2204460492503131e-016, FLT_MIN = 1.175494351e-38, DBL_MIN = 2.2250738585072014e-308 for my compiler. Why define some very small values when there are quite a few already defined in a header file. BTW, when using DBL_EPSILON I often got less precise results. Based on a run with a few dozen random numbers, the results with (abs(diff) > FLT_MIN) were identical to those with (diff != 0.0). – JSz Feb 27 '17 at 15:14

1 Answers1

1

You can do it using GMP:

mpq_t op;
mpq_set_d(op, number);
mpq_canonicalize(op);
long numer = mpz_get_si(mpq_numref(op));
long denom = mpz_get_si(mpq_denref(op));

Ref: https://gmplib.org/manual/Rational-Number-Functions.html

John Zwinck
  • 239,568
  • 38
  • 324
  • 436