MathOps.java

/*
** Module   : MathOps.java
** Abstract : Progress 4GL compatible MathOps object
**
** Copyright (c) 2004-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
** 001 GES 20050519   @21249 Created initial version with full support
**                           for all Progress math processing including
**                           all functions and arithmetic operators. 
** 002 GES 20050608   @21463 Minor fixes and additions.
** 003 GES 20050830   @22386 Added error condition processing.
** 004 GES 20060123   @24018 Minor JavaDoc change.
** 005 GES 20060412   @25490 Added negate() signatures.
** 006 NVS 20060605   @26955 Fixed divide() method when it returns an
**                           unknown value.
** 007 GES 20070321   @32507 Javadoc improvements.
** 008 ECF 20070608   @34010 Implemented decimal math using BigDecimal
**                           instead of double wherever possible. Methods
**                           which use double are maintained for
**                           compatibility.
** 009 ECF 20070618   @34091 Added a variant of round() which accepts a
**                           BigDecimal value and a precision.
** 010 ECF 20070627   @34281 Removed improper divide() optimization for
**                           integers. In cases where both operands are
**                           integers, we were doing integer division,
**                           when we needed to do fractional division.
**                           Also modified modulo() implementation to use
**                           BigDecimal.
** 011 WCP 20071008   @35397 Modulo implementation in 4GL has a peculiar
**                           behavior (incorrect) when a negative number
**                           is given as the first operand. Both the
**                           primitive and BigDecimal forms of the
**                           operator needed to be updated to be
**                           compatible.
** 012 ECF 20071008   @35398 Re-implemented pow(NumberType, NumberType) to
**                           use BigDecimal when possible. Synchronized
**                           access to random number generator. Optimized
**                           modulo(NumberType, NumberType) slightly by
**                           re-using decimal.ZERO constant.
** 013 ECF 20080306   @37463 Fixed defect in random(). Computed result was
**                           able to go beyond low end of range by one.
** 014 ECF 20080330   @37729 Added optimized variants of modulo(). In the
**                           common case of integral parameters, we avoid
**                           the considerable overhead of BigDecimal.
** 015 GES 20080415   @38017 Changed to BigDecimal(int) constructor
**                           instead of using BigDecimal(String) for the
**                           same case. This constructor is new and should
**                           be more performant.
** 016 GES 20080603   @38522 Fix for a certain plus() case which was
**                           doubling operand 2 instead of adding the
**                           two operands.
** 017 GES 20090422   @41911 Converted to standard string formatting.
** 018 SIY 20100408   @44803 Fixed minus() method.
** 019 OM  20121219          Added support for int64 ops.
** 020 OM  20130402          Switched all intermediary results to 64 bit integers.
** 021 GES 20130523          Fixed random built-in function since the long that is returned
**                           can be negative which leaves the modulo result as negative and
**                           that broke the algorithm (it would result in values outside of
**                           the requested range). Instead we now take the absolute value
**                           of the generated random number. In addition, the initialization
**                           of the generator was unsafe and that was fixed.
** 022 HC  20140202          BigDecimal-based sqrt implementation.
** 023 HC  20140212          BigDecimal-based log implementation.
** 024 HC  20140219          BigDecimal-based pow implementation.
** 025 HC  20140227          Error handling fix in pow(BigDecimal, BigDecimal).
** 026 OM  20151006          Small particular hack to handle _POLY arguments.
** 027 EVL 20160224          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 028 CA  20181221          Added a ROUND variant; added integer variants for plus and minus.
**     GES 20181228          Added integer variant for multiply.
** 029 CA  20190520          Added truncate(NumberType).
** 030 CA  20200503          MathOps.round must return a decimal, not double.
**     CA  20210216          Arithmetic operations are always int64.
**     IAS 20210101          Return UNKNOWN for UNKNOWN arguments in the 'round' method
**     CA  20211118          Added round(NumberType, int64).
**     TJD 20220504 Java 11 compatibility minor changes
** 031 ICP 20250123          Used int64.of to leverage caches instances.
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.util;

import java.math.*;
import java.util.Random;

/**
 * A class that provides Progress 4GL compatible math functions and arithmetic
 * operators.  This class provides all functionality as statics and has no instance data.
 * <p>
 * The following is the mapping of Progress language features to the
 * corresponding feature in this class:
 * <p>
 * <pre>
 * +        operator                 {@link #plus(NumberType,NumberType)}
 * +        operator                 {@link #plus(int64,int64)}
 * -        operator                 {@link #minus(NumberType,NumberType)}
 * -        operator                 {@link #minus(int64,int64)}
 * -        operator (unary minus)   {@link #negate(decimal)}
 * -        operator (unary minus)   {@link #negate(int64)}
 * *        operator                 {@link #multiply(NumberType,NumberType)}
 * *        operator                 {@link #multiply(int64,int64)}
 * /        operator                 {@link #divide(NumberType,NumberType)}
 * MODULO   operator                 {@link #modulo(NumberType,NumberType)}
 * absolute function                 {@link #abs(decimal)}
 * absolute function                 {@link #abs(int64)}
 * log      function                 {@link #log(NumberType,NumberType)}
 * exp      function                 {@link #pow(NumberType,NumberType)}
 * random   function                 {@link #random(int64,int64)}
 * round    function                 {@link #round(decimal,int64)} 
 * sqrt     function                 {@link #sqrt(NumberType)}
 * truncate function                 {@link #truncate(decimal,int64)}
 * </pre>
 * <p>
 * The above methods all return an instance of {@link decimal} or {@link int64} which allows the 
 * Progress unknown value to be properly handled.  Overloaded versions of the operator and 
 * function methods exist which handle different combinations of primitive type parameters, 
 * allowing the resulting code to be implemented without constant construction of
 * the wrapper types for all parameters.
 * The return value for the above functions are usually determined by their parameters. Usually,
 * if there is at least one of decimal type, the result is of decimal type. The exceptions are 
 * <code>modulo</code> and <code>random</code>, for which the arguments are first rounded to 
 * their integer value and then the operation take place returning an integer and divide that 
 * will always return a decimal.
 * <p>
 * As <code>integer</code> class is derived from <code>int64</code>, the above operations which 
 * have <code>integer</code> parameters will also work on <code>integer</code> arguments.
 * <br><strong>Note:</strong><br>
 * Progress documentation that mention that if both argument of an operation are
 * <code>integer</code> the result will also be <code>integer</code>.
 * We have found no evidence of that and we cannot find any way to actually prove that the 4GL
 * will emit integer in practice. In all our testcases any 32-bit overflow will silently yield
 * an int64 and we cannot find any use case where integer is required and int64 makes things fail.
 * Even the <code>-noint64</code> mode has no effect on the return type 4GL built-in functions, 
 * they can return values not representable on 32 bits.
 * <p>
 * This class implements unknown value logic for all operators and functions.
 * Generally, if any parameter of the these methods is unknown, the result is unknown.
 * <p>
 * @author    GES
 */
public final class MathOps
{
   /** Largest mantissa that can be safely represented without digit loss. */
   private static final double MAX_SAFE = 17976931348623157.0D;
   
   /** The maximum number of iterations for the converging SQRT algorithm. */
   private static final int SQRT_MAX_ITERATIONS = 50;
   
   /**
    * Natural logarithm of 10. The number should be at least of the same precision as
    * {@linkplain MathOps#LOG_PRECISION}. Used for the log implementation.
    */
   private static final BigDecimal LOG_10 = new BigDecimal("2.3025850929940456840179914546844");
   
   /** The length of the hyperbolic tangent function series used for the log implementation. */
   private static final int LOG_ITERATIONS = 20;
   
   /** The internal precision of the log implementation derived from {@linkplain decimal#MAX_SCALE}. */
   private static final int LOG_PRECISION = decimal.MAX_SCALE * 2;

   /** Math context used by the exponentiation algorithm. */ 
   private static final MathContext EXP_MCONTEXT = new MathContext(decimal.MAX_SCALE * 2, RoundingMode.HALF_UP);
   
   /**
    * Cached random number generator.
    * <p>
    * Note: this is safe (and actually it is preferred) to be JVM-wide 
    * (static) rather than a context-local instance.  This is true since 
    * any use of this from multiple threads will have a much more random
    * result than if the usage was only on one thread.
    */
   private static Random generator = new Random();
   
   /**
    * Return the absolute value of the passed numeric parameter.
    *
    * @param    d
    *           The number on which to take the absolute value.
    *
    * @return   The resulting absolute value or <code>unknown value</code>
    *           when the input is the <code>unknown value</code>.
    */
   public static decimal abs(decimal d)
   {
      if (d.isUnknown())
      {
         return new decimal();
      }
      
      return new decimal(d.toBigDecimal().abs());
   }
   
