mpq_get_d_nearest

Marc Nieper-Wißkirchen marc.nieper+gnu at gmail.com
Wed Aug 28 09:32:57 CEST 2024


Hi,

There was a discussion on the mailing list roughly ten years ago about
adding a function mpq_get_d_nearest, which would be more useful than
mpq_get_d in many use cases:

https://gmplib.org/list-archives/gmp-devel/2013-April/003223.html

What happened to this suggestion?  Or did it come to nothing?

I attached an attempt to handle subnormal numbers in Paul's code.

Thanks,

Marc

**

#if FLT_RADIX != 2
#error "unsupported float radix"
#endif

double
mpq_get_d_nearest (mpq_t q)
{
  mpz_ptr a = mpq_numref (q);
  mpz_ptr b = mpq_denref (q);
  size_t sa = mpz_sizeinbase (a, 2);
  size_t sb = mpz_sizeinbase (b, 2);
  size_t na, nb;
  mpz_t aa, bb;
  double d;

  /* easy case: |a|, |b| < 2^DBL_MANT_DIG, no overflow nor underflow can
occur */
  if (sa <= DBL_MANT_DIG && sb <= DBL_MANT_DIG)
    return mpz_get_d (a) / mpz_get_d (b);

  /* same if a = m*2^e with m representable on DBL_MANT_DIG bits, idem for
b, but beware
     that both a and b do not give an overflow */
  na = sa - mpz_scan1 (a, 0);
  nb = sb - mpz_scan1 (b, 0);
  if (sa <= DBL_MAX_EXP && na <= DBL_MANT_DIG && sb <= DBL_MAX_EXP && nb <=
DBL_MANT_DIG)
    return mpz_get_d (a) / mpz_get_d (b);

  /* hard case */
  mpz_init (aa);
  mpz_init (bb);

  if (sa >= sb)
    {
      mpz_set (aa, a);
      mpz_mul_2exp (bb, b, sa - sb);
    }
  else
    {
      mpz_mul_2exp (aa, a, sb - sa);
      mpz_set (bb, b);
    }

  /* q = aa/bb*2^(sa-sb) */

  if (mpz_cmpabs (aa, bb) >= 0)
    {
      mpz_mul_2exp (bb, bb, 1);
      sa ++;
    }

  mp_bitcnt_t bits = DBL_MANT_DIG + 1;

  /* handle subnormal numbers */
  mp_bitcnt_t e = sb + DBL_MIN_EXP + 1;
  if (sa < e)
    {
      e -= sa;
      if (e >= bits)
        bits = 1;
      else
        bits -= e;
    }

  mpz_mul_2exp (aa, aa, bits);
  sb += bits;

  mpz_tdiv_qr (aa, bb, aa, bb);

  /* the quotient aa should have exactly BITS bits */

  if (mpz_tstbit (aa, 0) == 0)
    {
    }
  else if (mpz_cmp_ui (bb, 0) != 0)
    {
      if (mpz_sgn (aa) > 0)
        mpz_add_ui (aa, aa, 1);
      else
        mpz_sub_ui (aa, aa, 1);
    }
  else /* mid case: round to even */
    {
      if (mpz_tstbit (aa, 1) == 0)
        {
          if (mpz_sgn (aa) > 0)
            mpz_sub_ui (aa, aa, 1);
          else
            mpz_add_ui (aa, aa, 1);
        }
      else
        {
          if (mpz_sgn (aa) > 0)
            mpz_add_ui (aa, aa, 1);
          else
            mpz_sub_ui (aa, aa, 1);
        }
    }

  d = mpz_get_d (aa); /* exact */
  mpz_clear (aa);
  mpz_clear (bb);
  return ldexp (d, (long) sa - (long) sb);
}

**


More information about the gmp-discuss mailing list