Представление рациональных чисел в виде округленных десятичных дробей


Используя библиотеку gmp и rationals (mpq_t), я пытаюсь вывести rational, который у меня есть, как десятичную дробь с заданной точностью (цифры после десятичного разделителя).

Мой текущий подход состоит в том, чтобы записать в буфер char, округлить цифры в буфере, а затем распечатать это. Это работает, но у меня есть ощущение, что я делаю это ваааай слишком сложно, а именно:
  • вычислить целочисленную часть путем деления
  • вычислить дробная часть путем умножения остатка на 10^(prec+1) и деления
  • поместите оба в буфер char
  • возвращаемся от конца буфера, делая округление по цифрам
  • распечатайте номер со всей дополнительной информацией, собранной по пути.
    • необязательный знак минус
    • переполнение (так что 0.9999 с точностью 3 фактически будет 1, например)
    • забота о дополнительных нулях (0.00001, для пример)

Вопрос:

Есть ли способ сделать это лучше? Еще проще? Что - то, чего мне совершенно не хватает?

Заметим, что числитель и знаменатель рационального могут быть "сколь угодно" большими.

Вот соответствующий код, mpz1, mpz2 имеют тип mpz_t и уже инициализированы, рациональное я преобразую в mpq1:

Edit : где-то в этом коде есть по крайней мере одна ошибка, но я не чувствую например, найти его, когда я его переписывал.

/* We might need to insert a digit between the sign
 * and the rest of the number:
 * deal with the sign explicitly
 */
int negative = 0;
if (mpz_sgn(mpq_numref(mpq1)) == -1) /* negative number */
    negative = 1;

/* Calculate the integer part and the remainder */
mpz_tdiv_qr(mpz1, mpz2, mpq_numref(mpq1), mpq_denref(mpq1));
if (mpz_cmp_ui(mpz2, 0) == 0) { /* remainder is 0 */
    gmp_printf("%Zd", mpz1);
    return;
}

/* What is the maximum possible length of the decimal fraction? */
size_t max_len =
      mpz_sizeinbase(mpz1, 10) /* length of the string in digits */
    + 1 /* '' terminator */
    /* + 1  possible minus sign: dealing with it explicitly */
    /* + 1  decimal point: dealing with it explicitly */
    + real_precision + 1; /* precision and the extra digit */

/* Prepare the buffer for the string */
/* ... */
/* block of sufficient size at char *str */
char *end = str;
end += gmp_sprintf(end, "%Zd", mpz1);
char *dec_point = end;

/* Calculate the fractional part and write it to the buffer:
 * to round correctly, we need to know one more digit than
 * the precision we are aiming at
 */
mpz_abs(mpz2, mpz2);
mpz_ui_pow_ui(mpz1, 10, real_precision + 1);
mpz_mul(mpz2, mpz2, mpz1);
mpz_tdiv_q(mpz2, mpz2, mpq_denref(mpq1));
end += gmp_sprintf(end, "%Zd", mpz2);
size_t extra_zeros = real_precision + 1 - (end - dec_point);

char *p = end - 1; /* position of the extra digit */
/* Do we need to round up or not? */
int roundup = 0;
if (*p > '4')
    roundup = 1;

/* Propagate the round up back the string of digits */
while (roundup && p != str) {
    --p;
    ++*p;
    if (*p > '9')
        *p = '0';
    else
        roundup = 0;
}

/* Move end back to the first non-zero of the fractional part */
p = end - 2; /* position of the last significant digit */
while (*p == '0' && p != dec_point - 1)
    --p;
end = p + 1; /* the new end */

/* Output the number */
if (negative) /* minus sign */
    putc('-', stdout);

if (roundup) /* overflow */
    putc('1', stdout);

/* Integer part */
p = str;
while (p != dec_point) {
    putc(*p, stdout);
    ++p;
}
if (p == end) /* There is no fractional part after rounding */
    return;

/* Fractional part */
putc('.', stdout);
while (extra_zeros-- != 0)
    putc('0', stdout);
while (p != end) {
    putc(*p, stdout);
    ++p;
}
2 2

2 ответа:

Если бы вы хотели округлить беззнаковое рациональное значение до ближайшего целого числа, вы бы добавили 0.5 и затем отображали только целочисленную часть.

Для 1 цифры после десятичной точки вы бы добавили 0,05.

Для 2 цифр после десятичной точки вы бы добавили 0.005.

Для n цифр после десятичной точки вы бы добавили 5 / ( 10**(n+1) ).

Просто для потомков, то, что я в итоге сделал, действительно соответствует ответу Брендана. Поскольку я подписал рационалы, я делаю следующее (Не вдаваясь в подробности):

  • Добавьте 1/(2*10^precision) к положительному или -1/(2*10^precision) к отрицательному рациональному
  • разделить, распечатать, оставив последнюю цифру и замыкающие нули.