   /**
    * Return the absolute value of the passed numeric parameter.
    *
    * @param    d
    *           The number on which to take the absolute value.
    *
    * @return   The resulting absolute value.
    */
   public static decimal abs(double d)
   {
      return new decimal(Math.abs(d));
   }
   
   /**
    * Return the absolute value of the passed numeric parameter.
    *
    * @param    i
    *           The number on which to take the absolute value.
    *
    * @return   The resulting absolute value or <code>unknown value</code>
    *           when the input is the <code>unknown value</code>.
    */
   public static int64 abs(int64 i)
   {
      if (i.isUnknown())
      {
         return  int64.UNKNOWN;
      }
      
      return abs(i.longValue());
   }

   /**
    * Return the absolute value of the passed numeric parameter.
    *
    * @param    i
    *           The number on which to take the absolute value.
    *
    * @return   The resulting absolute value.
    */
   public static int64 abs(long i)
   {
      return int64.of(Math.abs(i));
   }

   /**
    * Calculate the natural logarithm of a number (base e).
    *
    * @param   num
    *          The number for which the logarithm should be computed.
    *
    * @return  The logarithmic value or the <code>unknown value</code> when input is the
    *          <code>unknown</code> value or not strictly positive.
    */
   public static decimal log(NumberType num)
   {
      if (num == null)
      {
         return new decimal();
      }
      
      return new decimal(log(num.toBigDecimal()));
   }

   /**
    * Calculate the logarithm of a number in a specific base.
    *
    * @param   num
    *          The number for which the logarithm should be computed.
    * @param   base
    *          The logarithm base.
    *
    * @return The logarithmic value or <code>unknown</code> value when any input is <code>null</code>
    *         value or when <code>base</code> is not strictly positive or 1 or when <code>num</code>
    *         is not strictly positive.
    */
   public static decimal log(NumberType num, NumberType base)
   {
      if (num == null || base == null)
      {
         return new decimal();
      }
      
      return new decimal(log(num.toBigDecimal(), base.toBigDecimal()));
   }

   /**
    * Calculate the logarithm of a number in a specific base.
    *
    * @param   num
    *          The number for which the logarithm should be computed.
    * @param   base
    *          The logarithm base.
    *
    * @return The logarithmic value or <code>unknown</code> value when any input is <code>null</code>
    *         value or when <code>base</code> is not strictly positive or 1 or when <code>num</code>
    *         is not strictly positive.
    */
   public static decimal log(NumberType num, double base)
   {
      if (num == null)
      {
         return new decimal();
      }
      
      return new decimal(log(num.toBigDecimal(), new BigDecimal(base)));
   }

   /**
    * Calculate the logarithm of a number in a specific base.
    * 
    * @param num
    *           The number for which the logarithm should be computed.
    * @param base
    *           The logarithm base.
    * 
    * @return The logarithmic value or <code>unknown</code> value when any input is <code>null</code>
    *         value or when <code>base</code> is not strictly positive or 1 or when <code>num</code>
    *         is not strictly positive.
    */
   public static decimal log(NumberType num, int base)
   {
      if (num == null)
      {
         return new decimal();
      }
      
      return new decimal(log(num.toBigDecimal(), new BigDecimal(base)));
   }

   /**
    * Calculate the logarithm of a number in a specific base.
    *
    * @param   num
    *          The number for which the logarithm should be computed.
    * @param   base
    *          The logarithm base.
    *
    * @return The logarithmic value or <code>unknown</code> value when any input is <code>null</code>
    *         value or when <code>base</code> is not strictly positive or 1 or when <code>num</code>
    *         is not strictly positive.
    */
   public static decimal log(double num, double base)
   {
      return new decimal(log(new BigDecimal(num), new BigDecimal(base)));
   }
   
   /**
    * Calculate the natural logarithm of a number (base e).
    *
    * @param    num
    *           The number for which the logarithm should be computed.
    *
    * @return   The logarithmic value. 
    */
   public static decimal log(double num)
   {
      return new decimal(log(new BigDecimal(num)));
   }

   /**
    * Raise one number (the base) to the power of the second number (the exponent).
    *
    * @param   base
    *          The number to be exponentiated.
    * 
    * @param   exponent
    *          The power to which the number should be raised.
    *
    * @return  The exponentiated value or <code>unknown value</code> when either input is the 
    *          <code>unknown value</code>. 
    */
   public static decimal pow(NumberType base, NumberType exponent)
   {
      if (base.isUnknown() || exponent.isUnknown())
      {
         return new decimal();
      }
      
      return (new decimal(pow(base.toBigDecimal(), exponent.toBigDecimal())));
   }

   /**
    * Raise one number (the base) to the power of the second number (the
    * exponent).
    *
    * @param    base
    *           The number to be exponentiated.
    * 
    * @param    exponent
    *           The power to which the number should be raised.
    *
    * @return   The exponentiated value or <code>unknown value</code>
    *           when either input is the <code>unknown value</code>. 
    */
   public static decimal pow(double base, NumberType exponent)
   {
      if (exponent.isUnknown())
      {
         return new decimal();
      }
      
      return new decimal(pow(new BigDecimal(base), exponent.toBigDecimal()));
   }

   /**
    * Raise one number (the base) to the power of the second number (the
    * exponent).
    *
    * @param    base
    *           The number to be exponentiated.
    * 
    * @param    exponent
    *           The power to which the number should be raised.
    *
    * @return   The exponentiated value or <code>unknown value</code>
    *           when either input is the <code>unknown value</code>. 
    */
   public static decimal pow(NumberType base, double exponent)
   {
      if (base.isUnknown())
      {
         return new decimal();
      }
      
      return new decimal(pow(base.toBigDecimal(), new BigDecimal(exponent)));
   }

   /**
    * Raise one number (the base) to the power of the second number (the
    * exponent).
    *
    * @param    base
    *           The number to be exponentiated.
    * 
    * @param    exponent
    *           The power to which the number should be raised.
    *
    * @return   The exponentiated value.
    */
   public static decimal pow(double base, double exponent)
   {
      return new decimal(pow(new BigDecimal(base), new BigDecimal(exponent)));
   }
   
   /**
    * Generate a psuedo-randomly distributed number between the low and
    * high values inclusively.
    *
    * @param    low
    *           The bottom end of the range (inclusive).
    * @param    high
    *           The top end of the range (inclusive).
    *
    * @return   The random integral value within the specified range.
    */
   public static int64 random(long low, int64 high)
   {
      if (high.isUnknown())
      {
         return  int64.UNKNOWN;
      }
      
      return random(low, high.longValue());
   }

   /**
    * Generate a psuedo-randomly distributed number between the low and
    * high values inclusively.
    *
    * @param    low
    *           The bottom end of the range (inclusive).
    * @param    high
    *           The top end of the range (inclusive).
    *
    * @return   The random integral value within the specified range.
    */
   public static int64 random(int64 low, long high)
   {
      if (low.isUnknown())
      {
         return  int64.UNKNOWN;
      }
      
      return random(low.longValue(), high);
   }

   /**
    * Generate a psuedo-randomly distributed number between the low and
    * high values inclusively.
    *
    * @param    low
    *           The bottom end of the range (inclusive).
    * @param    high
    *           The top end of the range (inclusive).
    *
    * @return   The random integral value within the specified range.
    */
   public static int64 random(int64 low, int64 high)
   {
      if (low.isUnknown() || high.isUnknown())
      {
         return  int64.UNKNOWN;
      }
      
      return random(low.longValue(), high.longValue());
   }

   /**
    * Generate a psuedo-randomly distributed number between the low and
    * high values inclusively.
    *
    * @param    low
    *           The bottom end of the range (inclusive).
    * @param    high
    *           The top end of the range (inclusive).
    *
    * @return   The random integral value within the specified range.
    */
   public static int64 random(long low, long high)
   {
      if (low >= high)
      {
         ErrorManager.recordOrThrowError(380, 
               "For the RANDOM function, first argument must be less than second");
         return int64.UNKNOWN;
      }
      
      long interval = (high - low) + 1;
      long result;
      
      synchronized (generator)
      {
         result = Math.abs(generator.nextLong() % interval) + low;
      }
      
      return int64.of(result);
   }

   /**
    * Generate a psuedo-randomly distributed number between the low and high values inclusively.
    * Both arguments will be rounded to closest long before computing the random value. 
    *
    * @param    low
    *           The bottom end of the range (inclusive).
    * @param    high
    *           The top end of the range (inclusive).
    *
    * @return   The random integral value within the specified range.
    */
   public static int64 random(NumberType low, NumberType high)
   {
      if (low.isUnknown() || high.isUnknown())
      {
         return int64.UNKNOWN;
      }
      
      // if an operand is decimal, Progress converts it to int64 before going on.
      // if its value is too large, error 78 will be raised.
      int64 iop1 = (low  instanceof int64) ? (int64) low  : new int64(low);
      int64 iop2 = (high instanceof int64) ? (int64) high : new int64(high);

      return random(iop1.longValue(), iop2.longValue());
   }

