KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > quercus > lib > BcmathModule


1 /*
2  * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
3  *
4  * This file is part of Resin(R) Open Source
5  *
6  * Each copy or derived work must preserve the copyright notice and this
7  * notice unmodified.
8  *
9  * Resin Open Source is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Resin Open Source is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
17  * of NON-INFRINGEMENT. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Resin Open Source; if not, write to the
22  *
23  * Free Software Foundation, Inc.
24  * 59 Temple Place, Suite 330
25  * Boston, MA 02111-1307 USA
26  *
27  * @author Sam
28  */

29
30 package com.caucho.quercus.lib;
31
32 import com.caucho.quercus.annotation.Optional;
33 import com.caucho.quercus.env.DoubleValue;
34 import com.caucho.quercus.env.Env;
35 import com.caucho.quercus.env.LongValue;
36 import com.caucho.quercus.env.StringValue;
37 import com.caucho.quercus.env.StringValueImpl;
38 import com.caucho.quercus.env.Value;
39 import com.caucho.quercus.module.AbstractQuercusModule;
40 import com.caucho.util.L10N;
41
42 import java.math.BigDecimal JavaDoc;
43 import java.math.BigInteger JavaDoc;
44 import java.math.MathContext JavaDoc;
45 import java.math.RoundingMode JavaDoc;
46 import java.util.HashMap JavaDoc;
47 import java.util.Map JavaDoc;
48
49 /**
50  * PHP math routines.
51  */

