Search code examples
c++mathfractionsrational-number

Is there a way to simplify a fraction including decimals?


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?


Solution

  • 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.