4

Given a decimal floating-point value, how can you find its fractional equivalent/approximation? For example:

as_fraction(0.1) -> 1/10
as_fraction(0.333333) -> 1/3
as_fraction(514.0/37.0) -> 514/37

Is there a general algorithm that can convert a decimal number to fractional form? How can this be implemented simply and efficiently in C++?

Cody Gray
  • 230,875
  • 49
  • 477
  • 553
Kehlin Swain
  • 451
  • 1
  • 4
  • 13
  • One idea, just an idea, figure out a way to find 2 fractions, one less than the answer, the other larger. Then from there, loop towards each other and find the closest, unless there is always going to be a whole number. – Evan Carslake Oct 30 '14 at 01:57
  • By "decimal", do you mean your input is a string of decimal digits and a decimal point, or do you simply mean, not integer? Note that both decimals and binary floating-point numbers actually are representations of fractions, but not necessarily the fraction you were thinking of. For example, the representation of the fraction 1/3 in either binary or decimal is actually a different number. – Ben Voigt Oct 30 '14 at 02:03
  • There's an efficient algorithm outlined in this answer http://stackoverflow.com/questions/12098461/how-can-i-detect-if-a-float-has-a-repeating-decimal-expansion-in-c/12101996#12101996 It finds successively closer fractional approximations of "nice" form. – Michael Anderson Oct 30 '14 at 02:21
  • 2
    I wrote up an answer, but since this has been closed I've put it as a gist : https://gist.github.com/mikeando/7073d62385a34a61a6f7 – Michael Anderson Oct 30 '14 at 02:37
  • 1
    There's a slightly better answer at http://stackoverflow.com/a/5128558/221955, and my gist now contains a C++ implementation of it and a comparison of the two methods. – Michael Anderson Oct 30 '14 at 03:54
  • Old question, but with a continued fraction, you get a series of ever better approximations. E.g. pi≈3; pi≈3+1/7; pi≈3+1/(7+1/16)=3+16/113; pi≈3+1/(7+1/(16-1/294))=3+4703/33215 -> accurate up to 10 digits! – Sebastian Apr 13 '22 at 14:33

6 Answers6

12

First get the fractional part and then take the gcd. Use the Euclidean algorithm http://en.wikipedia.org/wiki/Euclidean_algorithm

void foo(double input)
{
    double integral = std::floor(input);
    double frac = input - integral;

    const long precision = 1000000000; // This is the accuracy.

    long gcd_ = gcd(round(frac * precision), precision);

    long denominator = precision / gcd_;
    long numerator = round(frac * precision) / gcd_;

    std::cout << integral << " + ";
    std::cout << numerator << " / " << denominator << std::endl;
}

long gcd(long a, long b)
{
    if (a == 0)
        return b;
    else if (b == 0)
        return a;

    if (a < b)
        return gcd(a, b % a);
    else
        return gcd(b, a % b);
}
qbt937
  • 949
  • 7
  • 25
  • I don't think you should be dividing by the denominator, and this won't find the "nicest" fraction, but it is a reasonable approach to getting some reduced fraction. – Ben Voigt Oct 30 '14 at 02:07
  • 1
    This site can help some people to understand your algorithm: http://www.webmath.com/dec2fract.html – Ludovic Feltz Oct 30 '14 at 02:08
  • @BenVoigt: Sorry that was a typo. – qbt937 Oct 30 '14 at 02:08
  • @Ludovic: That webpage uses a very different algorithm. Try putting `0.3333333` into it. – Ben Voigt Oct 30 '14 at 02:09
  • @benVoigt ok i saw what you mean but it can be a good help to understand it, isn't it? – Ludovic Feltz Oct 30 '14 at 02:11
  • +1 `frac = input - (long) input;` - maybe better to use `input - std::floor(input)`...? – Tony Delroy Oct 30 '14 at 02:22
  • @BenVoigt the site appears to use at least two different algorithms, depending on the number you give it. One of them is very similar to this answer, and both depend on the GCD as shown here. – Mark Ransom Oct 30 '14 at 02:30
  • 1
    Gah, that web2frac is aweful. Tried it on 1.17171 and it gave nothing of value. The algorithm in my gist gives the values "41/35", "116/99" and "1549/1322" as successively better approximations. – Michael Anderson Oct 30 '14 at 02:41
  • @MichaelAnderson: Good answer. I think you had is a good idea to use continued fractions. – qbt937 Oct 30 '14 at 02:48
