Converting a floating-point decimal value to a fraction

巧了我就是萌 提交于 2020-12-09 05:00:12

问题


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++?


回答1:


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);
}



回答2:


#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



回答3:


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;
 } } }   



回答4:


(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


来源:https://stackoverflow.com/questions/64828740/convert-decimal-to-a-fraction

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!