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 (> 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 > 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 > 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 > 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 > 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 > 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 > 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;
}
}