Skip to content

Commit

Permalink
#407 - add decimalPlacesResult configuration for rounding final result (
Browse files Browse the repository at this point in the history
  • Loading branch information
uklimaschewski authored Nov 19, 2023
1 parent b6290f9 commit 5fd377c
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 22 deletions.
23 changes: 20 additions & 3 deletions docs/concepts/rounding.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ EvalEx supports all rounding modes defined in _java.math.RoundingMode_:

The default rounding mode in EvalEx is _HALF_EVEN_.

### Configuring Precision, Rounding Mode and Automatic Scaling
### Configuring Precision, Rounding Mode and Automatic Rounding

Precision and rounding mode are configured through the _ExpressionConfiguration_ by specifying
the _java.math.MathContext_:
Expand All @@ -66,12 +66,12 @@ ExpressionConfiguration configuration =
.build();
```

Automatic scaling is disabled by default. When enabled, EvalEx will round all input variables,
Automatic rounding is disabled by default. When enabled, EvalEx will round all input variables,
intermediate operation and function results and the final result to the specified number of decimal
digits, using the current rounding mode:

```java
// set precision to 32 and rounding mode to HALF_UP
// set automatic rounding
ExpressionConfiguration configuration =
ExpressionConfiguration.builder()
.decimalPlacesRounding(2)
Expand All @@ -87,3 +87,20 @@ System.out.println(
.getNumberValue());
```

If only the final result should be rounded, this can be configured using the _decimalPlacesResult_:

```java
// set rounding of final result
ExpressionConfiguration configuration =
ExpressionConfiguration.builder()
.decimalPlacesResult(3)
.build();

Expression expression = new Expression("1.22222+1.22222+1.22222", configuration);