6
#include <iostream>
#include <valarray> 

using namespace std;

void as_fraction(double number, int cycles = 10, double precision = 5e-4){
    int sign  = number > 0 ? 1 : -1;
    number = number * sign; //abs(number);
    double new_number,whole_part;
    double decimal_part =  number - (int)number;
    int counter = 0;
    
    valarray<double> vec_1{double((int) number), 1}, vec_2{1,0}, temporary;
    
    while(decimal_part > precision & counter < cycles){
        new_number = 1 / decimal_part;
        whole_part = (int) new_number;
        
        temporary = vec_1;
        vec_1 = whole_part * vec_1 + vec_2;
        vec_2 = temporary;
        
        decimal_part = new_number - whole_part;
        counter += 1;
    }
    cout<<"x: "<< number <<"\tFraction: " << sign * vec_1[0]<<'/'<< vec_1[1]<<endl;
}

int main()
{
    as_fraction(3.142857);
    as_fraction(0.1);
    as_fraction(0.333333);
    as_fraction(514.0/37.0);
    as_fraction(1.17171717);
    as_fraction(-1.17);
}


x: 3.14286      Fraction: 22/7                                                                                                                
x: 0.1          Fraction: 1/10                                                                                                                        
x: 0.333333     Fraction: 1/3                                                                                                                 
x: 13.8919      Fraction: 514/37                                                                                                              
x: 1.17172      Fraction: 116/99                                                                                                              
x: 1.17         Fraction: -117/100

Sometimes you would want to approximate the decimal, without needing the equivalence. Eg pi=3.14159 is approximated as 22/7 or 355/113. We could use the cycles argument to obtain these:

as_fraction(3.14159, 1);
as_fraction(3.14159, 2);
as_fraction(3.14159, 3);

x: 3.14159      Fraction: 22/7                                                                                                                
x: 3.14159      Fraction: 333/106                                                                                                             
x: 3.14159      Fraction: 355/113
drescherjm
  • 9,653
  • 5
  • 43
  • 62
