Whenever a user inputs a fraction, it might be contain decimals (long double). I have some code (C++) below to show how I implement this:
class Fraction
{
public:
long double numerator, denominator;
Fraction(long double _numerator, long double _denominator) : numerator(_numerator), denominator(_denominator) {}
}
bool isInt(long double a)
{
long double intPart;
std::modf(a, &intPart);
return (a == intPart);
}
void simplifyRealFraction(Fraction& frac)
{
constexpr long double MULTIPLE_FACTOR = 2L;
constexpr int EXPONENT_LIMIT = 100;
int n_exp, d_exp;
std::frexp(frac.numerator, &n_exp);
std::frexp(frac.denominator, &d_exp);
long double new_n = frac.numerator / std::exp2(std::max(n_exp,d_exp));
long double new_d = frac.denominator / std::exp2(std::max(n_exp,d_exp));
while(!isInt(new_n) || !isInt(new_d))
{
new_n *= MULTIPLE_FACTOR;
new_d *= MULTIPLE_FACTOR;
}
std::frexp(new_n, &n_exp);
std::frexp(new_d, &d_exp);
if(n_exp > EXPONENT_LIMIT || d_exp > EXPONENT_LIMIT)
{
return;
}
long long int int_gcd = std::gcd(static_cast<long long int>(new_n), static_cast<long long int>(new_d));
new_n /= int_gcd;
new_d /= int_gcd;
frac.numerator = new_n;
frac.denominator = new_d;
}
However, when I try to run my function with the fraction 0.15 / 0.20, it changes it to a much larger, but equivalent value: 5.40432e+15 / 7.20576e+15
After some debugging, I found that std::gcd returned 1. I am assuming that this is a rounding error occurring in the floating point number from 0.15 to something else (14.999999 or similar for example).
Is there a better way to do this process? How can I avoid the rounding errors?
What you're seeing is how floating point numbers work. They don't hold numbers in decimal like .15. They hold them in binary. So it can hold .5 (2^-1), .25 (2^-2), .125 (2^-3), and other powers of 2 (or combos thereof) exactly. Something like .15 is held as .125 + .015625 (2^-6)+... So any number that isn't exactly a sum of powers of 2 will not be held exactly. It will be estimated.
If you truly need to hold them exactly, you need to use a fixed point math library. Or if you know that all numbers are exactly 2 decimal points, hold them as integer hundreths instead of whole numbers.