   /**
    * Generate a psuedo-randomly distributed number between the low and high values inclusively.
    * Both arguments will be rounded to closest long before computing the random value. 
    *
    * @param    low
    *           The bottom end of the range (inclusive).
    * @param    high
    *           The top end of the range (inclusive).
    *
    * @return   The random integral value within the specified range.
    */
   public static int64 random(double low, NumberType high)
   {
      if (high.isUnknown()) 
      {
         return int64.UNKNOWN;
      }
      
      return random(new decimal(low), high);
   }

   /**
    * Generate a psuedo-randomly distributed number between the low and high values inclusively.
    * Both arguments will be rounded to closest long before computing the random value. 
    *
    * @param    low
    *           The bottom end of the range (inclusive).
    * @param    high
    *           The top end of the range (inclusive).
    *
    * @return   The random integral value within the specified range.
    */
   public static int64 random(NumberType low, double high)
   {
      if (low.isUnknown())
      {
         return int64.UNKNOWN;
      }
      
      return random(low, new decimal(high));
   }

   /**
    * Generate a psuedo-randomly distributed number between the low and high values inclusively.
    * Both arguments will be rounded to closest long before computing the random value. 
    *
    * @param    low
    *           The bottom end of the range (inclusive).
    * @param    high
    *           The top end of the range (inclusive).
    *
    * @return   The random integral value within the specified range.
    */
   public static int64 random(double low, double high)
   {
      return random(new decimal(low), new decimal(high));
   }
   
   /**
    * Calculate the square root of a number.
    *
    * @param    num
    *           The number for which to obtain the square root.
    *
    * @return   The square root or <code>unknown value</code>
    *           when input is negative.  
    */
   public static decimal sqrt(NumberType num)
   {
      if (num.isUnknown())
      {
         return new decimal();
      }
      
      if (num.doubleValue() < 0)
      {
         return new decimal();
      }
      
      if (num instanceof decimal)
      {
         return new decimal(sqrt(((decimal)num).toJavaType()));
      }
      else
      {
         return new decimal(sqrt(new BigDecimal(num.longValue())));
      }
   }

   /**
    * Calculate the square root of a number.
    *
    * @param    num
    *           The number for which to obtain the square root.
    *
    * @return   The square root or <code>unknown value</code>
    *           when input is negative.  
    */
   public static decimal sqrt(double num)
   {
      return new decimal(sqrt(new BigDecimal(num)));
   }
   
   /**
    * Calculate the square root of a number. The actual implementation.
    * <p>
    * When null is returned, it should be interpreted as an unknown value 
    * back to the converted code.
    * <p>
    * Precision-wise this method yields comparable outputs up to the inputs
    * of 10^15. See the table below showing error magnitudes of several exemplary 
    * inputs comparing Progress native sqrt (version 10.2B) and this method.
    * <code>
    *            Input: 10^11          10^13          10^15          10^17           10^41
    * Progress   Error: 0.0000209975   0.0010625323   0.2415973135   28.5870600263   6385341029806439311343616
    * P2J        Error: 0.0000422480   0.0004300767   0.0026421128   0.00006997839   17801490614.8294175592
    * </code>
    *
    * @param    num
    *           The number for which to obtain the square root.
    *
    * @return   The square root.
    *           <code>null</code> if the input value is <code>null</code>
    *           or negative.
    */
   private static BigDecimal sqrt(BigDecimal num)
   {
      // do not throw an exception
      if (num == null)
      {
         return null;
      }

      // negative number?
      if (num.signum() < 0)
      {
         return null;
      }

      // zero?
      if (num.compareTo(BigDecimal.ZERO) == 0)
      {
         // take a shortcut
         return BigDecimal.ZERO;
      }
      
      // one?
      if (num.compareTo(BigDecimal.ONE) == 0)
      {
         // take a shortcut
         return BigDecimal.ONE;
      }

      // calculate initial approximation
      BigInteger integerPart = num.toBigInteger();
      int dLength = integerPart.toString().length();
      dLength /= 2;
      BigDecimal candidate = BigDecimal.TEN.pow(dLength);

      BigDecimal lastCandidate = null;
      for (int i = 0; i < SQRT_MAX_ITERATIONS; i++)
      {
         lastCandidate = candidate;
         candidate = num.divide(candidate, decimal.MAX_SCALE, RoundingMode.HALF_UP).add(lastCandidate)
               .divide(BigDecimal.valueOf(2), decimal.MAX_SCALE, RoundingMode.HALF_UP);

         if (candidate.equals(lastCandidate))
         {
            break;
         }
      }

      return candidate.stripTrailingZeros();
   }
   
   /**
    * Rounds the passed <code>decimal</code> to the user-specified number of
    * digits of precision to the right side of the decimal point.  If the 
    * precision is specified as X then the X+1th digit is 5 or more, the
    * Xth digit is increased by 1, otherwise it is left alone. All digits
    * after the Xth are then dropped/truncated.  The specified precision
    * must be non-negative and any value greater than 10 is forced to 10.
    * <p>
    * If this instance represents the <code>unknown value</code>, the
    * result will be the <code>unknown value</code>.
    *
    * @param    value
    *           The number to round.
    * @param    precision
    *           The number of digits of precision to support.
    *
    * @return   The rounded <code>decimal</code> result.
    */
   public static decimal round(decimal value, int64 precision)
   {
      return value == null || value.isUnknown() || 
             precision == null || precision.isUnknown() ? new decimal() : 
               new decimal(round(value.toBigDecimal(), precision.intValue()));
   }
   
   /**
    * Rounds the passed <code>decimal</code> to the user-specified number of
    * digits of precision to the right side of the decimal point.  If the 
    * precision is specified as X then the X+1th digit is 5 or more, the
    * Xth digit is increased by 1, otherwise it is left alone. All digits
    * after the Xth are then dropped/truncated.  The specified precision
    * must be non-negative and any value greater than 10 is forced to 10.
    * <p>
    * If this instance represents the <code>unknown value</code>, the
    * result will be the <code>unknown value</code>.
    *
    * @param    value
    *           The number to round.
    * @param    precision
    *           The number of digits of precision to support.
    *
    * @return   The rounded <code>decimal</code> result.
    */
   public static decimal round(decimal value, int precision)
   {
      return value == null || value.isUnknown() ? new decimal() : 
                 new decimal(round(value.toBigDecimal(), precision));
   }
   
   /**
    * Rounds the passed <code>decimal</code> to the user-specified number of
    * digits of precision to the right side of the decimal point.  If the 
    * precision is specified as X then the X+1th digit is 5 or more, the
    * Xth digit is increased by 1, otherwise it is left alone. All digits
    * after the Xth are then dropped/truncated.  The specified precision
    * must be non-negative and any value greater than 10 is forced to 10.
    * <p>
    * If this instance represents the <code>unknown value</code>, the
    * result will be the <code>unknown value</code>.
    *
    * @param    value
    *           The number to round.
    * @param    precision
    *           The number of digits of precision to support.
    *
    * @return   The rounded <code>decimal</code> result.
    */
   public static decimal round(NumberType value, int precision)
   {
      return round(new decimal(value), precision);
   }
   
   /**
    * Rounds the passed <code>decimal</code> to the user-specified number of
    * digits of precision to the right side of the decimal point.  If the 
    * precision is specified as X then the X+1th digit is 5 or more, the
    * Xth digit is increased by 1, otherwise it is left alone. All digits
    * after the Xth are then dropped/truncated.  The specified precision
    * must be non-negative and any value greater than 10 is forced to 10.
    * <p>
    * If this instance represents the <code>unknown value</code>, the
    * result will be the <code>unknown value</code>.
    *
    * @param    value
    *           The number to round.
    * @param    precision
    *           The number of digits of precision to support.
    *
    * @return   The rounded <code>decimal</code> result.
    */
   public static decimal round(NumberType value, int64 precision)
   {
      return value == null || value.isUnknown() || 
            precision == null || precision.isUnknown() ? new decimal() : 
              new decimal(round(value.toBigDecimal(), precision.intValue()));
   }
   
   /**
    * Rounds the passed <code>decimal</code> to 10 digits of precision to the
    * right side of the decimal point.  If the 11th digit is 5 or more, the
    * 10th digit is increased by 1, otherwise it is left alone. All digits
    * after the 10th are then dropped/truncated.
    * <p>
    * If this instance represents the <code>unknown value</code>, the
    * result will be the <code>unknown value</code>.
    *
    * @param    value
    *           The number to round.
    *
    * @return   The rounded <code>decimal</code> result.
    */
   public static decimal round(decimal value)
   {
      return new decimal(round(value.toBigDecimal(), decimal.MAX_SCALE));
   }
   
