Reputation:
Using the gmp library and rationals (mpq_t
), I am trying to print out the rational I have as a decimal fraction up to a given precision (digits after the decimal separator).
My current approach is to write to a char
buffer, do the rounding on the digits in the buffer, then print this out. It works, but I have the feeling that I am doint it waaaay too complicated, namely:
char
buffer1
, for example)The question:
Is there a way of doing this better? More simple? Something I am missing completely?
Note that the numerator and denominator of the rational can be "arbitrarily" big.
Here is the relevant code, mpz1
, mpz2
are of type mpz_t
and are already initialized, the rational I am converting is in mpq1
:
edit: there is at least one error somewhere in this code, but I don't feel like finding it as I re-wrote it anyway.
/* 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;
}
Upvotes: 2
Views: 189
Reputation:
Just for posterity, what I ended up doing is indeed along the lines of Brendan's answer. As I have signed rationals, I do the following (without going in detail):
1/(2*10^precision)
to the positive, or -1/(2*10^precision)
to the negative rationalUpvotes: 0
Reputation: 37222
If you wanted to round an unsigned rational value to the nearest integer you'd add 0.5 and then only display the integer part.
For 1 digit after the decimal point you'd add 0.05.
For 2 digits after the decimal point you'd add 0.005.
For n
digits after the decimal point you'd add 5 / ( 10**(n+1) )
.
Upvotes: 1