Skip to content

Commit 5fd377c

Browse files
#407 - add decimalPlacesResult configuration for rounding final result (#408)
1 parent b6290f9 commit 5fd377c

File tree

5 files changed

+93
-22
lines changed

5 files changed

+93
-22
lines changed

docs/concepts/rounding.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ EvalEx supports all rounding modes defined in _java.math.RoundingMode_:
5353

5454
The default rounding mode in EvalEx is _HALF_EVEN_.
5555

56-
### Configuring Precision, Rounding Mode and Automatic Scaling
56+
### Configuring Precision, Rounding Mode and Automatic Rounding
5757

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

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

7373
```java
74-
// set precision to 32 and rounding mode to HALF_UP
74+
// set automatic rounding
7575
ExpressionConfiguration configuration =
7676
ExpressionConfiguration.builder()
7777
.decimalPlacesRounding(2)
@@ -87,3 +87,20 @@ System.out.println(
8787
.getNumberValue());
8888
```
8989

90+
If only the final result should be rounded, this can be configured using the _decimalPlacesResult_:
91+
92+
```java
93+
// set rounding of final result
94+
ExpressionConfiguration configuration =
95+
ExpressionConfiguration.builder()
96+
.decimalPlacesResult(3)
97+
.build();
98+
99+
Expression expression = new Expression("1.22222+1.22222+1.22222", configuration);
100+
101+
// prints 3.667
102+
System.out.println(
103+
expression
104+
.evaluate()
105+
.getNumberValue());
106+
```

docs/configuration/configuration.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ By default, the _ExpressionConfiguration.DEFAULT_DATE_TIME_FORMATTERS_ are used:
6666
* _DateTimeFormatter.ISO_LOCAL_DATE_
6767
* _DateTimeFormatter.RFC_1123_DATE_TIME_
6868

69+
### Decimal Places Result
70+
71+
If specified, only the final result of the evaluation will be rounded to the specified number of decimal digits,
72+
using the MathContexts rounding mode.
73+
74+
The default value of _DECIMAL_PLACES_ROUNDING_UNLIMITED_ will disable rounding.
75+
6976
### Decimal Places Rounding
7077

7178
Specifies the amount of decimal places to round to in each operation or function.

