MathOption

When performing mathematical operations on MJ variables, the applicable options from the enumeration MJVariable.MathOption affect some important aspects of those operations. The options are listed and explained below.

EVALUATE_JAVA_TYPE Use Java value (e.g., intValue(), decimalValue()) when evaluating operands of mathematical operations.
EVALUATE_MAPPER_TYPE Use Mapper value (i.e., toMapperNumber()) when evaluating operands of mathematical operations.
USE_ZERO_FOR_NULL If operand of mathematical operation has NULL value, treat as zero during evaluation.
RETURN_EXACT Return result as exact, whole number only, report error if truncation of fractional digits would occur.
PREFER_DECIMAL_OVER_FLOAT Convert string literals containing real numbers (e.g., '3.14') to BigDecimal, compute fractional numbers as BigDecimal during operation and return fractional result as BigDecimal.
PREFER_FLOAT_OVER_DECIMAL Convert string literals containing real numbers (e.g., '3.14') to Double, compute fractional numbers as Double during operation and return fractional result as Double.

MJ variable methods that implement mathematical operations and math formulas both support MathOption, and each method (or formula) has a default set of MathOption values; those default options are specified in the Javadoc.

EVALUATE_JAVA_TYPE vs. EVALUATE_MAPPER_TYPE

Options EVALUATE_JAVA_TYPE and EVALUATE_MAPPER_TYPE call out a distinction in mathematical calculations carried out by @ART and @CHG. The difference is often a subtle one, but is illustrated by this example:

// @LDV <target>f3.2=1.71 .  Stored as 1.71, displayed as '1.7' [rounded-down]
MJDecimal target = new MJDecimal(VariableScope.LOCAL, 3, 2, new BigDecimal("1.71"));

// @LDV <incr>f3.2=8.76 .    Stored as 8.76, displayed as '8.8' [rounded-up]
MJDecimal incr = new MJDecimal(VariableScope.LOCAL, 3, 2, new BigDecimal("8.76"));

// @ART <target>+<incr> <rsltArt>f7.3 .    Uses displayed values: 1.7 + 8.8
Number sumArt = target.add(incr, EnumSet.of(MathOption.EVALUATE_MAPPER_TYPE));
assert sumArt instanceof BigDecimal;
MJDecimal rsltArt = new MJDecimal(VariableScope.LOCAL, 7, 3, (BigDecimal) sumArt);
assert "10.500".equals(rsltArt.toMapperNumericString());

// @CHG <rsltChg>f7.3 <target> + <incr> .  Uses stored values: 1.71 + 8.76
Number sumChg = target.add(incr, EnumSet.of(MathOption.EVALUATE_JAVA_TYPE));
assert sumChg instanceof BigDecimal;
MJDecimal rsltChg = new MJDecimal(VariableScope.LOCAL, 7, 3, (BigDecimal) sumChg);
assert "10.470".equals(rsltChg.toMapperNumericString());

Different results are obtained although the same variables are summed because @ART adds the "display value" of the variables while @CHG adds the primitive, raw values of the variables. The enumeration name EVALUATE_MAPPER_TYPE denotes use of the Mapper value (toMapperNumber()) of a variable when evaluating operands of mathematical operations, and should always be used for @ART calculations. Similarly, EVALUATE_JAVA_TYPE denotes use of the raw, Java primitive value of a variable, and should always be used for @CHG calculations.

USE_ZERO_FOR_NULL

This option allow zero to "stand in" for variables whose value is NULL during a mathematical operation:

// @LDV <target>f3.2=1.71 .
MJDecimal target = new MJDecimal(VariableScope.LOCAL, 3, 2, new BigDecimal("1.71"));

// @INC,999.5 <target> .
target.increment(new BigDecimal("999.5"));

// <target> displayed by MAPPER as '***' since 1001 does not fit in F3.2
assert 100100 == target.decimalValue().unscaledValue().longValue();
assert null == target.toMapperNumber();

// @ART <target>+1 <rslt>f9.3
// @. MAPPER fails on above with "There is an invalid character in this expression",
// @. since @ART uses display value of <target> in calculation, which is '***'
try {
  Number sum = target.add(1L, EnumSet.of(MathOption.EVALUATE_MAPPER_TYPE));
}
catch (MJExecuteException ex) {
  // "cannot add() to variable 1001.00: variable is NULL and USE_ZERO_FOR_NULL not specified"
  ex.printStackTrace();
}

// Try again where NULL returned from target.toMapperNumber() is treated as zero,
// essentially becoming @ART 0+1.
Number sum = target.add(1L, EnumSet.of(MathOption.EVALUATE_MAPPER_TYPE,
  MathOption.USE_ZERO_FOR_NULL));
assert sum instanceof BigDecimal;
MJDecimal rslt = new MJDecimal(VariableScope.LOCAL, 9, 3, (BigDecimal) sum);
assert "1.000".equals(rslt.toMapperNumericString());

RETURN_EXACT

Similarly to java.math.BigDecimal.longValueExact, this option throws an exception if the result of mathematical operation contains a fractional part (therefore, use RETURN_EXACT only when the result is expected to be a whole number):

// @LDV <dvr>f1=3 .
MJDecimal dvr = new MJDecimal(VariableScope.LOCAL, 1, 0, new BigDecimal("3"));

// @CHG <quot>f9.3 10 / <dvr> .
try {
  Number quot = MJMath.divide(new Integer(10), dvr,
    EnumSet.of(MathOption.EVALUATE_JAVA_TYPE, MathOption.RETURN_EXACT));
}
catch (MJExecuteException ex) {
  // "MathOption.RETURN_EXACT specified but 10 divided by 3 has fractional part"
  ex.printStackTrace();
}

PREFER_DECIMAL_OVER_FLOAT vs. PREFER_FLOAT_OVER_DECIMAL

When mathematical operations on whole integers (division, for example) result in a fractional number, these options specify which Number-derived class stores the result, java.math.BigDecimal (PREFER_DECIMAL_OVER_FLOAT) or java.math.Double (PREFER_FLOAT_OVER_DECIMAL). Applicable mostly to MJInteger and MJMath methods that carry out division, but consult the Javadoc to determine exactly which methods support these options:

// @LDV <dvd>i4=22, <dvi>i4=7 . Whole integers
MJInteger dvd = new MJInteger(VariableScope.LOCAL, 4, EnumSet.noneOf(LoadOption.class), "22");
MJInteger dvi = new MJInteger(VariableScope.LOCAL, 4, EnumSet.noneOf(LoadOption.class), "7");

// @ART <dvd>/<dvi> <quot>f18.17 .
Number decQuot = dvd.divide(dvi, EnumSet.of(MathOption.EVALUATE_MAPPER_TYPE,
  MathOption.PREFER_DECIMAL_OVER_FLOAT));
assert decQuot instanceof BigDecimal;

// @ART <dvd>/<dvi> <quot>f18.17 . Same as above, except result is java.lang.Double
Number dblQuot = dvd.divide(dvi, EnumSet.of(MathOption.EVALUATE_MAPPER_TYPE,
  MathOption.PREFER_FLOAT_OVER_DECIMAL));
assert dblQuot instanceof Double;