onyambu
  • 49,350
  • 3
  • 19
  • 45
  • @Onyambu Could you elaborate on what `cycles` and `precision` are supposed to mean? It is not obvious (to me, at least) why `0.33328`, `0.3333` and `0.33338` would all return the fraction [`1/3`](https://godbolt.org/z/97fvq7). – dxiv Nov 13 '20 at 23:29
  • @dxiv increase the number of cycles and precision and you will have the result. eg `as_fraction(0.33328,10, 1e-7);` – onyambu Nov 13 '20 at 23:34
  • @Onyambu Thanks, but that would only shift the question to different examples. Could you state (in the answer) what the expected result is in terms of `cycles` and `precision` in general? Without that, I can't even tell whether `1/3` is the correct answer in those cases, simply because I don't know what the function is expected to return. – dxiv Nov 13 '20 at 23:40
  • @dxiv i ran that and I got 2083/6250 which is exactly 0.33328. This is the reason as to why I gave the precision and cycles parameters – onyambu Nov 13 '20 at 23:48
  • @Onyambu That you get a different result on your machine than the `1/3` that I linked to only confirms that this is implementation dependent, and inherently unreliable. Leaving that aside for a moment, though, you have still not defined what the function is supposed to return in terms of its arguments. I don't see the point of a function whose behavior remains unspecified. Something like "`as_fraction` *returns *" would be a proper description. "*If you don't like the result use a different* `precision`" is not. – dxiv Nov 14 '20 at 00:05
  • @dxiv and `as_fraction(0.33338,10, 1e-7);` will give you `16669/50000`. The higher the precision, the more correct the approximation. Note that when you write `0.3333` for example, this can be interpreted as `3333/10000` rather than 1/3. So you need to really tell when you need `1/3` or even `3333/10000` and you could use the cycle and precision parameters. The best thing to do is try and give the correct values into the function. ie x – onyambu Nov 14 '20 at 00:08
  • *Any* result could be defended, as long as you do not (or cannot) specify what the expected return of the function is in the general case. – dxiv Nov 14 '20 at 00:24
  • @dxiv technically, there there is no decimal equivalent for 1/3 but we still take 0.3333 as an approximate – onyambu Nov 14 '20 at 00:45
  • @dxiv. The function I wrote kind of tries to approximate the fractional representation of a decimal up to the 4th significant value. eg 0.333 =>333/1000, while 0.3333=>1/3. Note that when you have 0.33332=>1/3. So can use the precision argument and/or cycles argument to increase the precision of your fraction estimation. You can look at the algorithm under continued fractions on wikipedia – onyambu Nov 14 '20 at 01:55
  • The main difficulty in defining what `as_fraction` actually does is that you cannot guarantee consistent results as long as you are using floating point calculations in that loop. The rest of my would-be comment had to go into an "answer" of sorts because it wouldn't fit here. – dxiv Nov 14 '20 at 04:14
1

(Too long for a comment.)

Some comments claim that this is not possible. But I am of a contrary opinion.

I am of the opinion that it is possible in the right interpretation, but it is too easy to misstate the question or misunderstand the answer.

The question posed here is to find rational approximation(s) to a given floating point value.

This is certainly possible since floating point formats used in C++ can only store rational values, most often in the form of sign/mantissa/exponent. Taking IEEE-754 single precision format as an example (to keep the numbers simpler), 0.333 is stored as 1499698695241728 * 2^(-52). That is equivalent to the fraction 1499698695241728 / 2^52 whose convergents provide increasingly accurate approximations, all the way up to the original value: 1/3, 333/1000, 77590/233003, 5586813/16777216.

Two points of note here.

  • For a variable float x = 0.333; the best rational approximation is not necessarily 333 / 1000, since the stored value is not exactly 0.333 but rather 0.333000004291534423828125 because of the limited precision of the internal representation of floating points.

  • Once assigned, a floating point value has no memory of where it came from, or whether the source code had it defined as float x = 0.333; vs. float x = 0.333000004; because both of those values have the same internal representation. This is why the (related, but different) problem of separating a string representation of a number (for example, a user-entered value) into integer and fractional parts cannot be solved by first converting to floating point then running floating point calculations on the converted value.


[ EDIT ]   Following is the step-by-step detail of the 0.333f example.

  1. The code to convert a float to an exact fraction.
#include <cfloat>
#include <cmath>
#include <limits>
#include <iostream>
#include <iomanip>

void flo2frac(float val, unsigned long long* num, unsigned long long* den, int* pwr)
{
    float mul = std::powf(FLT_RADIX, FLT_MANT_DIG);
    *den = (unsigned long long)mul;
    *num = (unsigned long long)(std::frexp(val, pwr) * mul);
    pwr -= FLT_MANT_DIG;
}

void cout_flo2frac(float val)
{
    unsigned long long num, den; int pwr;
    flo2frac(val, &num, &den, &pwr);

    std::cout.precision(std::numeric_limits<float>::max_digits10);
    std::cout << val << " = " << num << " / " << den << " * " << FLT_RADIX << "^(" << pwr << ")" << std::endl;
}

int main()
{
    cout_flo2frac(0.333f);
}
  1. Output.
0.333000004 = 11173626 / 16777216 * 2^(-1)
  1. This gives the rational representation of float val = 0.333f; as 5586813/16777216.

  2. What remains to be done is determine the convergents of the exact fraction, which can be done using integer calculations, only. The end result is (courtesy WA):

0, 1/3, 333/1000, 77590/233003, 5586813/16777216
dxiv
  • 16,095
  • 2
  • 22
  • 46
  • could you try as_fractions(0.333) it will give you 333/1000 and not 1/3 but try increasing that ie as_fractions(0.333333333) this will give you 1/3. So what point exactly are you trying to put across? What is the contention about? Get any simple fraction ie upto 4 significant places and try it out and see whether you will not get the result you want. The notion here is to try and do an approximation. First we can not have an exact 1/3 representation. even a simple 1/10 which is 0.1 does not have exact representation in Binary. The method I provided which is a simple continued fraction is... – onyambu Nov 14 '20 at 04:57
  • used by matlab and R and probably others to represent decimals to fraction equivalent, with the. In matlab, the precision is to 1e-6 i believe whereby 0.3333333 will be 1/3 and thus 0.33333333768 will still be 1/3. So I just presented a quick way of solving the problem in C++. You can write the same function while letting the default precision to be 1e-8 and you will have pretty accurate fractional approximations of the fraction – onyambu Nov 14 '20 at 05:13
  • @Onyambu 1) Again, you ask the user to tweak the parameters in order to obtain a result they know already. That's not how it's supposed to work. Instead, *you* should document what the function does and what the expected result is in terms of its inputs. 2) But that is difficult to do with the code as-is, because of numerical instabilities due to using floating point in the intermediate calculations e.g. I posted a link to `0.33328` returning `1/3` while you got `2083/6250`. 3) There *is* a right way to do it, using exact integers as described above, but that takes considerably more work. – dxiv Nov 14 '20 at 05:47
  • @Onyambu "*Get any simple fraction ie upto 4 significant places and try it out and see whether you will not get the result you want*" If I "*wanted*" a certain result, I wouldn't need to call the function to begin with ;-) For "*simple examples*", `as_fraction(987.0/610.0);` returns `144/89`, and `(4181.0/6765.0)` returns `55/89`. I am not saying the results are wrong, and neither can I say they are right, simply because the function does not set any expectation of *what* it is supposed to be returning. – dxiv Nov 14 '20 at 05:47
  • I feel we are in different spectrums. Could you please just define a funtion that would transform a decimal to its approximate/equivalent fraction just like in matlab, R, Python and the rest and post it here. Let it not be too sophisticated. I would indeed accept your answer. – onyambu Nov 14 '20 at 05:51
  • @Onyambu See for example [frexp](https://en.cppreference.com/w/cpp/numeric/math/frexp) and [how can I extract the mantissa of a double](https://stackoverflow.com/a/5673518). – dxiv Nov 14 '20 at 05:54
  • In no way does frexp give a rational representation of a number. Eg if you are given 0.1, it does not return 1/10. Please look at the question again. We need a rational representation of a decimal number – onyambu Nov 14 '20 at 06:19
  • @Onyambu You are missing the point that there is no `double` that has a value of exactly `0.1`. When you write `double x = 0.1;` what gets stored in `x` is a value close to, but not equal to `0.1`. – dxiv Nov 14 '20 at 06:48
  • Yes there is no double that is exactly 0.1. That is true. But could you get a rational approximation of 0.1? Please note that that rational approximation is used in mathematica, matlab, Python, R etc. So what is the issue of contention? My funtion is a simple funtion that can do the rational approximation. Is it 100%correct? Of course not. Even the built in funtion in matlab is not 100%accurate. – onyambu Nov 14 '20 at 07:01
  • @Onyambu I added the code to convert a floating point value to the exact rational fraction (less error checking etc). After that, you can add the integer calculations for convergents, and write a function that returns consistent, predictable results. And of course, you can use similar code to "*get a rational approximation of 0.1*". All being said, I'll leave it at that since this doesn't seem to be going anywhere. – dxiv Nov 14 '20 at 07:59
0

I came up with an algorithm for this problem, but I think it is too lengthy and can be accomplished with less lines of code. Sorry about the poor indentation it is hard trying to align everything on overflow.

#include <iostream>
using namespace std;


// converts the string half of the inputed decimal number into numerical values void converting
 (string decimalNumber, float&numerator, float& denominator )

 { float number; string valueAfterPoint =decimalNumber.substr(decimalNumber.find("."    ((decimalNumber.length() -1) )); // store the value after the decimal into a valueAfterPoint 

int length = valueAfterPoint.length(); //stores the length of the value after the decimal point into length 

numerator = atof(valueAfterPoint.c_str()); // converts the string type decimal number into a float value and stores it into the numerator

// loop increases the decimal value of the numerator by multiples of ten as long as the length is above zero of the decimal

for (; length > 0; length--)  
    numerator *= 10;

do
 denominator *=10;
  while  (denominator < numerator);



// simplifies the the converted values of the numerator and denominator into simpler values for          an easier to read output 


void simplifying (float& numerator, float& denominator) { int maximumNumber = 9; //Numbers in the tenths place can only range from zero to nine so the maximum number for a position in a position for the decimal number will be nine

bool isDivisble; // is used as a checker to verify whether the value of the numerator has the       found the dividing number that will a value of zero
 // Will check to see if the numerator divided denominator is will equal to zero


   if(int(numerator) % int(denominator) == 0) {
   numerator /= denominator;
   denominator = 1;   
   return; }


  //check to see if the maximum number is greater than the denominator to simplify to lowest     form while (maximumNumber < denominator) { maximumNumber *=10;  }


 // the maximum number loops from nine to zero. This conditions stops if the function isDivisible is true 
 for(; maximumNumber > 0;maximumNumber --){

 isDivisble = ((int(numerator) % maximumNumber == 0) && int(denominator)% maximumNumber == 0);

  if(isDivisble)
 {
    numerator /= maximumNumber;  // when is divisible true numerator be devided by the max        number value for example 25/5 = numerator = 5

   denominator /= maximumNumber; //// when is divisible true denominator be devided by themax        number value for example 100/5 = denominator = 20

 }


 // stop value if numerator and denominator is lower than 17 than it is at the lowest value
 int stop = numerator + denominator;

 if (stop < 17)
 {
     return;
 } } }   
Kehlin Swain
  • 451
  • 1
  • 4
  • 13
0
#include<iostream>
#include<cmath>
#include<algorithm>
#include<functional>
#include<iomanip>

/*
May be over done but seems to work as far as I have tested. 
Convert a decimal to a fraction or mixed number.
write a function that derives the number of decimal places by multipling
the fractional part by powers of 10 until no fractional part is left
maybe check using a \bool is_fraction()\ function that returns true or false
and a recursive function to multiply powers of 10 , to derive the numerator by 
moving the decimal place:
the denominator is the power of ten that leaves no fractional parts.
use the solutions to generate a fraction then if needed reduce it(maybe a 
function 
that finds the greatest common divisor).
if it reduces to a numerator over 1 print just the whole number integer.
remember to catch divide by zero, and irrational numbers should be rounded and
displayed as approx values.
*/

struct Fraction {
    double value{0.0}; // The initial value entered
}; Fraction F;

class fraction {
private:
    double decimal{}; // The decimal part
    int whole{};   // The whole number part
    int numerator{}; 
    int denominator{};


public:
    fraction() {} // default constructor
   // This does the conversion, calls all the help
    void get_fraction(double& n) {         
        F.value = n;
        set_whole_part(F.value);
        set_fraction_part(F.value);
        set_numerator();
    }
    // This provides the help
    double set_fraction_part(const double& );
    double set_whole_part(const double& );
    double get_f_part();
    double get_w_part();
    double set_numerator();
    int set_denominator(unsigned&);
    int get_denominator(){ return denominator; }
    double get_numerator() { return numerator; }
    int GCD(int numer, int denom);
    bool is_numerator(double&);
    double number{}; // bool number 0.99999999
    // output operator
    friend std::ostream& operator<<(std::ostream& os, fraction& f); 
    // input operator
    friend std::istream& operator>>(std::istream& is, fraction& f);
   
};
double fraction::set_whole_part(const double& )
{
    double w_part = F.value; // whole number part
    return whole = static_cast<int>(w_part); // return an integer value
}
double fraction::set_fraction_part(const double&)
{
    double f_part = F.value; // fractional number part
 // returns the fractional component of float or double
    return decimal = modf(F.value, &f_part);  

}
double fraction::get_f_part()
{
    return decimal; // decimal part
}
double fraction::get_w_part()
{
    return whole;
}
// istream operator 
std::istream& operator>>(std::istream& is, fraction& f) {
    double n = 0.0;
    is >> n;
    f.get_fraction(n);
    return is;
}
// ostream operator has friend status so can access private members

std::ostream& operator<<(std::ostream& os, fraction& f)
{
    os << static_cast<double>(f.numerator)/f.denominator << '\n';
    if (f.numerator == 0 && f.denominator == 1) {
        f.numerator = (f.whole);
    }
    if (f.whole >= 1 && f.numerator != 0 && f.denominator != 0) {
        os << f.whole<<' ' << f.numerator << '/' << f.denominator << '\n';
        return os;
    }
    if (f.whole == 0 && f.numerator != 0 && f.denominator != 0) {
        os << f.numerator << '/' << f.denominator << '\n';
        return os;
    }
    if (f.numerator == 0 && f.denominator == 0) {
        os << f.whole << '\n';
        return os;
    }
        os << "failed to create numerator/denominator\n";
        return os;
}
// returns the greatest common divisor
int fraction::GCD(int numer, int denom) {
    return denom > 1 ? GCD(denom, numer % denom) : numer;
}
bool fraction::is_numerator(double& n)
{
//using the rounding information to create the break point.
//This will lose information on the original value stored.
//is the least significant digit being rounded toward zero or one?
    if (n <= 0.00000001) { number = 0.00000000; return false; }   // dont't round
    if (n <= 0.99999999) { number = 0.99999999; return true; } // round up

    return false;
}
int fraction::set_denominator(unsigned& denom)
{
    return denominator = denom;
}
double fraction::set_numerator() 
{
    double num = get_f_part();
    double dec = 0.0;
    unsigned count = 0;
    double f_part = 0;

    while (is_numerator(num) == true) 
    {
        num *= 10; // move one decimal place and count
        f_part = num; // make a new double.decimal 
        double dec = modf(num, &f_part); // get the new decimal part
    
        num = dec; // re-initialize
    
        ++count; // keeps count of shifts of fractional part
    }
    unsigned exp = 1; // exponenent = power of 10
    for (unsigned i = 0; i < count;  ++i) {
        exp *= 10; // get the total shifts of decimal point
    }
    //promote exp to avoid  arithmatic over flow
    double s_dec = decimal * static_cast<double>(exp);
    // cast back to an int  for GCD(returns an int)
    int n_dec = static_cast<int>(s_dec);    
    if (number == 0.999999) {
        n_dec += 1; // round up
    }
if ((whole != 0) && (number == 0.99999999)) {
    // if rounding toward one than add one.(the bool discarded that info).
    n_dec += 1;
}
    unsigned reduced = GCD(n_dec, exp); // find Greatest Common Divisor 
    if (reduced <= 0) { // no common divisor return as is
    
        set_denominator(exp);
        return numerator = n_dec; // may produce a zero numerator
        std::cerr << "could not convert fraction\n";
    }
    else if (reduced >= 1) { // reduce the fraction
        unsigned denom = exp / reduced;
        denominator=denom;
        unsigned numer = n_dec / reduced;
        return numerator = numer;
    }
    else
        std::cerr << "could not convert fraction\n";

    return 0;
}

 int main()
{
    fraction f;
    for (fraction ff; std::cin >> std::setprecision(12) >> std::fixed >> f;)
        std::cout << f;
        return 0;
}
0

I agree completely with dxiv's solution but I needed a more general function (I threw in the signed stuff for fun because my use cases only included positive values):

#include <concepts>
template <std::integral Tout, std::floating_point Tin>
auto to_fraction(Tin f) -> std::tuple<Tout, Tout>
{
    // may need to be "const" instead of "constexpr" depending on library
    constexpr Tin multiplier
    {
        std::pow(std::numeric_limits<Tin>::radix, std::numeric_limits<Tin>::digits)
    };
    uint64_t denominator{ static_cast<uint64_t>(multiplier) };
    int power;
    int64_t numerator{ static_cast<int64_t>(std::frexp(f, &power) * multiplier) };
    uint64_t factor
    {
        static_cast<uint64_t>(
            std::pow(std::numeric_limits<Tin>::radix, std::abs(power)))
    };
    if (power > 0)
    {
        numerator *= factor;
    }
    else
    {
        denominator *= factor;
    }

    Tout num_fix {1};
    if constexpr(std::is_signed_v<Tout>)
    {
        num_fix = numerator < 0 ? -1 : 1;
        numerator = std::abs(numerator);
    }

    // get the results into the requested sized integrals.
    while((numerator > std::numeric_limits<Tout>::max() 
           || denominator > std::numeric_limits<Tout>::max()) 
          && denominator > 1)
    {
        numerator >>= 1;
        denominator >>= 1;
    }

    return 
    {
        num_fix * static_cast<Tout>(numerator),
        static_cast<Tout>(denominator)
    };
};

You can call this like:

auto [n, d] { to_fraction<int8_t>(-124.777f) };

And you get n=-124, d=1;

auto [n, d] { to_fraction<uint64_t>(.33333333333333) };

gives n=6004799503160601, d=18014398509481984

mheyman
  • 3,944
  • 34
  • 33