3

I have made the following rational numbers C++ class with all the general arithmetic functions (+, -, *, /, == and !=).

template <class T>
struct rationalNumber
{
    static_assert(!std::numeric_limits<T>::is_signed, "ERROR: Type T must be unsigned");
    static_assert(std::is_integral<T>::value, "ERROR: Type T must be integral");

    T numerator;
    T denominator;
    bool sign;

    rationalNumber(const int n = 0) : numerator(std::abs(n)), denominator(1), sign(std::signbit(n)) {}
    rationalNumber(const T n, const T d, const bool s = false) : numerator(n), denominator(d), sign(s) {}
    rationalNumber(const rationalNumber&) = default;
    rationalNumber& operator=(const rationalNumber&) = default;

    rationalNumber operator-() const
    {
        return rationalNumber(numerator, denominator, !sign);
    }

    void reduce()
    {
        T divisor = gcd(numerator, denominator);
        if (divisor != 1)
        {
            numerator /= divisor;
            denominator /= divisor;
        }
        else if (numerator == 0)
        {
            denominator = 1;
            sign = false;
        }

        assert(denominator != 0);
    }
};

using RN = rationalNumber<unsigned long long>;

Is it feasible to implement the remaining relational operators operators (<, >, <=, >=) using floating point arithmetic, or will that lead to error prone results?

Note that I have only considered floating points since cross multiplying could in many cases lead to integer overflow.

Rodrigo de Azevedo
  • 1,097
  • 9
  • 17
Jonas
  • 6,915
  • 8
  • 35
  • 53
  • 2
    Well, if we say `T` is `int64_t` we have up to 19 digits, while `double` is good to 15 or 16... so clearly you can have a problem sometimes. – Tony Delroy Feb 16 '15 at 09:13

1 Answers1

3

Yes, it is feasible to implement the test for inequality using floating point operations. And, yes, that will potentially give "error prone results" due to finite precision of floating point.

It is not actually necessary to use floating point at all. Mathematically, a test of "a/b > c/d" (assuming a,b,c,d are positive) is equivalent to the test "ad > bc". With unsigned variables, you will also need to account for (or work around) the effects of modulo arithmetic (I'll leave doing that as an exercise) but it is quite feasible to implement the test exactly without using floating point at all.

Rob
  • 1,966
  • 9
  • 13
  • 1
    I think the "modulo arithmetic" you mention (I suppose you are referring to the possible overflows?) are exactly what the OP tries to avoid with FP. Trivially comparing ad with bc is clearly not correct -- how would you go about the exercise? – Peter - Reinstate Monica Feb 16 '15 at 09:15
  • Yes, the problem is the effects of modulo arithmetic. However, I am still curious as to how "ad > bc" should handle negative numbers. – Jonas Feb 16 '15 at 09:15
  • 2
    @Jonas: negative numbers are pretty simple to handle - if only one of the sides is negative then it's obviously less than the other: if they're both negative then you can return a comparison of the absolute/positive values. For example, `x < y` where both negative -> `|x| > |y|`. – Tony Delroy Feb 16 '15 at 09:27
  • Hideous but perhaps hangs together: the problem case is `a * d` and `b * c` overflow, then `(double)a * d` and `double(b) * c` are close (within an epsilon value of each other), so then revisit the `uint64_t` products known to be modulo-2^64: they shouldn't differ by too much so if one side is close to 2^64 and the other close to 0, consider the close-to-0 one larger. – Tony Delroy Feb 16 '15 at 09:58
  • How would I got about the exercise? There are plenty of algorithms for (unsigned) integer multiplication that produce a potential result outside the range of the values being multiplied (e.g. multiplying two 64 bit unsigned integers may produce a 128 bit result). These aren't generally one-line solutions though. – Rob Feb 16 '15 at 13:15
  • An alternative to comparing `ad < bc` is to use the existing subtraction operator, then use the sign of the result. Again, it depends on having sufficient range in the numerator and denominator types. – Toby Speight Aug 15 '17 at 12:02
  • There's not much point to a rational number library that isn't coupled with multi-word arithmetic. Rationals just get too big too fast to do anything interesting in a `long long int`. So you would generally have arbitrary-width multiplication available already. – Sneftel May 01 '18 at 15:26