Сравнение двух дробей (‹ и др.)

У меня есть две дроби, которые я люблю сравнивать. Они хранятся так:

struct fraction {
    int64_t numerator;
    int64_t denominator;
};

В настоящее время я сравниваю их так:

bool fraction_le(struct fraction a, struct fraction b)
{
    return a.numerator * b.denominator < b.numerator * a.denominator;
}

Это работает нормально, за исключением того, что (64 bit value) * (64 bit value) = (128 bit value) означает, что будет переполнение для числителей и знаменателей, которые слишком далеки от нуля.

Как сделать так, чтобы сравнение работало всегда, даже для абсурдных дробей?

Да, и кстати: дроби всегда хранятся упрощенно, и только числитель может быть отрицательным. Может быть, это входное ограничение делает возможным какой-то алгоритм...


person Jasmijn    schedule 10.11.2012    source источник
comment
Если вы не можете полагаться на 128-битный тип (или произвольную точность), метод непрерывной дроби, который использует Boost (согласно ответу Коса), является лучшим методом.   -  person Daniel Fischer    schedule 11.11.2012
comment
Почему бы просто не сравнить дробное значение? Это может потерять точность, но лучше, чем переполнение.   -  person texasbruce    schedule 11.11.2012


Ответы (6)


Если вы используете GCC, вы можете использовать __int128.

person HelloWorld    schedule 10.11.2012

Вот как это реализует Boost. Код хорошо прокомментирован.

template <typename IntType>
bool rational<IntType>::operator< (const rational<IntType>& r) const
{
    // Avoid repeated construction
    int_type const  zero( 0 );

    // This should really be a class-wide invariant.  The reason for these
    // checks is that for 2's complement systems, INT_MIN has no corresponding
    // positive, so negating it during normalization keeps it INT_MIN, which
    // is bad for later calculations that assume a positive denominator.
    BOOST_ASSERT( this->den > zero );
    BOOST_ASSERT( r.den > zero );

    // Determine relative order by expanding each value to its simple continued
    // fraction representation using the Euclidian GCD algorithm.
    struct { int_type  n, d, q, r; }  ts = { this->num, this->den, this->num /
     this->den, this->num % this->den }, rs = { r.num, r.den, r.num / r.den,
     r.num % r.den };
    unsigned  reverse = 0u;

    // Normalize negative moduli by repeatedly adding the (positive) denominator
    // and decrementing the quotient.  Later cycles should have all positive
    // values, so this only has to be done for the first cycle.  (The rules of
    // C++ require a nonnegative quotient & remainder for a nonnegative dividend
    // & positive divisor.)
    while ( ts.r < zero )  { ts.r += ts.d; --ts.q; }
    while ( rs.r < zero )  { rs.r += rs.d; --rs.q; }

    // Loop through and compare each variable's continued-fraction components
    while ( true )
    {
        // The quotients of the current cycle are the continued-fraction
        // components.  Comparing two c.f. is comparing their sequences,
        // stopping at the first difference.
        if ( ts.q != rs.q )
        {
            // Since reciprocation changes the relative order of two variables,
            // and c.f. use reciprocals, the less/greater-than test reverses
            // after each index.  (Start w/ non-reversed @ whole-number place.)
            return reverse ? ts.q > rs.q : ts.q < rs.q;
        }

        // Prepare the next cycle
        reverse ^= 1u;

        if ( (ts.r == zero) || (rs.r == zero) )
        {
            // At least one variable's c.f. expansion has ended
            break;
        }

        ts.n = ts.d;         ts.d = ts.r;
        ts.q = ts.n / ts.d;  ts.r = ts.n % ts.d;
        rs.n = rs.d;         rs.d = rs.r;
        rs.q = rs.n / rs.d;  rs.r = rs.n % rs.d;
    }

    // Compare infinity-valued components for otherwise equal sequences
    if ( ts.r == rs.r )
    {
        // Both remainders are zero, so the next (and subsequent) c.f.
        // components for both sequences are infinity.  Therefore, the sequences
        // and their corresponding values are equal.
        return false;
    }
    else
    {
        // Exactly one of the remainders is zero, so all following c.f.
        // components of that variable are infinity, while the other variable
        // has a finite next c.f. component.  So that other variable has the
        // lesser value (modulo the reversal flag!).
        return ( ts.r != zero ) != static_cast<bool>( reverse );
    }
}
person Kos    schedule 10.11.2012