52 public class BcmathModule extends AbstractQuercusModule {
53   private static final L10N L = new L10N(BcmathModule.class);
54
55   private static final BigDecimal JavaDoc ZERO = BigDecimal.ZERO;
56   private static final BigDecimal JavaDoc ONE = BigDecimal.ONE;
57   private static final BigDecimal JavaDoc TWO = new BigDecimal JavaDoc(2);
58   private static final int SQRT_MAX_ITERATIONS = 50;
59
60   private static final HashMap JavaDoc<String JavaDoc,StringValue> _iniMap
61     = new HashMap JavaDoc<String JavaDoc,StringValue>();
62
63   static {
64     addIni(_iniMap, "bcmath.scale", "0", PHP_INI_ALL);
65   }
66
67   public String JavaDoc []getLoadedExtensions()
68   {
69     return new String JavaDoc[] { "bcmath" };
70   }
71
72   public Map JavaDoc<String JavaDoc,StringValue> getDefaultIni()
73   {
74     return _iniMap;
75   }
76
77   private static BigDecimal JavaDoc toBigDecimal(Value value)
78   {
79     try {
80       if (value instanceof StringValue)
81         return new BigDecimal JavaDoc(value.toString());
82       if (value instanceof DoubleValue)
83         return new BigDecimal JavaDoc(value.toDouble());
84       else if (value instanceof LongValue)
85         return new BigDecimal JavaDoc(value.toLong());
86       else
87         return new BigDecimal JavaDoc(value.toString());
88     }
89     catch (NumberFormatException JavaDoc ex) {
90       return ZERO;
91     }
92     catch (IllegalArgumentException JavaDoc ex) {
93       return ZERO;
94     }
95   }
96
97   private static int calculateScale(Env env, int scale)
98   {
99     if (scale < 0) {
100       Value iniValue = env.getIni("bcmath.scale");
101
102       if (iniValue != null)
103         scale = iniValue.toInt();
104     }
105
106     if (scale < 0)
107       scale = 0;
108
109     return scale;
110   }
111
112   /**
113    * Add two arbitrary precision numbers.
114    *
115    * The optional scale indicates the number of decimal digits to include in
116    * the result, the default is the value of a previous call to {@link #bcscale}
117    * or the value of the ini variable "bcmath.scale".
118    */

119   public static String JavaDoc bcadd(Env env, Value value1, Value value2, @Optional("-1") int scale)
120   {
121     scale = calculateScale(env, scale);
122
123     BigDecimal JavaDoc bd1 = toBigDecimal(value1);
124     BigDecimal JavaDoc bd2 = toBigDecimal(value2);
125
126     BigDecimal JavaDoc bd = bd1.add(bd2);
127
128     bd = bd.setScale(scale, RoundingMode.DOWN);
129
130     return bd.toPlainString();
131   }
132
133   /**
134    * Compare two arbitrary precision numbers, return -1 if value 1 < value2,
135    * 0 if value1 == value2, 1 if value1 > value2.
136    *
137    * The optional scale indicates the number of decimal digits to include in
138    * comparing the values, the default is the value of a previous call to
139    * {@link #bcscale} or the value of the ini variable "bcmath.scale".
140    */

141   public static int bccomp(Env env, Value value1, Value value2, @Optional("-1") int scale)
142   {
143     scale = calculateScale(env, scale);
144
145     BigDecimal JavaDoc bd1 = toBigDecimal(value1);
146     BigDecimal JavaDoc bd2 = toBigDecimal(value2);
147
148     bd1 = bd1.setScale(scale, RoundingMode.DOWN);
149     bd2 = bd2.setScale(scale, RoundingMode.DOWN);
150
151     return bd1.compareTo(bd2);
152   }
153
154   /**
155    * Divide one arbitrary precision number (value1) by another (value2).
156    *
157    * A division by zero results in a warning message and a return value of null.
158    *
159    * The optional scale indicates the number of decimal digits to include in
160    * the result, the default is the value of a previous call to {@link #bcscale}
161    * or the value of the ini variable "bcmath.scale".
162    */

163   public static String JavaDoc bcdiv(Env env, Value value1, Value value2, @Optional("-1") int scale)
164   {
165     scale = calculateScale(env, scale);
166
167     BigDecimal JavaDoc bd1 = toBigDecimal(value1);
168     BigDecimal JavaDoc bd2 = toBigDecimal(value2);
169
170     if (bd2.compareTo(ZERO) == 0) {
171       env.warning(L.l("division by zero"));
172       return null;
173     }
174
175     BigDecimal JavaDoc result;
176
177     if (scale > 0) {
178       result = bd1.divide(bd2, scale + 2, RoundingMode.DOWN);
179     }
180     else {
181       result = bd1.divide(bd2, 2, RoundingMode.DOWN);
182     }
183
184     result = result.setScale(scale, RoundingMode.DOWN);
185
186     return result.toPlainString();
187   }
188
189   /**
190    * Return the modulus of an aribtrary precison number.
191    * The returned number is always a whole number.
192    *
193    * A modulus of 0 results in a division by zero warning message and a
194    * return value of null.
195    */

196   public static String JavaDoc bcmod(Env env, Value value, Value modulus)
197   {
198     BigDecimal JavaDoc bd1 = toBigDecimal(value).setScale(0, RoundingMode.DOWN);
199     BigDecimal JavaDoc bd2 = toBigDecimal(modulus).setScale(0, RoundingMode.DOWN);
200
201     if (bd2.compareTo(ZERO) == 0) {
202       env.warning(L.l("division by zero"));
203       return null;
204     }
205
206     BigDecimal JavaDoc bd = bd1.remainder(bd2, MathContext.DECIMAL128);
207
208     // scale is always 0 in php
209
bd = bd.setScale(0, RoundingMode.DOWN);
210
211     return bd.toPlainString();
212   }
213
214   /**
215    * Multiply two arbitrary precision numbers.
216    *
217    * The optional scale indicates the number of decimal digits to include in
218    * the result, the default is the value of a previous call to {@link #bcscale}
219    * or the value of the ini variable "bcmath.scale".
220    */

221   public static String JavaDoc bcmul(Env env, Value value1, Value value2, @Optional("-1") int scale)
222   {
223     scale = calculateScale(env, scale);
224
225     BigDecimal JavaDoc bd1 = toBigDecimal(value1);
226     BigDecimal JavaDoc bd2 = toBigDecimal(value2);
227
228     BigDecimal JavaDoc bd = bd1.multiply(bd2);
229
230     // odd php special case for 0, scale is ignored:
231
if (bd.compareTo(ZERO) == 0) {
232       if (scale > 0)
233         return "0.0";
234       else
235         return "0";
236     }
237
238     bd = bd.setScale(scale, RoundingMode.DOWN);
239     bd = bd.stripTrailingZeros();
240
241     return bd.toPlainString();
242   }
243
244   /**
245    * Raise one arbitrary precision number (base) to the power of another (exp).
246    *
247    * exp must be a whole number. Negative exp is supported.
248    *
249    * The optional scale indicates the number of decimal digits to include in
250    * the result, the default is the value of a previous call to {@link #bcscale}
251    * or the value of the ini variable "bcmath.scale".
252    */

253   public static String JavaDoc bcpow(Env env, Value base, Value exp, @Optional("-1") int scale)
254   {
255     scale = calculateScale(env, scale);
256
257     BigDecimal JavaDoc bd1 = toBigDecimal(base);
258     BigDecimal JavaDoc bd2 = toBigDecimal(exp);
259
260     if (bd2.scale() > 0)
261       env.warning("fractional exponent not supported");
262
263     int exponent = bd2.toBigInteger().intValue();
264
265     if (exponent == 0)
266       return "1";
267
268     boolean isNeg;
269
270     if (exponent < 0) {
271       isNeg = true;
272       exponent *= -1;
273     }
274     else
275       isNeg = false;
276
277     BigDecimal JavaDoc bd = bd1.pow(exponent);
278
279     if (isNeg)
280       bd = ONE.divide(bd, scale + 2, RoundingMode.DOWN);
281
282     bd = bd.setScale(scale, RoundingMode.DOWN);
283
284     if (bd.compareTo(BigDecimal.ZERO) == 0)
285       return "0";
286
287     bd = bd.stripTrailingZeros();
288
289     return bd.toPlainString();
290   }
291
292   /**
293    * Raise one arbitrary precision number (base) to the power of another (exp),
294    * and then return the modulus.
295    * The returned number is always a whole number.
296    *
297    * exp must be a whole number. Negative exp is supported.
298    *
299    * The optional scale indicates the number of decimal digits to include in
300    * the pow calculation, the default is the value of a previous call to {@link #bcscale}
301    * or the value of the ini variable "bcmath.scale".
302    */

303   public static String JavaDoc bcpowmod(Env env, Value base, Value exp, Value modulus, @Optional("-1") int scale)
304   {
305     scale = calculateScale(env, scale);
306
307     // XXX: this is inefficient, s/b fast-exponentiation
308
String JavaDoc pow = bcpow(env, base, exp, scale);
309
310     if (pow == null)
311       return null;
312
313     return bcmod(env, new StringValueImpl(pow), modulus);
314   }
315
316
317   /**
318    * Set the default scale to use for subsequent calls to bcmath functions.
319    * The scale is the number of decimal points to include in the string that
320    * results from bcmath calculations.
321    *
322    * A default scale set with this function overrides the value of the
323    * "bcmath.scale" ini variable.
324    */

325   public static boolean bcscale(Env env, int scale)
326   {
327     env.setIni("bcmath.scale", String.valueOf(scale));
328
329     return true;
330   }
331
332   /**
333    * Return the square root of an arbitrary precision number.
334    *
335    * A negative operand results in a warning message and a return value of null.
336    *
337    * The optional scale indicates the number of decimal digits to include in
338    * the result, the default is the value of a previous call to {@link #bcscale}
339    * or the value of the ini variable "bcmath.scale".
340    */

341   public static String JavaDoc bcsqrt(Env env, Value operand, @Optional("-1") int scale)
342   {
343     scale = calculateScale(env, scale);
344
345     BigDecimal JavaDoc value = toBigDecimal(operand);
346
347     int compareToZero = value.compareTo(ZERO);
348
349     if (compareToZero < 0) {
350       env.warning(L.l("square root of negative number"));
351       return null;
352     }
353     else if (compareToZero == 0) {
354       return "0";
355     }
356
357     int compareToOne = value.compareTo(ONE);
358
359     if (compareToOne == 0)
360       return "1";
361
362     // newton's algorithm
363

364     int cscale;
365
366     // initial guess
367

368     BigDecimal JavaDoc initialGuess;
369
370     if (compareToOne < 1) {
371       initialGuess = ONE;
372       cscale = value.scale();
373     }
374     else {
375       BigInteger JavaDoc integerPart = value.toBigInteger();
376
377       int length = integerPart.toString().length();
378
379       if ((length % 2) == 0)
380         length--;
381
382       length /= 2;
383
384       initialGuess = ONE.movePointRight(length);
385
386       cscale = Math.max(scale, value.scale()) + 2;
387     }
388
389     // iterate
390

391     BigDecimal JavaDoc guess = initialGuess;
392
393     BigDecimal JavaDoc lastGuess;
394
395     for (int iteration = 0; iteration < SQRT_MAX_ITERATIONS; iteration++) {
396       lastGuess = guess;
397       guess = value.divide(guess, cscale, RoundingMode.DOWN);
398       guess = guess.add(lastGuess);
399       guess = guess.divide(TWO, cscale, RoundingMode.DOWN);
400
401       if (lastGuess.equals(guess)) {
402           break;
403       }
404     }
405
406     value = guess;
407
408     value = value.setScale(scale, RoundingMode.DOWN);
409
410     return value.toPlainString();
411   }
412
413   /**
414    * Subtract arbitrary precision number (value2) from another (value1).
415    *
416    * The optional scale indicates the number of decimal digits to include in
417    * the result, the default is the value of a previous call to {@link #bcscale}
418    * or the value of the ini variable "bcmath.scale".
419    */

420   public static String JavaDoc bcsub(Env env, Value value1, Value value2, @Optional("-1") int scale)
421   {
422     scale = calculateScale(env, scale);
423
424     BigDecimal JavaDoc bd1 = toBigDecimal(value1);
425     BigDecimal JavaDoc bd2 = toBigDecimal(value2);
426
427     BigDecimal JavaDoc bd = bd1.subtract(bd2);
428
429     bd = bd.setScale(scale, RoundingMode.DOWN);
430
431     return bd.toPlainString();
432   }
433 }
434
Popular Tags