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

Converting a floating-point decimal value to a fraction

本文关键字:转换 小数 十进制      更新时间:2023-10-16

给定一个十进制浮点值,如何找到其分数等价值/近似值?例如:

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

有没有一种通用的算法可以将十进制数转换为小数形式?如何在C++中简单高效地实现这一点?

首先得到分数部分,然后取gcd。使用欧几里得算法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

有时你会想要近似小数,而不需要等价。例如,pi=3.14159近似为22/7或355/113。我们可以使用循环参数来获得这些:

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

注释太长。)

一些评论声称这是不可能的。但我持相反意见

我认为,在正确的解释下,这是可能的,但很容易错报问题或误解答案。

这里提出的问题是找到给定浮点值的有理逼近。

这当然是可能的,因为C++中使用的浮点格式只能存储有理值,通常是以符号/尾数/指数的形式。以IEEE-754单精度格式为例(为了简化数字),0.333存储为1499698695241728 * 2^(-52)。这相当于分数1499698695241728 / 2^52,其收敛性提供了越来越精确的近似,一直到原始值:1/3333/100077590/2330035586813/16777216

这里有两点需要注意。

  • 对于变量float x = 0.333;,最佳有理逼近不一定是333 / 1000,因为由于浮点的内部表示精度有限,存储的值不是精确地0.333,而是0.333000004291534423828125

  • 一旦赋值,浮点值就不记得它来自哪里,也不记得源代码是否将其定义为float x = 0.333;float x = 0.333000004;,因为这两个值都有相同的内部表示。这就是为什么将数字的字符串表示(例如,用户输入的值)分离为整数和小数部分的(相关但不同)问题无法通过首先转换为浮点,然后对转换后的值运行浮点计算来解决。


[EDIT]以下是0.333f示例的分步详细信息。

  1. 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. 输出
0.333000004 = 11173626 / 16777216 * 2^(-1)
  1. 这给出了CCD_ 16作为CCD_。

  2. 剩下要做的是确定精确分数的收敛性,这只能使用整数计算来完成。最终结果是(WA提供):

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

我为这个问题想出了一个算法,但我认为它太长了,可以用更少的代码行来完成。很抱歉缩进不好,很难在溢出时对齐所有内容。

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

我完全同意dxiv的解决方案,但我需要一个更通用的函数(我为了好玩而加入了签名的东西,因为我的用例只包括正值):

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

你可以这样称呼它:

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

你得到了n=-124d=1

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

给出n=6004799503160601d=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 numbern"; }};
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 fractionn";
    std::cout << "Enter a "q" to quitn";
    // 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;
 }