问题
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 necessarily333 / 1000
, since the stored value is not exactly0.333
but rather0.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.
- 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);
}
- Output.
0.333000004 = 11173626 / 16777216 * 2^(-1)
This gives the rational representation of
float val = 0.333f;
as5586813/16777216
.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