   /**
    * Rounds the passed <code>double</code> to the user-specified number of
    * digits of precision to the right side of the decimal point.  If the 
    * precision is specified as X then the X+1th digit is 5 or more, the
    * Xth digit is increased by 1, otherwise it is left alone. All digits
    * after the Xth are then dropped/truncated.  The specified precision
    * must be non-negative and any value greater than 10 is forced to 10.
    * <p>
    * If the value is <code>NaN</code> the resulting <code>double</code> will
    * be set to <code>NaN</code>.
    *
    * @param    value
    *           The number to round.
    * @param    precision
    *           The number of digits of precision to support.
    *
    * @return   The rounded <code>double</code> result.
    */
   public static decimal round(double value, int precision)
   {
      return new decimal(truncateWorker(value, precision, true));
   }
   
   /**
    * Rounds the given decimal value to the user-specified number of digits
    * of precision to the right side of the decimal point.  If the precision 
    * is specified as X then the X+1th digit is 5 or more, the Xth digit is
    * increased by 1, otherwise it is left alone. All digits after the Xth
    * are then dropped/truncated.  The specified precision must be
    * non-negative and any value greater than 10 is forced to 10.
    * <p>
    * If the value is <code>null</code>, <code>null</code> is returned.  If
    * precision is negative, 0 is returned.
    *
    * @param    value
    *           The number to round.
    * @param    precision
    *           The number of digits of precision to support.
    *
    * @return   The rounded <code>decimal</code> result.
    */
   public static BigDecimal round(BigDecimal value, int precision)
   {
      return round(value, precision, RoundingMode.HALF_UP);
   }
   
   /**
    * Rounds the given decimal value to 10 digits of precision to the right
    * side of the decimal point.  If the 11th digit is 5 or more, the 10th
    * digit is increased by 1, otherwise it is left alone. All digits after
    * the 10th are then dropped/truncated.
    * <p>
    * If the value is <code>null</code>, <code>null</code> is returned.
    *
    * @param    value
    *           The number to round.
    *
    * @return   The rounded <code>decimal</code> result.
    */
   public static BigDecimal round(BigDecimal value)
   {
      return round(value, decimal.MAX_SCALE, RoundingMode.HALF_UP);
   }
   
   /**
    * Rounds the passed <code>double</code> to 10 digits of precision to the
    * right side of the decimal point.  If the 11th digit is 5 or more, the
    * 10th digit is increased by 1, otherwise it is left alone. All digits
    * after the 10th are then dropped/truncated.
    * <p>
    * If the value is <code>NaN</code> the resulting <code>double</code> will
    * be set to <code>NaN</code>.
    *
    * @param    value
    *           The number to round.
    *
    * @return   The rounded <code>double</code> result.
    */
   public static decimal round(double value)
   {
      return new decimal(truncateWorker(value, decimal.MAX_SCALE, true));
   }
   
   /**
    * Truncates the passed <code>decimal</code> to the user-specified number
    * of digits of precision to the right side of the decimal point.  The
    * specified precision must be non-negative and any value greater than 10
    * is forced to 10.
    * <p>
    * If this instance represents the <code>unknown value</code>, the
    * result will be the <code>unknown value</code>.
    *
    * @param    value
    *           The number to truncate.
    * @param    precision
    *           The number of digits of precision to support.
    *
    * @return   The truncated <code>decimal</code> result.
    */
   public static decimal truncate(decimal value, int64 precision)
   {
      return new decimal(truncate(value.toBigDecimal(), precision.intValue()));
   }
   
   /**
    * Truncates the passed <code>decimal</code> to the user-specified number
    * of digits of precision to the right side of the decimal point.  The
    * specified precision must be non-negative and any value greater than 10
    * is forced to 10.
    * <p>
    * If this instance represents the <code>unknown value</code>, the
    * result will be the <code>unknown value</code>.
    *
    * @param    value
    *           The number to truncate.
    * @param    precision
    *           The number of digits of precision to support.
    *
    * @return   The truncated <code>decimal</code> result.
    */
   public static decimal truncate(decimal value, int precision)
   {
      return new decimal(truncate(value.toBigDecimal(), precision));
   }
   
   /**
    * Truncates the passed <code>decimal</code> to the user-specified number
    * of digits of precision to the right side of the decimal point.  The
    * specified precision must be non-negative and any value greater than 10
    * is forced to 10.
    * <p>
    * If this instance represents the <code>unknown value</code>, the
    * result will be the <code>unknown value</code>.
    *
    * @param    value
    *           The number to truncate.
    * @param    precision
    *           The number of digits of precision to support.
    *
    * @return   The truncated <code>decimal</code> result.
    */
   public static decimal truncate(NumberType value, int precision)
   {
      return new decimal(truncate(value.toBigDecimal(), precision));
   }
   
   /**
    * Truncates the passed <code>BigDecimal</code> to the specified number
    * of digits of precision to the right side of the decimal point.  The
    * specified precision must be non-negative and any value greater than 10
    * is forced to 10.
    * <p>
    * If the value is <code>null</code>, then <code>null</code> is returned.
    *
    * @param    value
    *           The number to truncate.
    * @param    precision
    *           The number of digits of precision to support.
    *
    * @return   The truncated <code>BigDecimal</code> result.
    */
   public static BigDecimal truncate(BigDecimal value, int precision)
   {
      return round(value, precision, RoundingMode.DOWN);
   }
   
   /**
    * Truncates the passed <code>double</code> to the user-specified number
    * of digits of precision to the right side of the decimal point.  The
    * specified precision must be non-negative and any value greater than 10
    * is forced to 10.
    * <p>
    * If the value is <code>NaN</code> the resulting <code>double</code> will
    * be set to <code>NaN</code>.
    *
    * @param    value
    *           The number to truncate.
    * @param    precision
    *           The number of digits of precision to support.
    *
    * @return   The truncated <code>double</code> result.
    */
   public static double truncate(double value, int precision)
   {
      return truncateWorker(value, precision, false);
   }
   
   /**
    * Implements the unary minus operator for the <code>decimal</code> type.
    * <p>
    * If the operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    d
    *           The <code>decimal</code> to negate.
    *
    * @return   The negated result.
    */
   public static decimal negate(decimal d)
   {
      if (d.isUnknown())
         return (decimal) d.instantiateUnknown();
      
      return (new decimal(d.toBigDecimal().negate()));
   }
   
   /**
    * Implements the unary minus operator.  This is a "convenience" method
    * which makes it possible to use a common approach for unary minus. This
    * is needed to allow the proper wrapping to be handled in the runtime
    * rather than doing this in client code.
    *
    * @param    d
    *           The number to negate.
    *
    * @return   The negated result.
    */
   public static decimal negate(double d)
   {
      return new decimal(-d);
   }
   
   /**
    * Implements the unary minus operator for the <code>integer</code> type.
    * <p>
    * If the operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    i
    *           The <code>integer</code> to negate.
    *
    * @return   The negated result.
    */
   public static int64 negate(int64 i)
   {
      if (i.isUnknown())
         return int64.UNKNOWN;
      
      return int64.of(-(i.longValue()));
   }
   
   /**
    * Implements the unary minus operator.  This is a "convenience" method
    * which makes it possible to use a common approach for unary minus. This
    * is needed to allow the proper wrapping to be handled in the runtime
    * rather than doing this in client code.
    *
    * @param    i
    *           The number to negate.
    *
    * @return   The negated result.
    */
   public static int64 negate(long i)
   {
      return int64.of(-i);
   }

   /**
    * Implements the binary plus arithmetic operator to add the right
    * operand to the left and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The added result.
    */
   public static decimal plus(NumberType op1, NumberType op2)
   {
      if (op1.isUnknown() || op2.isUnknown())
      {
         return new decimal();
      }
      
      BigDecimal d1 = op1 instanceof decimal ? ((decimal) op1).toBigDecimal() : null;
      BigDecimal d2 = op2 instanceof decimal ? ((decimal) op2).toBigDecimal() : null;
      
      if (d1 == null && d2 == null)
      {
         // both operand are some kind of integer
         return new decimal(op1.longValue() + op2.longValue());
      }
      
      if (d1 == null)
      {
         d1 = new BigDecimal(op1.longValue());
      }
      else if (d2 == null)
      {
         d2 = new BigDecimal(op2.longValue());
      }
      return new decimal(d1.add(d2));
   }
   
   /**
    * Implements the binary plus arithmetic operator to add the right
    * operand to the left and return the result.
    * <p>
    * If the second operand is the <code>unknown value</code>, the result
    * will be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The added result.
    */
   public static decimal plus(double op1, NumberType op2)
   {
      return plus(new decimal(op1), op2);
   }
   
