xref: /netbsd-src/external/lgpl3/gmp/dist/mpq/get_d.c (revision 72c7faa4dbb41dbb0238d6b4a109da0d4b236dd4)
1 /* double mpq_get_d (mpq_t src) -- mpq to double, rounding towards zero.
2 
3 Copyright 1995, 1996, 2001-2005, 2018, 2019 Free Software Foundation, Inc.
4 
5 This file is part of the GNU MP Library.
6 
7 The GNU MP Library is free software; you can redistribute it and/or modify
8 it under the terms of either:
9 
10   * the GNU Lesser General Public License as published by the Free
11     Software Foundation; either version 3 of the License, or (at your
12     option) any later version.
13 
14 or
15 
16   * the GNU General Public License as published by the Free Software
17     Foundation; either version 2 of the License, or (at your option) any
18     later version.
19 
20 or both in parallel, as here.
21 
22 The GNU MP Library is distributed in the hope that it will be useful, but
23 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
24 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
25 for more details.
26 
27 You should have received copies of the GNU General Public License and the
28 GNU Lesser General Public License along with the GNU MP Library.  If not,
29 see https://www.gnu.org/licenses/.  */
30 
31 #include <stdio.h>  /* for NULL */
32 #include "gmp-impl.h"
33 #include "longlong.h"
34 
35 
36 /* All that's needed is to get the high 53 bits of the quotient num/den,
37    rounded towards zero.  More than 53 bits is fine, any excess is ignored
38    by mpn_get_d.
39 
40    N_QLIMBS is how many quotient limbs we need to satisfy the mantissa of a
41    double, assuming the highest of those limbs is non-zero.  The target
42    qsize for mpn_tdiv_qr is then 1 more than this, since that function may
43    give a zero in the high limb (and non-zero in the second highest).
44 
45    The use of 8*sizeof(double) in N_QLIMBS is an overestimate of the
46    mantissa bits, but it gets the same result as the true value (53 or 48 or
47    whatever) when rounded up to a multiple of GMP_NUMB_BITS, for non-nails.
48 
49    Enhancements:
50 
51    Use the true mantissa size in the N_QLIMBS formula, to save a divide step
52    in nails.
53 
54    Examine the high limbs of num and den to see if the highest 1 bit of the
55    quotient will fall high enough that just N_QLIMBS-1 limbs is enough to
56    get the necessary bits, thereby saving a division step.
57 
58    Bit shift either num or den to arrange for the above condition on the
59    high 1 bit of the quotient, to save a division step always.  A shift to
60    save a division step is definitely worthwhile with mpn_tdiv_qr, though we
61    may want to reassess this on big num/den when a quotient-only division
62    exists.
63 
64    Maybe we could estimate the final exponent using nsize-dsize (and
65    possibly the high limbs of num and den), so as to detect overflow and
66    return infinity or zero quickly.  Overflow is never very helpful to an
67    application, and can therefore probably be regarded as abnormal, but we
68    may still like to optimize it if the conditions are easy.  (This would
69    only be for float formats we know, unknown formats are not important and
70    can be left to mpn_get_d.)
71 
72    Future:
73 
74    If/when mpn_tdiv_qr supports its qxn parameter we can use that instead of
75    padding n with zeros in temporary space.
76 
77    Alternatives:
78 
79    An alternative algorithm, that may be faster:
80    0. Let n be somewhat larger than the number of significant bits in a double.
81    1. Extract the most significant n bits of the denominator, and an equal
82       number of bits from the numerator.
83    2. Interpret the extracted numbers as integers, call them a and b
84       respectively, and develop n bits of the fractions ((a + 1) / b) and
85       (a / (b + 1)) using mpn_divrem.
86    3. If the computed values are identical UP TO THE POSITION WE CARE ABOUT,
87       we are done.  If they are different, repeat the algorithm from step 1,
88       but first let n = n * 2.
89    4. If we end up using all bits from the numerator and denominator, fall
90       back to a plain division.
91    5. Just to make life harder, The computation of a + 1 and b + 1 above
92       might give carry-out...  Needs special handling.  It might work to
93       subtract 1 in both cases instead.
94 
95    Not certain if this approach would be faster than a quotient-only
96    division.  Presumably such optimizations are the sort of thing we would
97    like to have helping everywhere that uses a quotient-only division. */
98 
99 double
mpq_get_d(mpq_srcptr src)100 mpq_get_d (mpq_srcptr src)
101 {
102   double res;
103   mp_srcptr np, dp;
104   mp_ptr temp;
105   mp_size_t nsize = SIZ(NUM(src));
106   mp_size_t dsize = SIZ(DEN(src));
107   mp_size_t qsize, prospective_qsize, zeros;
108   mp_size_t sign_quotient = nsize;
109   long exp;
110 #define N_QLIMBS (1 + (sizeof (double) + GMP_LIMB_BYTES-1) / GMP_LIMB_BYTES)
111   mp_limb_t qarr[N_QLIMBS + 1];
112   mp_ptr qp = qarr;
113   TMP_DECL;
114 
115   ASSERT (dsize > 0);    /* canonical src */
116 
117   /* mpn_get_d below requires a non-zero operand */
118   if (UNLIKELY (nsize == 0))
119     return 0.0;
120 
121   TMP_MARK;
122   nsize = ABS (nsize);
123   dsize = ABS (dsize);
124   np = PTR(NUM(src));
125   dp = PTR(DEN(src));
126 
127   prospective_qsize = nsize - dsize;       /* from using given n,d */
128   qsize = N_QLIMBS;                        /* desired qsize */
129 
130   zeros = qsize - prospective_qsize;       /* padding n to get qsize */
131   exp = (long) -zeros * GMP_NUMB_BITS;     /* relative to low of qp */
132 
133   /* zero extend n into temporary space, if necessary */
134   if (zeros > 0)
135     {
136       mp_size_t tsize;
137       tsize = nsize + zeros;               /* size for copy of n */
138 
139       temp = TMP_ALLOC_LIMBS (tsize + 1);
140       MPN_FILL (temp, zeros, 0);
141       MPN_COPY (temp + zeros, np, nsize);
142       np = temp;
143       nsize = tsize;
144     }
145   else /* negative zeros means shorten n */
146     {
147       np -= zeros;
148       nsize += zeros;
149 
150       temp = TMP_ALLOC_LIMBS (nsize + 1);
151     }
152 
153   ASSERT (qsize == nsize - dsize);
154   mpn_div_q (qp, np, nsize, dp, dsize, temp);
155 
156   /* strip possible zero high limb */
157   qsize += (qp[qsize] != 0);
158 
159   res = mpn_get_d (qp, qsize, sign_quotient, exp);
160   TMP_FREE;
161   return res;
162 }
163