// prints 3.667
System.out.println(
expression
.evaluate()
.getNumberValue());
```
7 changes: 7 additions & 0 deletions docs/configuration/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ By default, the _ExpressionConfiguration.DEFAULT_DATE_TIME_FORMATTERS_ are used:
* _DateTimeFormatter.ISO_LOCAL_DATE_
* _DateTimeFormatter.RFC_1123_DATE_TIME_

### Decimal Places Result

If specified, only the final result of the evaluation will be rounded to the specified number of decimal digits,
using the MathContexts rounding mode.

The default value of _DECIMAL_PLACES_ROUNDING_UNLIMITED_ will disable rounding.

### Decimal Places Rounding

Specifies the amount of decimal places to round to in each operation or function.
Expand Down
45 changes: 28 additions & 17 deletions src/main/java/com/ezylang/evalex/Expression.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,22 @@ public Expression(String expressionString, ExpressionConfiguration configuration
* @throws ParseException If there were problems while parsing the expression.
*/
public EvaluationValue evaluate() throws EvaluationException, ParseException {
return evaluateSubtree(getAbstractSyntaxTree());
EvaluationValue result = evaluateSubtree(getAbstractSyntaxTree());
if (result.isNumberValue()) {
BigDecimal bigDecimal = result.getNumberValue();
if (configuration.getDecimalPlacesResult()
!= ExpressionConfiguration.DECIMAL_PLACES_ROUNDING_UNLIMITED) {
bigDecimal = roundValue(bigDecimal, configuration.getDecimalPlacesResult());
}

if (configuration.isStripTrailingZeros()) {
bigDecimal = bigDecimal.stripTrailingZeros();
}

result = EvaluationValue.numberValue(bigDecimal);
}

return result;
}

/**
Expand Down Expand Up @@ -128,8 +143,14 @@ public EvaluationValue evaluateSubtree(ASTNode startNode) throws EvaluationExcep
default:
throw new EvaluationException(token, "Unexpected evaluation token: " + token);
}
if (result.isNumberValue()
&& configuration.getDecimalPlacesRounding()
!= ExpressionConfiguration.DECIMAL_PLACES_ROUNDING_UNLIMITED) {
return EvaluationValue.numberValue(
roundValue(result.getNumberValue(), configuration.getDecimalPlacesRounding()));
}

return result.isNumberValue() ? roundAndStripZerosIfNeeded(result) : result;
return result;
}

private EvaluationValue getVariableOrConstant(Token token) throws EvaluationException {
Expand Down Expand Up @@ -192,25 +213,15 @@ private EvaluationValue evaluateStructureSeparator(ASTNode startNode) throws Eva
}

/**
* Rounds the given value, if the decimal places are configured. Also strips trailing decimal
* zeros, if configured.
* Rounds the given value.
*
* @param value The input value.
* @param decimalPlaces The number of decimal places to round to.
* @return The rounded value, or the input value if rounding is not configured or possible.
*/
private EvaluationValue roundAndStripZerosIfNeeded(EvaluationValue value) {
BigDecimal bigDecimal = value.getNumberValue();
if (configuration.getDecimalPlacesRounding()
!= ExpressionConfiguration.DECIMAL_PLACES_ROUNDING_UNLIMITED) {
bigDecimal =
bigDecimal.setScale(
configuration.getDecimalPlacesRounding(),
configuration.getMathContext().getRoundingMode());
}
if (configuration.isStripTrailingZeros()) {
bigDecimal = bigDecimal.stripTrailingZeros();
}
return EvaluationValue.numberValue(bigDecimal);
private BigDecimal roundValue(BigDecimal value, int decimalPlaces) {
value = value.setScale(decimalPlaces, configuration.getMathContext().getRoundingMode());
return value;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,14 +229,23 @@ public class ExpressionConfiguration {
@Builder.Default @Getter
private final int powerOfPrecedence = OperatorIfc.OPERATOR_PRECEDENCE_POWER;

/**
* If specified, only the final result of the evaluation will be rounded to the specified number
* of decimal digits, using the MathContexts rounding mode.
*
* <p>The default value of _DECIMAL_PLACES_ROUNDING_UNLIMITED_ will disable rounding.
*/
@Builder.Default @Getter
private final int decimalPlacesResult = DECIMAL_PLACES_ROUNDING_UNLIMITED;

/**
* If specified, all results from operations and functions will be rounded to the specified number
* of decimal digits, using the MathContexts rounding mode.
*
* <p>Automatic scaling is disabled by default. When enabled, EvalEx will round all input
* <p>Automatic rounding is disabled by default. When enabled, EvalEx will round all input
* variables, constants, intermediate operation and function results and the final result to the
* specified number of decimal digits, using the current rounding mode. Using a value of
* _DECIMAL_PLACES_ROUNDING_UNLIMITED_ will disable automatic scaling.
* _DECIMAL_PLACES_ROUNDING_UNLIMITED_ will disable automatic rounding.
*/
@Builder.Default @Getter
private final int decimalPlacesRounding = DECIMAL_PLACES_ROUNDING_UNLIMITED;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,31 @@ void testDoNotStripZeros() throws EvaluationException, ParseException {
Expression expression = new Expression("9.000", config);
assertThat(expression.evaluate().getNumberValue()).isEqualTo("9.000");
}

@Test
void testDecimalPlacesResult() throws EvaluationException, ParseException {
ExpressionConfiguration config =
ExpressionConfiguration.builder().decimalPlacesResult(3).build();
Expression expression = new Expression("1.6666+1.6666+1.6666", config);

assertThat(expression.evaluate().getStringValue()).isEqualTo("5");
}

@Test
void testDecimalPlacesResultNoStrip() throws EvaluationException, ParseException {
ExpressionConfiguration config =
ExpressionConfiguration.builder().decimalPlacesResult(3).stripTrailingZeros(false).build();
Expression expression = new Expression("1.6666+1.6666+1.6666", config);

assertThat(expression.evaluate().getStringValue()).isEqualTo("5.000");
}

@Test
void testDecimalPlacesResultAndAuto() throws EvaluationException, ParseException {
ExpressionConfiguration config =
ExpressionConfiguration.builder().decimalPlacesResult(3).decimalPlacesRounding(2).build();
Expression expression = new Expression("1.6666+1.6666+1.6666", config);

assertThat(expression.evaluate().getStringValue()).isEqualTo("5.01");
}
}

0 comments on commit 5fd377c

Please sign in to comment.