src/main/java/com/ezylang/evalex/Expression.java

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,22 @@ public Expression(String expressionString, ExpressionConfiguration configuration
7373
* @throws ParseException If there were problems while parsing the expression.
7474
*/
7575
public EvaluationValue evaluate() throws EvaluationException, ParseException {
76-
return evaluateSubtree(getAbstractSyntaxTree());
76+
EvaluationValue result = evaluateSubtree(getAbstractSyntaxTree());
77+
if (result.isNumberValue()) {
78+
BigDecimal bigDecimal = result.getNumberValue();
79+
if (configuration.getDecimalPlacesResult()
80+
!= ExpressionConfiguration.DECIMAL_PLACES_ROUNDING_UNLIMITED) {
81+
bigDecimal = roundValue(bigDecimal, configuration.getDecimalPlacesResult());
82+
}
83+
84+
if (configuration.isStripTrailingZeros()) {
85+
bigDecimal = bigDecimal.stripTrailingZeros();
86+
}
87+
88+
result = EvaluationValue.numberValue(bigDecimal);
89+
}
90+
91+
return result;
7792
}
7893

7994
/**
@@ -128,8 +143,14 @@ public EvaluationValue evaluateSubtree(ASTNode startNode) throws EvaluationExcep
128143
default:
129144
throw new EvaluationException(token, "Unexpected evaluation token: " + token);
130145
}
146+
if (result.isNumberValue()
147+
&& configuration.getDecimalPlacesRounding()
148+
!= ExpressionConfiguration.DECIMAL_PLACES_ROUNDING_UNLIMITED) {
149+
return EvaluationValue.numberValue(
150+
roundValue(result.getNumberValue(), configuration.getDecimalPlacesRounding()));
151+
}
131152

132-
return result.isNumberValue() ? roundAndStripZerosIfNeeded(result) : result;
153+
return result;
133154
}
134155

135156
private EvaluationValue getVariableOrConstant(Token token) throws EvaluationException {
@@ -192,25 +213,15 @@ private EvaluationValue evaluateStructureSeparator(ASTNode startNode) throws Eva
192213
}
193214

194215
/**
195-
* Rounds the given value, if the decimal places are configured. Also strips trailing decimal
196-
* zeros, if configured.
216+
* Rounds the given value.
197217
*
198218
* @param value The input value.
219+
* @param decimalPlaces The number of decimal places to round to.
199220
* @return The rounded value, or the input value if rounding is not configured or possible.
200221
*/
201-
private EvaluationValue roundAndStripZerosIfNeeded(EvaluationValue value) {
202-
BigDecimal bigDecimal = value.getNumberValue();
203-
if (configuration.getDecimalPlacesRounding()
204-
!= ExpressionConfiguration.DECIMAL_PLACES_ROUNDING_UNLIMITED) {
205-
bigDecimal =
206-
bigDecimal.setScale(
207-
configuration.getDecimalPlacesRounding(),
208-
configuration.getMathContext().getRoundingMode());
209-
}
210-
if (configuration.isStripTrailingZeros()) {
211-
bigDecimal = bigDecimal.stripTrailingZeros();
212-
}
213-
return EvaluationValue.numberValue(bigDecimal);
222+
private BigDecimal roundValue(BigDecimal value, int decimalPlaces) {
223+
value = value.setScale(decimalPlaces, configuration.getMathContext().getRoundingMode());
224+
return value;
214225
}
215226

216227
/**

src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,14 +229,23 @@ public class ExpressionConfiguration {
229229
@Builder.Default @Getter
230230
private final int powerOfPrecedence = OperatorIfc.OPERATOR_PRECEDENCE_POWER;
231231

232+
/**
233+
* If specified, only the final result of the evaluation will be rounded to the specified number
234+
* of decimal digits, using the MathContexts rounding mode.
235+
*
236+
* <p>The default value of _DECIMAL_PLACES_ROUNDING_UNLIMITED_ will disable rounding.
237+
*/
238+
@Builder.Default @Getter
239+
private final int decimalPlacesResult = DECIMAL_PLACES_ROUNDING_UNLIMITED;
240+
232241
/**
233242
* If specified, all results from operations and functions will be rounded to the specified number
234243
* of decimal digits, using the MathContexts rounding mode.
235244
*
236-
* <p>Automatic scaling is disabled by default. When enabled, EvalEx will round all input
245+
* <p>Automatic rounding is disabled by default. When enabled, EvalEx will round all input
237246
* variables, constants, intermediate operation and function results and the final result to the
238247
* specified number of decimal digits, using the current rounding mode. Using a value of
239-
* _DECIMAL_PLACES_ROUNDING_UNLIMITED_ will disable automatic scaling.
248+
* _DECIMAL_PLACES_ROUNDING_UNLIMITED_ will disable automatic rounding.
240249
*/
241250
@Builder.Default @Getter
242251
private final int decimalPlacesRounding = DECIMAL_PLACES_ROUNDING_UNLIMITED;

src/test/java/com/ezylang/evalex/ExpressionEvaluatorDecimalPlacesTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,31 @@ void testDoNotStripZeros() throws EvaluationException, ParseException {
163163
Expression expression = new Expression("9.000", config);
164164
assertThat(expression.evaluate().getNumberValue()).isEqualTo("9.000");
165165
}
166+
167+
@Test
168+
void testDecimalPlacesResult() throws EvaluationException, ParseException {
169+
ExpressionConfiguration config =
170+
ExpressionConfiguration.builder().decimalPlacesResult(3).build();
171+
Expression expression = new Expression("1.6666+1.6666+1.6666", config);
172+
173+
assertThat(expression.evaluate().getStringValue()).isEqualTo("5");
174+
}
175+
176+
@Test
177+
void testDecimalPlacesResultNoStrip() throws EvaluationException, ParseException {
178+
ExpressionConfiguration config =
179+
ExpressionConfiguration.builder().decimalPlacesResult(3).stripTrailingZeros(false).build();
180+
Expression expression = new Expression("1.6666+1.6666+1.6666", config);
181+
182+
assertThat(expression.evaluate().getStringValue()).isEqualTo("5.000");
183+
}
184+
185+
@Test
186+
void testDecimalPlacesResultAndAuto() throws EvaluationException, ParseException {
187+
ExpressionConfiguration config =
188+
ExpressionConfiguration.builder().decimalPlacesResult(3).decimalPlacesRounding(2).build();
189+
Expression expression = new Expression("1.6666+1.6666+1.6666", config);
190+
191+
assertThat(expression.evaluate().getStringValue()).isEqualTo("5.01");
192+
}
166193
}

0 commit comments

Comments
 (0)