简体   繁体   English

将浮点十进制值转换为分数

[英]Converting a floating-point decimal value to a fraction

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++?在C++中如何简单高效的实现呢?

First get the fractional part and then take the gcd.先取小数部分,再取gcd。 Use the Euclidean algorithm http://en.wikipedia.org/wiki/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);
}
#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.例如,pi=3.14159 近似为 22/7 或 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

( 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.这当然是可能的,因为 C++ 中使用的浮点格式只能存储有理值,最常见的是符号/尾数/指数的形式。 Taking IEEE-754 single precision format as an example (to keep the numbers simpler), 0.333 is stored as 1499698695241728 * 2^(-52) .IEEE-754单精度格式为例(为了使数字更简单), 0.333存储为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 .这相当于分数1499698695241728 / 2^52收敛提供越来越准确的近似值,一直到原始值: 1/3333/100077590/2330035586813/16777216

Two points of note here.这里要注意两点。

  • For a variable float x = 0.333;对于变量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.最佳有理近似值不一定是333 / 1000 ,因为存储的值不完全是0.333而是0.333000004291534423828125因为浮点的内部表示的精度有限。

  • 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;一旦赋值,浮点值就没有记忆它来自哪里,或者源代码是否将它定义为float x = 0.333; vs. float x = 0.333000004;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. [编辑] 以下是0.333f示例的分步详细信息。

  1. The code to convert a float to an exact fraction.float转换为精确分数的代码。
#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;这给出了float val = 0.333f;的有理表示float val = 0.333f; as 5586813/16777216 .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): 最终结果是(由 WA 提供):

0, 1/3, 333/1000, 77590/233003, 5586813/16777216

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

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):我完全同意dxiv解决方案,但我需要一个更通用的 function (我为了好玩而加入了签名的东西,因为我的用例只包含正值):

#include <concepts>

/**
 * \brief Multiply two numbers together checking for overflow.
 * \tparam T The unsigned integral type to check for multiplicative overflow.
 * \param a The multiplier.
 * \param b The multicland.
 * \return The result and a value indicating whether the multiplication 
 *         overflowed.
 */
template<std::unsigned_integral T>
auto mul_overflow(T a, T b) -> std::tuple<T, bool>
{
    size_t constexpr shift{ std::numeric_limits<T>::digits / 2 };
    T constexpr mask{ (T{ 1 } << shift) - T{ 1 } };
    T const a_high = a >> shift;
    T const a_low = a & mask;
    T const b_high = b >> shift;
    T const b_low = b & mask;

    T const low_low{ a_low * b_low };
    if (!(a_high || b_high))
    {
        return { low_low, false };
    }

    bool overflowed = a_high && b_high;
    T const low_high{ a_low * b_high };
    T const high_low{ a_high * b_low };

    T const ret{ low_low + ((low_high + high_low) << shift) };
    return
    {
        ret,
        overflowed
        || ret < low_low
        || (low_high >> shift) != 0
        || (high_low >> shift) != 0
    };
}

/**
 * \brief Converts a floating point value to a numerator and
 * denominator pair.
 *
 * If the floating point value is larger than the maximum that the Tout
 * type can hold, the results are silly.
 *
 * \tparam Tout The integral output type.
 * \tparam Tin The floating point input type.
 * \param f The value to convert to a numerator and denominator.
 * \return The numerator and denominator.
 */