Я не понял код в ответе Коса, так что это может быть просто его дублирование.

Как уже упоминали другие люди, есть несколько простых особых случаев, например. b/c > -e/f и -b/c > -e/f, если e/f > b/c. Итак, мы остались со случаем положительных дробей.

Преобразуйте их в смешанные числа, т.е. a b/c и d e/f. Тривиальный случай имеет a != d, поэтому мы предполагаем a == d. Затем мы хотим сравнить b/c с e/f с b ‹ c, e ‹ f. Ну b/c > e/f если f/e > c/b. Они оба больше единицы, поэтому вы можете повторять тест смешанных чисел до тех пор, пока целые части числа не будут отличаться.

person Neil    schedule 10.11.2012

Дело меня заинтриговало, так что вот реализация ответа Нила, возможно, с ошибками :)

#include <stdint.h>
#include <stdlib.h>

typedef struct {

    int64_t num, den;

} frac;

int cmp(frac a, frac b) {

    if (a.num < 0) {

        if (b.num < 0) {

            a.num = -a.num;
            b.num = -b.num;

            return !cmpUnsigned(a, b);
        }

        else return 1;
    }

    else if (0 <= b.num) return cmpUnsigned(a, b);

    else return 0;
}

#define swap(a, b) { int64_t c = a; a = b; b = c; }

int cmpUnsigned(frac a, frac b) {

    int64_t c = a.num / a.den, d = b.num / b.den;

    if (c != d) return c < d;

    a.num = a.num % a.den;
    swap(a.num, a.den);

    b.num = b.num % b.den;
    swap(b.num, b.den);

    return !cmpUnsigned(a, b);
}

main() {

    frac a = { INT64_MAX - 1, INT64_MAX }, b = { INT64_MAX - 3, INT64_MAX };

    printf("%i\n", cmp(a, b));    
}
person HelloWorld    schedule 10.11.2012
comment
если a.num равно минимальному значению int64, -a.num скорее отрицательное, чем положительное - person esse; 18.05.2021

Итак, подписаны только ваши числители.

Особые случаи:

Если числитель a отрицателен, а числитель b положителен, то b больше a. Если числитель b отрицателен, а числитель a положителен, то a больше, чем b.

В противном случае:

Оба ваших числителя имеют одинаковый знак (+/-). Добавьте некоторый логический код или манипуляции с битами, чтобы удалить его, и используйте умножение с uint64_t, чтобы сравнить их. Помните, что если оба числителя отрицательные, то результат сравнения должен быть инвертирован.

person HelloWorld    schedule 10.11.2012
comment
Приведение к uint64_t дает только один дополнительный бит. Как это решает проблему переполнения? - person nemetroid; 11.11.2012
comment
Хотя это немного помогает. Если нет лучшего ответа, я приму это. - person Jasmijn; 11.11.2012

Почему бы просто не сравнить их напрямую как числа с плавающей запятой?

bool fraction_le(struct fraction a, struct fraction b)
{
    return (double)a.numerator / a.denominator < (double)b.numerator / b.denominator;
}
person dbush    schedule 10.02.2018
comment
Поскольку приведение длинного значения к двойному может привести к потере точности. Попробуйте, например. приведение максимального длинного к двойному и обратно. Таким образом, сравнение двух дробей, обе с потенциальной потерей точности, может привести к ошибочному выводу для некоторых входных значений. - person HelloWorld; 18.05.2021