   /**
    * Implements the binary plus arithmetic operator to add the right
    * operand to the left and return the result.
    * <p>
    * If the first operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The added result.
    */
   public static decimal plus(NumberType op1, double op2)
   {
      return plus(op1, new decimal(op2));
   }
   
   /**
    * Implements the binary plus arithmetic operator to add the right
    * operand to the left and return the result.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The added result.
    */
   public static decimal plus(double op1, double op2)
   {
      return plus(new decimal(op1), new decimal(op2));
   }
   
   /**
    * Implements the binary plus arithmetic operator to add the right
    * operand to the left and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The added result.
    */
   public static int64 plus(int64 op1, int64 op2)
   {
      if (op1.isUnknown() || op2.isUnknown())
      {
         return int64.UNKNOWN;
      }
      
      return int64.of(op1.longValue() + op2.longValue());
   }
   
   /**
    * Implements the binary plus arithmetic operator to add the right
    * operand to the left and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The added result.
    */
   public static int64 plus(integer op1, integer op2)
   {
      return plus((int64) op1, (int64) op2);
   }
   
   /**
    * Implements the binary plus arithmetic operator to add the right
    * operand to the left and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The added result.
    */
   public static int64 plus(integer op1, int op2)
   {
      return plus((int64) op1, int64.of((long) op2));
   }
   
   /**
    * Implements the binary plus arithmetic operator to add the right
    * operand to the left and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The added result.
    */
   public static int64 plus(int op1, integer op2)
   {
      return plus(new int64(op1), (int64) op2);
   }
   
   /**
    * Implements the binary plus arithmetic operator to add the right
    * operand to the left and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The added result.
    */
   public static int64 plus(int op1, int op2)
   {
      return plus((long) op1, (long) op2);
   }
   
   /**
    * Implements the binary plus arithmetic operator to add the right
    * operand to the left and return the result.
    * <p>
    * If the first operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The added result.
    */
   public static int64 plus(int64 op1, long op2)
   {
      if (op1.isUnknown())
      {
         return int64.UNKNOWN;
      }
      
      return int64.of(op1.longValue() + op2);
   }
   
   /**
    * Implements the binary plus arithmetic operator to add the right
    * operand to the left and return the result.
    * <p>
    * If the second operand is the <code>unknown value</code>, the result
    * will be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The added result.
    */
   public static int64 plus(long op1, int64 op2)
   {
      if (op2.isUnknown())
      {
         return int64.UNKNOWN;
      }
      
      return int64.of(op1 + op2.longValue());
   }
   
   /**
    * Implements the binary plus arithmetic operator to add the right
    * operand to the left and return the result.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The added result.
    */
   public static int64 plus(long op1, long op2)
   {
      return int64.of(op1 + op2);
   }
   
   /**
    * Implements the binary minus arithmetic operator to subtract the
    * right operand from the left and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The subtracted result.
    */
   public static decimal minus(NumberType op1, NumberType op2)
   {
      if (op1.isUnknown() || op2.isUnknown())
      {
         return new decimal();
      }
      
      BigDecimal d1 = op1 instanceof decimal ? ((decimal) op1).toBigDecimal() : null;
      BigDecimal d2 = op2 instanceof decimal ? ((decimal) op2).toBigDecimal() : null;
      
      if (d1 == null && d2 == null)
      {
         // both operand are some kind of integer
         return (new decimal(op1.longValue() - op2.longValue()));
      }

      if (d1 == null)
      {
         d1 = new BigDecimal(op1.intValue());
      }
      else if (d2 == null)
      {
         d2 = new BigDecimal(op2.intValue());
      }
      
      return (new decimal(d1.subtract(d2)));
   }
   
   /**
    * Implements the binary minus arithmetic operator to subtract the
    * right operand from the left and return the result.
    * <p>
    * If the second operand is the <code>unknown value</code>, the result
    * will be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The subtracted result.
    */
   public static decimal minus(double op1, NumberType op2)
   {
      return minus(new decimal(op1), op2);
   }
   
   /**
    * Implements the binary minus arithmetic operator to subtract the
    * right operand from the left and return the result.
    * <p>
    * If the first operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The subtracted result.
    */
   public static decimal minus(NumberType op1, double op2)
   {
      return minus(op1, new decimal(op2));
   }
   
   /**
    * Implements the binary minus arithmetic operator to subtract the
    * right operand from the left and return the result.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The subtracted result.
    */
   public static decimal minus(double op1, double op2)
   {
      return minus(new decimal(op1), new decimal(op2));
   }

   /**
    * Implements the binary minus arithmetic operator to subtract the
    * right operand from the left and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The subtracted result.
    */
   public static int64 minus(int64 op1, int64 op2)
   {
      if (op1.isUnknown() || op2.isUnknown())
      {
         return int64.UNKNOWN;
      }
      
      return int64.of(op1.longValue() - op2.longValue());
   }

   /**
    * Implements the binary minus arithmetic operator to subtract the
    * right operand from the left and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The subtracted result.
    */
   public static int64 minus(integer op1, integer op2)
   {
      return minus((int64) op1, (int64) op2);
   }

   /**
    * Implements the binary minus arithmetic operator to subtract the
    * right operand from the left and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The subtracted result.
    */
   public static int64 minus(integer op1, int op2)
   {
      return minus((int64) op1, (long) op2);
   }

   /**
    * Implements the binary minus arithmetic operator to subtract the
    * right operand from the left and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The subtracted result.
    */
   public static int64 minus(int op1, integer op2)
   {
      return minus((long) op1, (int64) op2);
   }

   /**
    * Implements the binary minus arithmetic operator to subtract the
    * right operand from the left and return the result.
    * <p>
    * If the second operand is the <code>unknown value</code>, the result
    * will be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The subtracted result.
    */
   public static int64 minus(long op1, int64 op2)
   {
      if (op2.isUnknown())
      {
         return int64.UNKNOWN;
      }

      return int64.of(op1 - op2.longValue());
   }
   
   /**
    * Implements the binary minus arithmetic operator to subtract the
    * right operand from the left and return the result.
    * <p>
    * If the first operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The subtracted result.
    */
   public static int64 minus(int64 op1, long op2)
   {
      if (op1.isUnknown())
      {
         return int64.UNKNOWN;
      }
      
      return int64.of(op1.longValue() - op2);
   }
   
   /**
    * Implements the binary minus arithmetic operator to subtract the
    * right operand from the left and return the result.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The subtracted result.
    */
   public static int64 minus(long op1, long op2)
   {
      return int64.of(op1 - op2);
   }
   
   /**
    * Implements the binary minus arithmetic operator to subtract the
    * right operand from the left and return the result.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The subtracted result.
    */
   public static int64 minus(int op1, int op2)
   {
      return minus((long) op1, (long) op2);
   }

   /**
    * Implements the binary multiply arithmetic operator to multiply the
    * two operands and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The multiplied result.
    */
   public static decimal multiply(NumberType op1, NumberType op2)
   {
      if (op1.isUnknown() || op2.isUnknown())
      {
         return new decimal();
      }
      
      BigDecimal d1 = op1 instanceof decimal ? ((decimal) op1).toBigDecimal() : null;
      BigDecimal d2 = op2 instanceof decimal ? ((decimal) op2).toBigDecimal() : null;
      
      if (d1 == null && d2 == null)
      {
         // both operand are some kind of integer
         return (new decimal(op1.longValue() * op2.longValue()));
      }

      if (d1 == null)
      {
         d1 = new BigDecimal(op1.longValue());
      }
      else if (d2 == null)
      {
         d2 = new BigDecimal(op2.longValue());
      }
      
      return (new decimal(d1.multiply(d2)));
   }
   
   /**
    * Implements the binary multiply arithmetic operator to multiply the
    * two operands and return the result.
    * <p>
    * If the second operand is the <code>unknown value</code>, the result
    * will be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The multiplied result.
    */
   public static decimal multiply(double op1, NumberType op2)
   {
      return multiply(new decimal(op1), op2);
   }
   
   /**
    * Implements the binary multiply arithmetic operator to multiply the
    * two operands and return the result.
    * <p>
    * If the first operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The multiplied result.
    */
   public static decimal multiply(NumberType op1, double op2)
   {
      return multiply(op1, new decimal(op2));
   }
   
   /**
    * Implements the binary multiply arithmetic operator to multiply the
    * two operands and return the result.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The multiplied result.
    */
   public static decimal multiply(double op1, double op2)
   {
      return multiply(new decimal(op1), new decimal(op2));
   }

   /**
    * Implements the binary multiply arithmetic operator to multiply the
    * two operands and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The multiplied result.
    */
   public static int64 multiply(int64 op1, int64 op2)
   {
      if (op1.isUnknown() || op2.isUnknown())
      {
         return int64.UNKNOWN;
      }

      return int64.of(op1.longValue() * op2.longValue());
   }
   