template <std::integral Tout, std::floating_point Tin>
auto to_fraction(Tin f) -> std::tuple<Tout, Tout>
{
    const Tin multiplier
    {
        std::pow(std::numeric_limits<Tin>::radix, 
                 std::numeric_limits<Tin>::digits)
    };
    uint64_t denominator{ static_cast<uint64_t>(multiplier) };
    int power;
    Tout num_fix{ 1 };
    if constexpr (std::is_signed_v<Tout>)
    {
        num_fix = f < static_cast<Tin>(0) ? -1 : 1;
        f = std::abs(f);
    }

    uint64_t numerator
    {
        static_cast<uint64_t>(std::frexp(f, &power) * multiplier)
    };
    uint64_t const factor
    {
        static_cast<uint64_t>(std::pow(
            std::numeric_limits<Tin>::radix, std::abs(power)))
    };
    if (power > 0)
    {
        while(true)
        {
            auto const [res, overflow]{ mul_overflow(numerator, factor) };
            if (!overflow)
            {
                numerator = res;
                break;                    
            }
            numerator >>= 1;
            denominator >>= 1;
        }
    }
    else
    {
        while (true)
        {
            auto const [res, overflow]{ mul_overflow(denominator, factor) };
            if (!overflow)
            {
                denominator = res;
                break;
            }
            numerator >>= 1;
            denominator >>= 1;
        }
    }

    // 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 ;你得到n=-124 , d=1

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

gives n=6004799503160601 , d=18014398509481984给出n=6004799503160601 , d=18014398509481984

#include<iostream>
#include<cmath>
#include<algorithm>
#include<functional>
#include<iomanip>
#include<string>
#include<vector>
#include <exception>
#include <sstream>

// note using std = c++11
// header section

#ifndef rational_H
#define rational_H

struct invalid : std::exception {
    const char* what() const noexcept { return "not a number\n"; }};

struct Fraction {
public:
    long long value{0};
    long long numerator{0};
    long long denominator{0};
}; Fraction F;

class fraction : public Fraction{

public:
    fraction() {}
    void ctf(double &);
    void get_fraction(std::string& w, std::string& d, long double& n) {
        F.value = (long long )n;
        set_whole_part(w);
        set_fraction_part(d);
        make_fraction();
    }
    long long set_whole_part(std::string& w) {
        return whole = std::stoll(w);
    }
    long long set_fraction_part(std::string& d) {
         return decimal = std::stoll(d);
    }
    void make_fraction();
    bool cmpf(long long&, long long&, const long double& epsilon);
    int Euclids_method(long long&, long long&);

    long long get_f_part() { return decimal; };
    void convert(std::vector<long long>&);
    bool  is_negative{ false };

    friend std::ostream& operator<<(std::ostream& os, fraction& ff);
    struct get_sub_length;

private:
    long long whole{ 0 };
    long long decimal{ 0 };
};
#endif // rational_H

// definitions/source

struct get_sub_length {
    size_t sub_len{};
    size_t set_decimal_length(size_t& n) {
        sub_len = n;
        return sub_len;
    }
    size_t get_decimal_length() { return sub_len; }
}; get_sub_length slen;

struct coefficient {
    std::vector<long long>coef;
}; coefficient C;

//compare's the value returned by convert with the original 
// decimal value entered.
//if its within the tolarence of the epsilon consider it the best
//approximation you can get.
//feel free to experiment with the epsilon.
//for better results.
 
bool fraction::cmpf(long long& n1, long long& d1, const long double& epsilon = 0.0000005) 
{
long double ex = pow(10, slen.get_decimal_length());
long long  d = get_f_part();       // the original fractional part to use for comparison.
long double  a = (long double)d / ex;
long double b = ((long double)d1 / (long double)n1);
if ((fabs(a - b) <= epsilon)) { return true; }
return false;
}

//Euclids algorithm returns the cofficients of a continued fraction through recursive division,
//for example: 0.375 -> 1/(375/1000) (note: for the fractional portion only).
// 1000/375 -> Remainder of 2.6666.... and  1000 -(2*375)=250,using only the integer value
// 375/250 -> Remainder of 1.5  and   375-(1*250)=125,
// 250/125 -> Remainder of 2.0  and   250-(2*125)=2
//the coefficients of the continued fraction are the integer values 2,1,2
// These are generally written [0;2,1,2] or [0;2,1,1,1] were 0 is the whole number value.

int fraction::Euclids_method(long long& n_dec, long long& exp) 
{

    long long quotient = 0;

    if ((exp >= 1) && (n_dec != 0)) {
        quotient = exp / n_dec;

        C.coef.push_back(quotient);

        long long divisor = n_dec;
        long long dividend = exp - (quotient * n_dec);

        Euclids_method(dividend, divisor); // recursive division 
    }
    return 0;
}

 // Convert is adding the elements stored in coef as a simple continued fraction
// which should result in a good approximation of the original decimal number.

void fraction::convert(std::vector<long long>& coef) 
{
    std::vector<long long>::iterator pos;
    pos = C.coef.begin(), C.coef.end();
    long long n1 = 0;
    long long n2 = 1;
    long long d1 = 1;
    long long d2 = 0;

    for_each(C.coef.begin(), C.coef.end(), [&](size_t pos) {

        if (cmpf(n1, d1) == false) {

            F.numerator = (n1 * pos) + n2;
            n2 = n1;
            n1 = F.numerator;

            F.denominator = (d1 * pos) + d2;
            d2 = d1;
            d1 = F.denominator;
        }
    });

    //flip the fraction back over to format the correct output.
    F.numerator = d1;
    F.denominator = n1;
}

// creates a fraction from the decimal component
// insures its in its abs form to ease calculations.

void fraction::make_fraction() {

    size_t count = slen.get_decimal_length();
    long long n_dec = decimal;
    long long exp = (long long)pow(10, count);

    Euclids_method(n_dec, exp);
    convert(C.coef);
}

std::string get_w(const std::string& s) 
{
    std::string st = "0";
    std::string::size_type pos;
    pos = s.find(".");
        if (pos - 1 == std::string::npos) {
            st = "0";
            return st;
        }
        else { st = s.substr(0, pos);
        return st;
        }

    if (!(s.find("."))){
            st = "0";
        return st;
    }
    return st;
 }

std::string get_d(const std::string& s)
{ 
    std::string st = "0";
    std::string::size_type pos;
        pos = s.find(".");
        if (pos == std::string::npos) {
            st = "0";
            return st;
        }
        std::string sub = s.substr(pos + 1);
            st = sub;
                size_t sub_len = sub.length(); 
                    slen.set_decimal_length(sub_len);
        return st;
}

void fraction::ctf(double& nn)
{
        //using stringstream for conversion to string
        std::istringstream is;
        is >> nn;
        std::ostringstream os;
        os << std::fixed << std::setprecision(14) << nn;

        std::string s = os.str();

        is_negative = false; //reset for loops
        C.coef.erase(C.coef.begin(), C.coef.end()); //reset for loops

        long double n = 0.0;
        int m = 0;

        //The whole number part will be seperated from the decimal part leaving a pure fraction.
        //In such cases using Euclids agorithm would take the reciprocal 1/(n/exp) or exp/n.
        //for pure continued fractions the cf must start with 0 + 1/(n+1/(n+...etc
        //So the vector is initilized with zero as its first element.

        C.coef.push_back(m);
        std::cout << '\n';
    
        if (s == "q") { // for loop structures
            exit(0);
        }

        if (s.front() == '-') { // flag negative values. 
            is_negative = true; // represent nagative in output
            s.erase(remove(s.begin(), s.end(), '-'), s.end()); // using abs
        }

        // w, d, seperate the string components
        std::string w = get_w(s); 
        std::string d = get_d(s);

        try
        {
            if (!(n = std::stold(s))) {throw invalid(); } // string_to_double()
            get_fraction(w, d, n);
        } 
        catch (std::exception& e) {
            std::cout << e.what();
            std::cout <<'\n'<< std::endl;
        }
}

// The ostream formats and displays the various outputs

std::ostream& operator<<(std::ostream& os, fraction& f) 
{
    std::cout << '\n';
    if (f.is_negative == true) {
        os << "The coefficients are [" << '-' << f.whole << ";";
            for (size_t i = 1; i < C.coef.size(); ++i) {
                os << C.coef[i] << ',';
            }
            std::cout << "]" << '\n';
        os << "The cf is: " << '-' << f.whole;
            for (size_t i = 1; i < C.coef.size(); ++i) {
                os << "+1/(" << C.coef[i];
            }
            for (size_t i = 1; i < C.coef.size(); ++i) {
                os << ')';
            }
            std::cout << '\n';

        if (F.value >= 1 && F.numerator == 0 && F.denominator == 1) {
            F.numerator = abs(f.whole);
                os << '-' << F.numerator << '/' << F.denominator << '\n';
                return os;
        }
        else if (F.value == 0 && F.numerator == 0 && F.denominator == 1) {
                os << F.numerator << '/' << F.denominator << '\n';
                return os;
        }
        else if (F.value == 0 && F.numerator != 0 && F.denominator != 0) {
                os << '-' << abs(F.numerator) << '/' << abs(F.denominator) << '\n';
                return os;
        }
        else if (F.numerator == 0 && F.denominator == 0) {
                os << '-' << f.whole << '\n';
                return os;
        }
        else
                os << '-' << (abs(f.whole) * abs(F.denominator) + abs(F.numerator)) << '/' << abs(F.denominator) << '\n';
    }

    if (f.is_negative == false) {

        os << "The coefficients are [" << f.whole << ";";
             for (size_t i = 1; i < C.coef.size(); ++i) {
                os << C.coef[i] << ',';
            }
            std::cout << "]" << '\n';
        os << "The cf is: " << f.whole;
            for (size_t i = 1; i < C.coef.size(); ++i) {
                os << "+1/(" << C.coef[i];
            }
            for (size_t i = 1; i < C.coef.size(); ++i) {
                os << ')';
            }
            std::cout << '\n';

        if (F.value >= 1 && F.numerator == 0 && F.denominator == 1) {
            F.numerator = abs(f.whole);
                os << F.numerator << '/' << F.denominator << '\n';
                return os;
        }
        else if (F.value == 0 && F.numerator != 0 && F.denominator != 0) {
                os << abs(F.numerator) << '/' << abs(F.denominator) << '\n';
                return os;
        }
        else if (F.numerator == 0 && F.denominator == 0) {
            os << f.whole << '\n';
            return os;
        }
        else
            os << (abs(f.whole) * abs(F.denominator) + abs(F.numerator)) << '/' << abs(F.denominator) << '\n';

            os << f.whole << ' ' << F.numerator << '/' << F.denominator << '\n';
    }
    return os;
}

int main() 
{
    fraction f;
    double s = 0;
    std::cout << "Enter a number to convert to a fraction\n";
    std::cout << "Enter a \"q\" to quit\n";
    // uncomment for a loop

    while (std::cin >> s) {
        f.ctf(s);
        std::cout << f << std::endl;
    }

    // comment out these lines if you want the loop

    //std::cin >> s; 
    //f.ctf(s);
    //std::cout << f << std::endl;
 }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM