1 /*
2 * intdiv.c - Provide integer div/mod for MPFR.
3 */
4
5 /*
6 * Copyright (C) 2017, 2018, 2021, 2022, the Free Software Foundation, Inc.
7 *
8 * This file is part of GAWK, the GNU implementation of the
9 * AWK Programming Language.
10 *
11 * GAWK is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * GAWK is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include <stdio.h>
31 #include <assert.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <math.h>
36
37 #include <sys/types.h>
38 #include <sys/stat.h>
39
40 #include "gawkapi.h"
41
42 #ifdef HAVE_MPFR
43 #include <gmp.h>
44 #include <mpfr.h>
45 #ifndef MPFR_RNDZ
46 /* for compatibility with MPFR 2.X */
47 #define MPFR_RNDZ GMP_RNDZ
48 #endif
49 #endif
50
51 #include "gettext.h"
52 #define _(msgid) gettext(msgid)
53 #define N_(msgid) msgid
54
55 static const gawk_api_t *api; /* for convenience macros to work */
56 static awk_ext_id_t ext_id;
57 static const char *ext_version = "intdiv extension: version 1.0";
58 static awk_bool_t (*init_func)(void) = NULL;
59
60 int plugin_is_GPL_compatible;
61
62 /* double_to_int --- get the integer part of a double */
63
64 static double
65 double_to_int(double d)
66 {
67 if (d >= 0)
68 d = floor(d);
69 else
70 d = ceil(d);
71 return d;
72 }
73
74 /* array_set_number --- set an array element to a numeric value */
75
76 static void
77 array_set_number(awk_array_t array, const char *sub, size_t sublen, double num)
78 {
79 awk_value_t index, tmp;
80
81 set_array_element(array, make_const_string(sub, sublen, & index), make_number(num, & tmp));
82 }
83
84 #ifdef HAVE_MPFR
85
86 /* mpz_conv --- convert an awk_value_t to an MPZ value */
87
88 static mpz_ptr
89 mpz_conv(const awk_value_t *arg, mpz_ptr tmp)
90 {
91 switch (arg->num_type) {
92 case AWK_NUMBER_TYPE_MPZ:
93 return arg->num_ptr;
94 case AWK_NUMBER_TYPE_MPFR:
95 if (! mpfr_number_p(arg->num_ptr))
96 return NULL;
97 mpz_init(tmp);
98 mpfr_get_z(tmp, arg->num_ptr, MPFR_RNDZ);
99 return tmp;
100 case AWK_NUMBER_TYPE_DOUBLE: /* can this happen? */
101 mpz_init(tmp);
102 mpz_set_d(tmp, double_to_int(arg->num_value));
103 return tmp;
104 default: /* should never happen */
105 fatal(ext_id, _("intdiv: invalid numeric type `%d'"), arg->num_type);
106 return NULL;
107 }
108 }
109
110 /* array_set_mpz --- set an array element to an MPZ value */
111
112 static void
113 array_set_mpz(awk_array_t array, const char *sub, size_t sublen, mpz_ptr num)
114 {
115 awk_value_t index, tmp;
116
117 set_array_element(array, make_const_string(sub, sublen, & index), make_number_mpz(num, & tmp));
118 }
119
120 #endif
121
122 /* do_intdiv --- do integer division, return quotient and remainder in dest array */
123
124 /*
125 * We define the semantics as:
126 * numerator = int(numerator)
127 * denominator = int(denonmator)
128 * quotient = int(numerator / denomator)
129 * remainder = int(numerator % denomator)
130 */
131
132 static awk_value_t *
133 do_intdiv(int nargs, awk_value_t *result, struct awk_ext_func *unused)
134 {
135 awk_value_t nv, dv, array_param;
136 awk_array_t array;
137
138 if (! get_argument(0, AWK_NUMBER, & nv)) {
139 warning(ext_id, _("intdiv: first argument must be numeric"));
140 return make_number(-1, result);
141 }
142 if (! get_argument(1, AWK_NUMBER, & dv)) {
143 warning(ext_id, _("intdiv: second argument must be numeric"));
144 return make_number(-1, result);
145 }
146 if (! get_argument(2, AWK_ARRAY, & array_param)) {
147 warning(ext_id, _("intdiv: third argument must be an array"));
148 return make_number(-1, result);
149 }
150 array = array_param.array_cookie;
151 clear_array(array);
152
153 #ifdef HAVE_MPFR
154 if (nv.num_type == AWK_NUMBER_TYPE_DOUBLE && dv.num_type == AWK_NUMBER_TYPE_DOUBLE)
155 #endif
156 {
157 /* regular precision */
158 double num, denom, quotient, remainder;
159
160 #ifndef HAVE_MPFR
161 if (nv.num_type != AWK_NUMBER_TYPE_DOUBLE || dv.num_type != AWK_NUMBER_TYPE_DOUBLE) {
162 static int warned = 0;
163 if (!warned) {
164 warning(ext_id, _("intdiv: MPFR arguments converted to IEEE because this extension was not compiled with MPFR support; loss of precision may occur"));
165 warned = 1;
166 }
167 }
168 #endif
169 num = double_to_int(nv.num_value);
170 denom = double_to_int(dv.num_value);
171
172 if (denom == 0.0) {
173 warning(ext_id, _("intdiv: division by zero attempted"));
174 return make_number(-1, result);
175 }
176
177 quotient = double_to_int(num / denom);
178 #ifdef HAVE_FMOD
179 remainder = fmod(num, denom);
180 #else /* ! HAVE_FMOD */
181 (void) modf(num / denom, & remainder);
182 remainder = num - remainder * denom;
183 #endif /* ! HAVE_FMOD */
184 remainder = double_to_int(remainder);
185
186 array_set_number(array, "quotient", 8, quotient);
187 array_set_number(array, "remainder", 9, remainder);
188 }
189 #ifdef HAVE_MPFR
190 else {
191 /* extended precision */
192 mpz_ptr numer, denom;
193 mpz_t numer_tmp, denom_tmp;
194 mpz_t quotient, remainder;
195
196 /* convert numerator and denominator to integer */
197 if (!(numer = mpz_conv(&nv, numer_tmp))) {
198 warning(ext_id, _("intdiv: numerator is not finite"));
199 return make_number(-1, result);
200 }
201 if (!(denom = mpz_conv(&dv, denom_tmp))) {
202 warning(ext_id, _("intdiv: denominator is not finite"));
203 if (numer == numer_tmp)
204 mpz_clear(numer);
205 return make_number(-1, result);
206 }
207 if (mpz_sgn(denom) == 0) {
208 warning(ext_id, _("intdiv: division by zero attempted"));
209 if (numer == numer_tmp)
210 mpz_clear(numer);
211 if (denom == denom_tmp)
212 mpz_clear(denom);
213 return make_number(-1, result);
214 }
215
216 mpz_init(quotient);
217 mpz_init(remainder);
218
219 /* do the division */
220 mpz_tdiv_qr(quotient, remainder, numer, denom);
221
222 array_set_mpz(array, "quotient", 8, quotient);
223 array_set_mpz(array, "remainder", 9, remainder);
224
225 /* release temporary variables */
226 if (numer == numer_tmp)
227 mpz_clear(numer);
228 if (denom == denom_tmp)
229 mpz_clear(denom);
230 }
231 #endif
232
233 return make_number(0, result);
234 }
235
236 static awk_ext_func_t func_table[] = {
237 { "intdiv", do_intdiv, 3, 3, awk_false, NULL },
238 };
239
240 /* define the dl_load function using the boilerplate macro */
241
242 dl_load_func(func_table, intdiv, "")