   /**
    * Implements the binary multiply arithmetic operator to multiply the
    * two operands and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The multiplied result.
    */
   public static int64 multiply(integer op1, integer op2)
   {
      return multiply((int64) op1, (int64) op2);
   }

   /**
    * Implements the binary multiply arithmetic operator to multiply the
    * two operands and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The multiplied result.
    */
   public static int64 multiply(integer op1, int op2)
   {
      return multiply((int64) op1, (long) op2);
   }

   /**
    * Implements the binary multiply arithmetic operator to multiply the
    * two operands and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The multiplied result.
    */
   public static int64 multiply(int op1, integer op2)
   {
      return multiply((long) op1, (int64) op2);
   }

   /**
    * Implements the binary multiply arithmetic operator to multiply the
    * two operands and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The multiplied result.
    */
   public static int64 multiply(int op1, int op2)
   {
      return multiply((long) op1, (long) op2);
   }

   /**
    * Implements the binary multiply arithmetic operator to multiply the
    * two operands and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The multiplied result.
    */
   public static int64 multiply(long op1, long op2)
   {
      return int64.of(op1 * op2);
   }

   /**
    * Implements the binary multiply arithmetic operator to multiply the
    * two operands and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The multiplied result.
    */
   public static int64 multiply(int64 op1, long op2)
   {
      if (op1.isUnknown())
      {
         return int64.UNKNOWN;
      }

      return int64.of(op1.longValue() * op2);
   }
   
   /**
    * Implements the binary multiply arithmetic operator to multiply the
    * two operands and return the result.
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The multiplied result.
    */
   public static int64 multiply(long op1, int64 op2)
   {
      if (op2.isUnknown())
      {
         return int64.UNKNOWN;
      }

      return int64.of(op1 * op2.longValue());
   }
   
   /**
    * Implements the binary floating-point division arithmetic operator to 
    * divide the left operand by the right operand and return the result.
    * There is no operator that provides integer division (use 
    * {@link #truncate} on the result if this is necessary).
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.  In addition, division by 0 will
    * silently result in the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The divided result.
    */
   public static decimal divide(NumberType op1, NumberType op2)
   {
      if (op1.isUnknown() || op2.isUnknown() || op2.doubleValue() == 0.0D)
      {
         return new decimal();
      }
      
      BigDecimal d1 = op1 instanceof decimal
                      ? ((decimal) op1).toBigDecimal()
                      : new decimal(op1.longValue()).toBigDecimal();
      BigDecimal d2 = op2 instanceof decimal
                      ? ((decimal) op2).toBigDecimal()
                      : new decimal(op2.longValue()).toBigDecimal();
      
      return new decimal(d1.divide(d2, decimal.MAX_SCALE, RoundingMode.HALF_UP));
   }
   
   /**
    * Implements the binary floating-point division arithmetic operator to 
    * divide the left operand by the right operand and return the result.
    * There is no operator that provides integer division (use 
    * {@link #truncate} on the result if this is necessary).
    * <p>
    * If the second operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.  In addition, division by 0 will
    * silently result in the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The divided result.
    */
   public static decimal divide(double op1, NumberType op2)
   {
      return divide(new decimal(op1), op2);
   }
   
   /**
    * Implements the binary floating-point division arithmetic operator to 
    * divide the left operand by the right operand and return the result.
    * There is no operator that provides integer division (use 
    * {@link #truncate} on the result if this is necessary).
    * <p>
    * If the first operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.  In addition, division by 0 will
    * silently result in the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The divided result.
    */
   public static decimal divide(NumberType op1, double op2)
   {
      return divide(op1, new decimal(op2));
   }
   
   /**
    * Implements the binary floating-point division arithmetic operator to 
    * divide the left operand by the right operand and return the result.
    * There is no operator that provides integer division (use 
    * {@link #truncate} on the result if this is necessary).
    * <p>
    * Division by 0 will silently result in the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand.
    * @param    op2
    *           The right operand.
    *
    * @return   The divided result.
    */
   public static decimal divide(double op1, double op2)
   {
      return divide(new decimal(op1), new decimal(op2));
   }
   
   /**
    * Hack! Temporarily. Handles implicit conversion of arguments obtained from _POLY function.
    * 
    * @param   op1
    *          The left operand.
    * @param   op2
    *          The right operand.
    *          
    * @return  The divided result.
    */
   public static decimal divide(BaseDataType op1, double op2)
   {
      return divide(new decimal(op1), new decimal(op2));
   }
   
   
   /**
    * Implements the binary modulo (remainder) arithmetic operator which divides the left operand 
    * by the right operand (the base) and returns the resulting integral remainder. 
    * <p>
    * If any of the operands are not integer type, they are first rounded to the closest long
    * and then the operation is performed using usual arithmetic.
    * After rounding, the base must be positive (&gt; 0).
    * <p>
    * If either operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand (number to divide).
    * @param    op2
    *           The right operand (the divisor or modulo base).
    *
    * @return   The remainder result.
    */
   public static int64 modulo(NumberType op1, NumberType op2)
   {
      if (op1.isUnknown() || op2.isUnknown())
      {
         return int64.UNKNOWN;
      }

      // if an operand is decimal, Progress converts it to int64 before going on.
      // if its value is too large, error 78 will be raised.
      int64 iop1 = (op1 instanceof int64) ? (int64) op1 : new int64(op1);
      int64 iop2 = (op2 instanceof int64) ? (int64) op2 : new int64(op2);

      return modulo(iop1.longValue(), iop2.longValue());
   }
   
   /**
    * Implements the binary modulo (remainder) arithmetic operator which
    * divides the left operand by the right operand (the base) and returns 
    * the resulting integral remainder.  The base must be &gt; 0. 
    * <p>
    * If the second operand is the <code>unknown value</code>, the result
    * will be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand (number to divide).
    * @param    op2
    *           The right operand (the divisor or modulo base).
    *
    * @return   The remainder result.
    */
   public static int64 modulo(double op1, NumberType op2)
   {
      return modulo(new decimal(op1), op2);
   }
   
   /**
    * Implements the binary modulo (remainder) arithmetic operator which
    * divides the left operand by the right operand (the base) and returns 
    * the resulting integral remainder.  The base must be &gt; 0. 
    * <p>
    * If the first operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand (number to divide).
    * @param    op2
    *           The right operand (the divisor or modulo base).
    *
    * @return   The remainder result.
    */
   public static int64 modulo(NumberType op1, double op2)
   {
      return modulo(op1, new decimal(op2));
   }
   
   /**
    * Implements the binary modulo (remainder) arithmetic operator which
    * divides the left operand by the right operand (the base) and returns 
    * the resulting integral remainder.  The base must be &gt; 0. 
    * <p>
    * If the first operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand (number to divide).
    * @param    op2
    *           The right operand (the divisor or modulo base).
    *
    * @return   The remainder result.
    */
   public static int64 modulo(long op1, NumberType op2)
   {
      return modulo(new int64(op1), op2);
   }
   
   /**
    * Implements the binary modulo (remainder) arithmetic operator which
    * divides the left operand by the right operand (the base) and returns 
    * the resulting integral remainder.  The base must be &gt; 0. 
    * <p>
    * If the first operand is the <code>unknown value</code>, the result will
    * be the <code>unknown value</code>.
    *
    * @param    op1
    *           The left operand (number to divide).
    * @param    op2
    *           The right operand (the divisor or modulo base).
    *
    * @return   The remainder result.
    */
   public static int64 modulo(NumberType op1, long op2)
   {
      return modulo(op1, new int64(op2));
   }
   
   /**
    * Implements the binary modulo (remainder) arithmetic operator which divides the left operand
    * by the right operand (the base) and returns the resulting integral remainder. 
    * The base must be &gt; 0. 
    * <p>
    * If one or both operands are not integers, they are first rounded to an integer value and
    * then the operation is carried using integer arithmetics. The rounding must fit in 64-bit
    * space or 
    * <p>
    * If the left operand is greater than zero, then the result will be as expected from normal
    * integral arithmetic (the remainder after integer division will be returned). However, if 
    * the left operand is not greater than zero, then the result will depend on the value of
    * the remainder after the absolute value of the left operand is integrally divided into the 
    * base. Zero will be returned if the remainder in this case is zero, otherwise the base minus
    * the remainder will be returned.
    * Warning: the Progress documentation only suggests that this is an invalid case which should
    * not be relied upon. However, client application code has been found to be reliant upon 
    * this behavior.
    *
    * @param    op1
    *           The left operand (number to divide).
    * @param    op2
    *           The right operand (the divisor or modulo base).
    *
    * @return   The remainder result.
    */
   public static int64 modulo(double op1, double op2)
   {
      // wrapping operand into decimal object will assure double to integer conversion
      return modulo(new decimal(op1), new decimal(op2));
   }
   
