C 将有理数表示为四舍五入的小数

C 将有理数表示为四舍五入的小数,c,rounding,gmp,C,Rounding,Gmp,使用gmp库和有理数(mpq\u t),我试图将有理数打印出来,作为给定精度的小数点(小数点分隔符后的数字) 我目前的方法是写入char缓冲区,对缓冲区中的数字进行舍入,然后打印出来。这是可行的,但我觉得我做得太复杂了,即: 用除法计算整数部分 将余数乘以10^(prec+1)并除以 将两者都放入char缓冲区 从缓冲区末端返回,对数字进行舍入 将沿途收集的所有额外信息打印出来 可选减号 溢出(例如,精度为3的0.9999实际上是1) 处理额外的零(例如,0.00001) 问题是: 有没

使用gmp库和有理数(
mpq\u t
),我试图将有理数打印出来,作为给定精度的小数点(小数点分隔符后的数字)

我目前的方法是写入
char
缓冲区,对缓冲区中的数字进行舍入,然后打印出来。这是可行的,但我觉得我做得太复杂了,即:

  • 用除法计算整数部分
  • 将余数乘以10^(prec+1)并除以
  • 将两者都放入
    char
    缓冲区
  • 从缓冲区末端返回,对数字进行舍入
  • 将沿途收集的所有额外信息打印出来
    • 可选减号
    • 溢出(例如,精度为3的0.9999实际上是
      1
    • 处理额外的零(例如,0.00001)
问题是:

有没有更好的方法?更简单?我完全错过了什么?

注意,有理数的分子和分母可以是“任意”大的

这里是相关代码,
mpz1
mpz2
属于
mpz_t
类型,并且已经初始化,我正在转换的rational在
mpq1
中:

编辑:此代码中至少有一个错误,但我不想在重新编写时找到它

/* 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 /* '\0' 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;
}

如果你想将一个无符号的有理数舍入到最接近的整数,你可以加0.5,然后只显示整数部分

小数点后1位加0.05

对于小数点后的2位数字,您需要添加0.005


对于小数点后的
n
数字,您将添加
5/(10**(n+1))

仅针对子孙后代,我最终所做的确实与布伦丹的答案一致。由于我已经签署了理性,我做了以下工作(没有详细说明):

  • 1/(2*10^精度)
    添加到正值,或将
    -1/(2*10^精度)
    添加到负值
  • 除法,打印出最后一位数字和尾随的零