5 * Copyright (C) 1999 - 2007 Michael C. Ring
7 * Permission to use, copy, and distribute this software and its
8 * documentation for any purpose with or without fee is hereby granted,
9 * provided that the above copyright notice appear in all copies and
10 * that both that copyright notice and this permission notice appear
11 * in supporting documentation.
13 * Permission to modify the software is granted. Permission to distribute
14 * the modified code is granted. Modifications are to be distributed by
15 * using the file 'license.txt' as a template to modify the file header.
16 * 'license.txt' is available in the official MAPM distribution.
18 * This software is provided "as is" without express or implied warranty.
22 * $Id: mapm_exp.c,v 1.37 2007/12/03 01:36:00 mike Exp $
24 * This file contains the EXP function.
26 * $Log: mapm_exp.c,v $
27 * Revision 1.37 2007/12/03 01:36:00 mike
30 * Revision 1.36 2004/06/02 00:29:03 mike
31 * simplify logic in compute_nn
33 * Revision 1.35 2004/05/29 18:29:44 mike
34 * move exp nn calculation into its own function
36 * Revision 1.34 2004/05/21 20:41:01 mike
37 * fix potential buffer overflow
39 * Revision 1.33 2004/02/18 02:46:45 mike
42 * Revision 1.32 2004/02/18 02:41:35 mike
43 * check to make sure 'nn' does not overflow
45 * Revision 1.31 2004/01/01 00:06:38 mike
46 * make a comment more clear
48 * Revision 1.30 2003/12/31 21:44:57 mike
49 * simplify logic in _exp
51 * Revision 1.29 2003/12/28 00:03:27 mike
52 * dont allow 'tmp7' to get too small prior to divide by 256
54 * Revision 1.28 2003/12/27 22:53:04 mike
55 * change 1024 to 512, if input is already small, call
56 * raw_exp directly and return
58 * Revision 1.27 2003/03/30 21:16:37 mike
59 * use global version of log(2) instead of local copy
61 * Revision 1.26 2002/11/03 22:30:18 mike
62 * Updated function parameters to use the modern style
64 * Revision 1.25 2001/08/06 22:07:00 mike
65 * round the 'nn' calculation to the nearest int
67 * Revision 1.24 2001/08/05 21:58:59 mike
68 * make 1 / log(2) constant shorter
70 * Revision 1.23 2001/07/16 19:10:23 mike
71 * add function M_free_all_exp
73 * Revision 1.22 2001/07/10 21:55:36 mike
74 * optimize raw_exp by using fewer digits as the
75 * subsequent terms get smaller
77 * Revision 1.21 2001/02/06 21:20:31 mike
78 * optimize 'nn' calculation (for the future)
80 * Revision 1.20 2001/02/05 21:55:12 mike
81 * minor optimization, use a multiply instead
84 * Revision 1.19 2000/08/22 21:34:41 mike
85 * increase local precision
87 * Revision 1.18 2000/05/18 22:05:22 mike
88 * move _pow to a separate file
90 * Revision 1.17 2000/05/04 23:21:01 mike
93 * Revision 1.16 2000/03/30 21:33:30 mike
94 * change termination of raw_exp to use ints, not MAPM numbers
96 * Revision 1.15 2000/02/05 22:59:46 mike
97 * adjust decimal places on calculation
99 * Revision 1.14 2000/02/04 20:45:21 mike
100 * re-compute log(2) on the fly if we need a more precise value
102 * Revision 1.13 2000/02/04 19:35:14 mike
103 * use just an approx log(2) for the integer divide
105 * Revision 1.12 2000/02/04 16:47:32 mike
106 * use the real algorithm for EXP
108 * Revision 1.11 1999/09/18 01:27:40 mike
109 * if X is 0 on the pow function, return 0 right away
111 * Revision 1.10 1999/06/19 20:54:07 mike
112 * changed local static MAPM to stack variables
114 * Revision 1.9 1999/06/01 22:37:44 mike
115 * adjust decimal places passed to raw function
117 * Revision 1.8 1999/06/01 01:44:03 mike
118 * change threshold from 1000 to 100 for 65536 divisor
120 * Revision 1.7 1999/06/01 01:03:31 mike
121 * vary 'q' instead of checking input against +/- 10 and +/- 40
123 * Revision 1.6 1999/05/15 01:54:27 mike
124 * add check for number of decimal places
126 * Revision 1.5 1999/05/15 01:09:49 mike
127 * minor tweak to POW decimal places
129 * Revision 1.4 1999/05/13 00:14:00 mike
130 * added more comments
132 * Revision 1.3 1999/05/12 23:39:05 mike
133 * change #places passed to sub functions
135 * Revision 1.2 1999/05/10 21:35:13 mike
136 * added some comments
138 * Revision 1.1 1999/05/10 20:56:31 mike
142 #include "m_apm_lc.h"
144 static M_APM MM_exp_log2R
;
145 static M_APM MM_exp_512R
;
146 static int MM_firsttime1
= TRUE
;
148 /****************************************************************************/
149 void M_free_all_exp()
151 if (MM_firsttime1
== FALSE
)
153 m_apm_free(MM_exp_log2R
);
154 m_apm_free(MM_exp_512R
);
156 MM_firsttime1
= TRUE
;
159 /****************************************************************************/
160 void m_apm_exp(M_APM r
, int places
, M_APM x
)
162 M_APM tmp7
, tmp8
, tmp9
;
167 MM_firsttime1
= FALSE
;
169 MM_exp_log2R
= m_apm_init();
170 MM_exp_512R
= m_apm_init();
172 m_apm_set_string(MM_exp_log2R
, "1.44269504089"); /* ~ 1 / log(2) */
173 m_apm_set_string(MM_exp_512R
, "1.953125E-3"); /* 1 / 512 */
176 tmp7
= M_get_stack_var();
177 tmp8
= M_get_stack_var();
178 tmp9
= M_get_stack_var();
180 if (x
->m_apm_sign
== 0) /* if input == 0, return '1' */
182 m_apm_copy(r
, MM_One
);
187 if (x
->m_apm_exponent
<= -3) /* already small enough so call _raw directly */
189 M_raw_exp(tmp9
, (places
+ 6), x
);
190 m_apm_round(r
, places
, tmp9
);
196 From David H. Bailey's MPFUN Fortran package :
198 exp (t) = (1 + r + r^2 / 2! + r^3 / 3! + r^4 / 4! ...) ^ q * 2 ^ n
200 where q = 256, r = t' / q, t' = t - n Log(2) and where n is chosen so
201 that -0.5 Log(2) < t' <= 0.5 Log(2). Reducing t mod Log(2) and
202 dividing by 256 insures that -0.001 < r <= 0.001, which accelerates
203 convergence in the above series.
205 I use q = 512 and also limit how small 'r' can become. The 'r' used
206 here is limited in magnitude from 1.95E-4 < |r| < 1.35E-3. Forcing
207 'r' into a narrow range keeps the algorithm 'well behaved'.
209 ( the range is [0.1 / 512] to [log(2) / 512] )
212 if (M_exp_compute_nn(&nn
, tmp7
, x
) != 0)
214 M_apm_log_error_msg(M_APM_RETURN
,
215 "\'m_apm_exp\', Input too large, Overflow");
222 dplaces
= places
+ 8;
224 /* check to make sure our log(2) is accurate enough */
226 M_check_log_places(dplaces
);
228 m_apm_multiply(tmp8
, tmp7
, MM_lc_log2
);
229 m_apm_subtract(tmp7
, x
, tmp8
);
232 * guarantee that |tmp7| is between 0.1 and 0.9999999....
233 * (in practice, the upper limit only reaches log(2), 0.693... )
238 if (tmp7
->m_apm_sign
!= 0)
240 if (tmp7
->m_apm_exponent
== 0)
244 if (tmp7
->m_apm_sign
>= 0)
247 m_apm_subtract(tmp8
, tmp7
, MM_lc_log2
);
248 m_apm_copy(tmp7
, tmp8
);
253 m_apm_add(tmp8
, tmp7
, MM_lc_log2
);
254 m_apm_copy(tmp7
, tmp8
);
258 m_apm_multiply(tmp9
, tmp7
, MM_exp_512R
);
260 /* perform the series expansion ... */
262 M_raw_exp(tmp8
, dplaces
, tmp9
);
265 * raise result to the 512 power
267 * note : x ^ 512 = (((x ^ 2) ^ 2) ^ 2) ... 9 times
274 m_apm_multiply(tmp9
, tmp8
, tmp8
);
275 m_apm_round(tmp8
, dplaces
, tmp9
);
281 /* now compute 2 ^ N */
283 m_apm_integer_pow(tmp7
, dplaces
, MM_Two
, nn
);
285 m_apm_multiply(tmp9
, tmp7
, tmp8
);
286 m_apm_round(r
, places
, tmp9
);
288 M_restore_stack(3); /* restore the 3 locals we used here */
290 /****************************************************************************/
292 compute int *n = round_to_nearest_int(a / log(2))
293 M_APM b = MAPM version of *n
298 int M_exp_compute_nn(int *n
, M_APM b
, M_APM a
)
308 tmp0
= M_get_stack_var();
309 tmp1
= M_get_stack_var();
311 /* find 'n' and convert it to a normal C int */
312 /* we just need an approx 1/log(2) for this calculation */
314 m_apm_multiply(tmp1
, a
, MM_exp_log2R
);
316 /* round to the nearest int */
318 if (tmp1
->m_apm_sign
>= 0)
320 m_apm_add(tmp0
, tmp1
, MM_0_5
);
321 m_apm_floor(tmp1
, tmp0
);
325 m_apm_subtract(tmp0
, tmp1
, MM_0_5
);
326 m_apm_ceil(tmp1
, tmp0
);
329 kk
= tmp1
->m_apm_exponent
;
332 if ((vp
= (void *)MAPM_MALLOC((kk
+ 16) * sizeof(char))) == NULL
)
334 /* fatal, this does not return */
336 M_apm_log_error_msg(M_APM_FATAL
, "\'M_exp_compute_nn\', Out of memory");
342 m_apm_to_integer_string(cp
, tmp1
);
345 m_apm_set_long(b
, (long)(*n
));
347 kk
= m_apm_compare(b
, tmp1
);
355 /****************************************************************************/
357 calculate the exponential function using the following series :
360 exp(x) == 1 + x + --- + --- + --- + --- ...
364 void M_raw_exp(M_APM rr
, int places
, M_APM xx
)
366 M_APM tmp0
, digit
, term
;
367 int tolerance
, local_precision
, prev_exp
;
370 tmp0
= M_get_stack_var();
371 term
= M_get_stack_var();
372 digit
= M_get_stack_var();
374 local_precision
= places
+ 8;
375 tolerance
= -(places
+ 4);
378 m_apm_add(rr
, MM_One
, xx
);
379 m_apm_copy(term
, xx
);
385 m_apm_set_long(digit
, m1
);
386 m_apm_multiply(tmp0
, term
, xx
);
387 m_apm_divide(term
, local_precision
, tmp0
, digit
);
388 m_apm_add(tmp0
, rr
, term
);
389 m_apm_copy(rr
, tmp0
);
391 if ((term
->m_apm_exponent
< tolerance
) || (term
->m_apm_sign
== 0))
396 local_precision
= local_precision
+ term
->m_apm_exponent
- prev_exp
;
398 if (local_precision
< 20)
399 local_precision
= 20;
402 prev_exp
= term
->m_apm_exponent
;
406 M_restore_stack(3); /* restore the 3 locals we used here */
408 /****************************************************************************/