   /**
    * Implements the binary modulo (remainder) arithmetic operator which divides the left operand
    * by the right operand (the base) and returns the resulting integral remainder. 
    * The base must be &gt; 0. 
    * <p>
    * If the left operand is greater than zero, then the result will be as expected from normal
    * integral arithmetic (the remainder after integer division will be returned). However, if 
    * the left operand is not greater than zero, then the result will depend on the value of
    * the remainder after the absolute value of the left operand is integrally divided into the 
    * base. Zero will be returned if the remainder in this case is zero, otherwise the base minus
    * the remainder will be returned.
    * Warning: the Progress documentation only suggests that this is an invalid case which should
    * not be relied upon. However, client application code has been found to be reliant upon 
    * this behavior.
    *
    * @param    op1
    *           The left operand (number to divide).
    * @param    op2
    *           The right operand (the divisor or modulo base).
    *
    * @return   The remainder result.
    */
   public static int64 modulo(long op1, long op2)
   {
      if (op2 < 1)
      {
         genModuloError(op2);
         return int64.UNKNOWN;
      }
      
      long result = Math.abs(op1) % op2;
      
      // common case
      if (op1 > 0)
      {
         return int64.of(result);
      }
      
      return result == 0 ? int64.of(0L) : int64.of(op2 - result);
   }

   /**
    * Rounds the given decimal value to the user-specified number of digits
    * of precision to the right side of the decimal point, using the given
    * rounding mode.  The rounding mode determines whether the value is
    * rounded or truncated to the specified precision.
    * <p>
    * For rounding mode <code>RoundingMode.HALF_UP</code>:  If the
    * precision is specified as X and the X+1th digit is 5 or more, the Xth
    * digit is increased by 1, otherwise it is left alone.  All digits after
    * the Xth are then dropped/truncated.
    * <p>
    * For rounding mode <code>BigDecimal.ROUND_DOWN</code>:  If the precision
    * is specified as X then all digits after the Xth are dropped/truncated.
    * <p>
    * The specified precision must be non-negative and any value greater than
    * 10 is forced to 10.
    * <p>
    * If the value is <code>null</code>, <code>null</code> is returned.  If
    * precision is negative, 0 is returned.
    *
    * @param    value
    *           The number to round.
    * @param    precision
    *           The number of digits of precision to support.
    * @param    roundingMode
    *           Either <code>RoundingMode.HALF_UP</code> (to round) or
    *           <code>BigDecimal.ROUND_DOWN</code> (to truncate).
    *
    * @return   The rounded/truncated <code>double</code> result.
    */
   private static BigDecimal round(BigDecimal value, int precision, RoundingMode roundingMode)
   {
      // negative precision is illegal
      if (precision < 0)
      {
         String err = "Decimal precision cannot be negative";
         ErrorManager.recordOrThrowError(84, err);
         
         return decimal.ZERO;
      }
      
      // return null if input is null
      if (value == null)
      {
         return null;
      }
      
      // force to decimal.MAX_SCALE digits if anything greater is set
      if (precision > decimal.MAX_SCALE)
      {
         precision = decimal.MAX_SCALE;
      }
      
      return value.setScale(precision, roundingMode);
   }
   
   /**
    * Core logic to round or truncate a passed <code>double</code> to the
    * user-specified precision (number of digits to the right of the
    * decimal point). If <code>round</code> is <code>true</code> and if the 
    * precision is specified as X then when the X+1th digit is 5 or more, the
    * Xth digit is increased by 1, otherwise it is left alone. All digits
    * after the Xth are then dropped/truncated.  If <code>round</code> is
    * <code>false</code> then the Xth digit is always unchanged and all
    * digits greater than X are truncated. The specified precision
    * must be non-negative and any value greater than 10 is forced to 10.
    * <p>
    * The algorithm used relies upon scaling (multiplying) the input value
    * by a power of 10 based on the precision (for example, by
    * <code>Math.pow(10, precision)</code>).  This leaves all digits to be
    * kept in the result, to the left of the decimal point.  Then this
    * <code>double</code> is cast to a <code>long</code> (truncate mode)
    * or rounded into a <code>long</code> (round mode) which drops the
    * extra digits.  Finally, the value is de-scaled (divided by) the same
    * scaling value to obtain a properly scaled result. 
    * <p>
    * This algorithm will fail when used on large numbers (positive or
    * negative) with large precision.  This is due to the fact that the 
    * intermediate resulting <code>double</code> (after scaling) can be larger
    * than <code>Long.MAX_VALUE</code> or smaller than
    * <code>Long.MIN_VALUE</code> which breaks the algorithm.
    * <p>
    * If the passed value to be rounded/truncated is <code>NaN</code>, the
    * result will be <code>NaN</code>.
    *
    * @param    value
    *           The value to be rounded or truncated.
    * @param    precision
    *           The number of significant digits to maintain to the right of
    *           the decimal point.  Use 0 to get a value that is integral.
    * @param    round
    *           <code>true</code> to using rounding, <code>false</code> to
    *           obtain truncation.
    *
    * @return   The rounded or truncated result.
    *
    * @throws   IllegalArgumentException
    *           If the intermediate result is too large or too small to fit
    *           into a <code>long</code> (e.g. positive or negative 
    *           infinity) or if the precision is negative.
    */
   private static double truncateWorker(double value, int precision, boolean round)
   throws IllegalArgumentException
   {
      // negative precision is illegal
      if (precision < 0)
      {
         String err = "Decimal precision cannot be negative";
         ErrorManager.recordOrThrowError(84, err); 
         return 0;
      }
      
      // return NaN if input is NaN
      if (Double.isNaN(value))
      {
         // nothing to do!
         return value;
      }
      
      // force to 10 digits if anything greater is set
      if (precision > decimal.MAX_SCALE)
      {
         precision = decimal.MAX_SCALE;
      }
      
      double scale = 10000000000.0D;
      
      if (precision != decimal.MAX_SCALE)
      {
         scale = Math.pow(decimal.MAX_SCALE, precision);
      }
      
      // the left side of the decimal point should never change BUT these
      // extra significant digits can change how numbers are represented
      // in floating point format; since these digits are not needed for
      // the rounding or truncation operation, we safe them off here and
      // will add them back later; this allows the calculation to be done
      // on a double in which we only ever care about 11 significant digits 
      // (those digits to the left of the decimal point which determine the
      // result)
      long left = (long) value;
      double right = value - left;
      
      // "shift" the right side value such that any digits that we don't want
      // are on the right side of the decimal point and all digits we must
      // save are on the left side of decimal point
      double scaled = right * scale;
      
      // a Java double can only have a total of ~16 significant digits 
      // (on the left side of the decimal point), so if the left side has
      // 16 or more digits, the result MAY SILENTLY lose or drop
      // least significant digits and the resulting value will be stored in
      // the closest possible representation; since Java doesn't notify us
      // when this occurs, we compare it to the maximum left side size
      // this allows us to detect the case where the result will be wrong
      boolean loss = left > MAX_SAFE;
      
      // make sure we don't overflow/underflow or lose significant digits
      if (scaled > Long.MAX_VALUE || scaled < Long.MIN_VALUE || loss)
      {
         // alternate (slower) approach which is guaranteed to work
         
         // rounding mode (ROUND_DOWN is truncation) 
         RoundingMode rmode = round ? RoundingMode.HALF_UP : RoundingMode.DOWN;
         
         // BD is immutable, create the full valued BD
         BigDecimal val = new BigDecimal(value);
         
         // obtain a new BD with the correct precision based on the requested
         // rounding/truncation mode
         BigDecimal res = val.setScale(precision, rmode);
         
         return res.doubleValue();
      }
      
      long trunc = 0;
      
      // remove the unneeded digits by converting to long
      if (round)
      {
         // use the rounding function to find the correct long value
         trunc = Math.round(scaled); 
      }
      else
      {
         // regular truncation
         trunc = (long) scaled;
      }
      
      // convert back to the proper scale and return the resulting
      // truncated/rounded double + the left side value
      return trunc/scale + left;
   }
   
   /**
    * Generate an error if the 2nd operand to {@link #modulo} is not positive.
    *
    * @param    op2
    *           The second parameter (provided for insertion into the error message.
    */
   private static void genModuloError(long op2)
   {
      String spec = "The second argument to MOD must be positive. %d is invalid";
      String err  = String.format(spec, op2);

      ErrorManager.recordOrThrowError(89, err);
   }
   
   /**
    * Calculate the logarithm of a number in a specific base.
    * 
    * @param num
    *           The number for which the logarithm should be computed.
    * @param base
    *           The logarithm base.
    * 
    * @return The logarithmic value or <code>null</code> value when any input is <code>null</code>
    *         value or when <code>base</code> is not strictly positive or 1 or when <code>num</code>
    *         is not strictly positive. Precision of the result is rounded-up to
    *         {@linkplain decimal#MAX_SCALE} number of decimal places.
    */
   private static BigDecimal log(BigDecimal num, BigDecimal base)
   {
      // invalid input?
      if (num == null || base == null)
      {
          return null;
      }

      // num less or equal zero?
      if (num.compareTo(BigDecimal.ZERO) <= 0)
      {
          return null;
      }
      
      // base equals to one or less or equal zero?
      if (base.compareTo(BigDecimal.ONE) == 0 || base.compareTo(BigDecimal.ZERO) <= 0)
      {
         return null;
      }
      
      // num equals to one?
      if (num.compareTo(BigDecimal.ONE) == 0)
      {
         return BigDecimal.ZERO;
      }
      
      BigDecimal logn = log(num);
      if (logn == null)
      {
         return null;
      }
      
      BigDecimal logb = log(base);
      if (logb == null)
      {
         return null;
      }
      
      return logn.divide(logb, decimal.MAX_SCALE, RoundingMode.HALF_UP);
   }
   
   /**
    * Calculate the natural logarithm of a number. The actual implementation.
    *
    * @param   num
    *          The number for which the logarithm should be computed.
    *
    * @return  The logarithmic value or the <code>null</code> when the input is
    *          <code>null</code> or when <code>num</code> is not strictly positive.
    *          Precision of the result may be higher than {@linkplain decimal#MAX_SCALE},
    *          it's the caller's responsibility to round down if required.
    */
   private static BigDecimal log(BigDecimal num)
   {
      // invalid input?
      if (num == null)
      {
          return null;
      }

      // num less or equal zero?
      if (num.compareTo(BigDecimal.ZERO) <= 0)
      {
          return null;
      }
      
      // num equals to one?
      if (num.compareTo(BigDecimal.ONE) == 0)
      {
         return BigDecimal.ZERO;
      }
        
      // The algorithm uses a polynomial approximation which is accurate only for small
      // input values, thus the original value must be reduced to a smaller one.
      // Start with braking down the input to the scientific notation of base*10^exponent
      // which can be expressed as log(base) + exponent*log(10).
      num = num.stripTrailingZeros();
      int exponent = num.unscaledValue().toString().length() - num.scale() - 1;
      BigDecimal base = num.movePointLeft(exponent);

      // reduce base even further, log(base) = 2*log(sqrt(base))
      base = new BigDecimal(Math.sqrt(base.doubleValue()));

      // now approximate the reduced base using a series based on hyperbolic tangent function
      // with the predefined number of iterations
      BigDecimal polynomResult = BigDecimal.ZERO;
      BigDecimal increment = null;
      BigDecimal polynomBase = base.subtract(BigDecimal.ONE).divide(base.add(BigDecimal.ONE),
            LOG_PRECISION, RoundingMode.HALF_UP);
      for (int i = 1; i < LOG_ITERATIONS * 2; i += 2)
      {
         increment = polynomBase.pow(i);
         increment = increment.divide(BigDecimal.valueOf(i), LOG_PRECISION, RoundingMode.HALF_UP);
         polynomResult = polynomResult.add(increment);
      }
      polynomResult = polynomResult.multiply(BigDecimal.valueOf(2));

      // the result is the approximation added to exponent*log(10)
      BigDecimal result = polynomResult.multiply(BigDecimal.valueOf(2)).add(
            new BigDecimal(exponent).multiply(LOG_10));
      
      // leave the result unrounded, this method can be called from log(num, base) 
      return result;
   }   
   
   /**
    * Raise one number (the base) to the power of the second number (the
    * exponent).
    * <p>
    * When base is equal or less than zero and exponent is less than zero,
    * error 9384 is raised.
    * <p>
    * When the exponent has no decimal component the method returns result 
    * of {@link BigDecimal#pow(int, java.math.MathContext)}, otherwise the
    * result of the method call {@link MathOps#pow(BigDecimal, BigDecimal)} is returned.
    * 
    * @param    base
    *           The number to be exponentiated.
    * 
    * @param    exponent
    *           The power to which the number should be raised.
    *
    * @return   The exponentiated value or <code>null</code>
    *           when either input is <code>null</code>. 
    */
   private static BigDecimal pow(BigDecimal base, BigDecimal exponent)
   {
      if (base == null || exponent == null)
      {
         return null;
      }

      String message9384 = "Unable to calculate solution for EXP function";
      
      // base equal or less zero and exponent less than zero lead to an error
      if (base.signum() <= 0 && exponent.signum() < 0)
      {
         ErrorManager.recordOrThrowError(9384, message9384, false);
         return null;
      }

      // zero exponent always yields one
      if (exponent.signum() == 0)
      {
         return BigDecimal.ONE;
      }

      // zero base
      if (base.signum() == 0)
      {
         return BigDecimal.ZERO;
      }

      // one base
      if (base.compareTo(BigDecimal.ONE) == 0)
      {
         return BigDecimal.ONE;
      }
      
      BigDecimal result = null;
      
      try
      {
         if (exponent.scale() == 0 || exponent.stripTrailingZeros().scale() == 0)
         {
            // no decimal component in exponent, use BigDecimal.pow
            result = base.pow(exponent.intValue(), EXP_MCONTEXT);
         } 
         else
         {
            // call the in-house implementation
            BigDecimal naturalBase = log(base).multiply(exponent);
            result = exp(naturalBase);
         }
      } 
      catch (ArithmeticException ex)
      {
         ErrorManager.recordOrThrowError(9384, message9384, false);
      }

      return result;
   }
   
   /**
    * Raise e (Euler's number) to the power of the second number (the
    * exponent).
    * <p>
    * The method exponentiates with the help of Taylor series, 
    * see {@link #expTaylorSeries(BigDecimal)}. 
    * 
    * @param    exponent
    *           The power to which the number should be raised.
    *
    * @return   The exponentiated value or <code>null</code>
    *           when the input is <code>null</code>. 
    */
   private static BigDecimal exp(BigDecimal exponent)
   {
      if (exponent == null)
      {
         return null;
      }

      // zero exponent?
      if (exponent.signum() == 0)
      {
         return BigDecimal.ONE;
      }

      // for negative exponents, invert the exponentiation
      if (exponent.signum() == -1)
      {
         return BigDecimal.ONE.divide(exp(exponent.negate()), EXP_MCONTEXT.getPrecision(),
               RoundingMode.HALF_UP);
      }

      // get rid of exponent's decimal component
      BigDecimal wholeExponent = exponent.setScale(0, RoundingMode.HALF_UP);

      // only decimal component?
      if (wholeExponent.signum() == 0)
      {
         // calculate the Taylor series directly
         return expTaylorSeries(exponent);
      }

      // get the decimal component
      BigDecimal decimalExponent = exponent.subtract(wholeExponent);

      // calculate e^(1 + decimalExponent / wholeExponent)
      BigDecimal taylorInput = BigDecimal.valueOf(1).add(
            decimalExponent.divide(wholeExponent, EXP_MCONTEXT.getPrecision(),
                  RoundingMode.HALF_UP));
      BigDecimal taylor = expTaylorSeries(taylorInput);

      // get the result back to current context
      return BigDecimal.ONE.multiply(taylor.pow(wholeExponent.intValue(), EXP_MCONTEXT)).setScale(
            EXP_MCONTEXT.getPrecision(), RoundingMode.HALF_UP);
   }
   
   /**
    * Raise e (Euler's number) to the power of the second number (the
    * exponent). The method implements the Taylor series exponentiation.
    * <p>
    * Do not call the method directly. This method is used only by
    * {@link #exp(BigDecimal)}.
    * 
    * @param    exponent
    *           The power to which the number should be raised.
    *           Must be a valid exponent - a positive decimal number 
    *           with nonzero decimal component.
    *
    * @return   The exponentiated value. 
    */
   private static BigDecimal expTaylorSeries(BigDecimal exponent)
   {
      // round one
      BigDecimal result = exponent.add(BigDecimal.valueOf(1));

      BigDecimal roundExp = exponent;
      BigDecimal factorial = BigDecimal.valueOf(1);
      BigDecimal prevResult;

      // round two+
      int round = 2;
      do
      {
         // exponent^i
         roundExp = roundExp.multiply(exponent).setScale(EXP_MCONTEXT.getPrecision(),
               RoundingMode.HALF_UP);
         // round!
         factorial = factorial.multiply(BigDecimal.valueOf(round));

         // exponent^(round/round)!
         BigDecimal seriesPart = roundExp.divide(factorial, EXP_MCONTEXT.getPrecision(),
               RoundingMode.HALF_UP);
         prevResult = result;

         // result += exponent^(round/round)!
         result = result.add(seriesPart);
         round++;

         // two successive sums must be equal to stop the loop
      } 
      while (result.compareTo(prevResult) != 0);

      return result;
   }
}