You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2020/02/26 17:24:31 UTC
[commons-jexl] branch master updated: JEXL-277: fast path basic
arithmetic ops on primitive relative numbers (long, int, short...),
check overflows Task #JEXL-277 - Speedup arithmetic evaluations
This is an automated email from the ASF dual-hosted git repository.
henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
The following commit(s) were added to refs/heads/master by this push:
new bfa64f3 JEXL-277: fast path basic arithmetic ops on primitive relative numbers (long, int, short...), check overflows Task #JEXL-277 - Speedup arithmetic evaluations
bfa64f3 is described below
commit bfa64f34a7a03e025e2ff7f4e458094ef349114c
Author: henrib <he...@apache.org>
AuthorDate: Wed Feb 26 18:23:19 2020 +0100
JEXL-277: fast path basic arithmetic ops on primitive relative numbers (long, int, short...), check overflows
Task #JEXL-277 - Speedup arithmetic evaluations
---
.../org/apache/commons/jexl3/JexlArithmetic.java | 120 ++++++++++++++++++++-
.../org/apache/commons/jexl3/ArithmeticTest.java | 10 ++
2 files changed, 127 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index add0d0f..89b602f 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -25,6 +25,7 @@ import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.Collection;
+import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
@@ -661,6 +662,26 @@ public class JexlArithmetic {
}
/**
+ * Checks if value class is a number that can be represented exactly in an int.
+ *
+ * @param value argument
+ * @return true if argument can be represented by an integer
+ */
+ private static Number asIntegerNumber(Object value) {
+ return value instanceof Integer || value instanceof Short || value instanceof Byte ? (Number) value : null;
+ }
+
+ /**
+ * Checks if value class is a number that can be represented exactly in a long.
+ *
+ * @param value argument
+ * @return true if argument can be represented by a long
+ */
+ private static Number asLongNumber(Object value) {
+ return value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte ? (Number) value : null;
+ }
+
+ /**
* Add two values together.
* <p>
* If any numeric add fails on coercion to the appropriate type,
@@ -680,6 +701,22 @@ public class JexlArithmetic {
: left instanceof String && right instanceof String;
if (!strconcat) {
try {
+ // if both (non null) args fit as long
+ Number ln = asLongNumber(left);
+ Number rn = asLongNumber(right);
+ if (ln != null && rn != null) {
+ long x = ln.longValue();
+ long y = rn.longValue();
+ long r = x + y;
+ // detect overflow, see java8 Math.addExact
+ if (((x ^ r) & (y ^ r)) < 0) {
+ return BigInteger.valueOf(x).add(BigInteger.valueOf(y));
+ }
+ if (!(left instanceof Long || right instanceof Long) && (int) r == r) {
+ return (int) r;
+ }
+ return r;
+ }
// if either are bigdecimal use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
BigDecimal l = toBigDecimal(left);
@@ -693,7 +730,7 @@ public class JexlArithmetic {
double r = toDouble(right);
return l + r;
}
- // otherwise treat as integers
+ // otherwise treat as (big) integers
BigInteger l = toBigInteger(left);
BigInteger r = toBigInteger(right);
BigInteger result = l.add(r);
@@ -719,6 +756,21 @@ public class JexlArithmetic {
if (left == null && right == null) {
return controlNullNullOperands();
}
+ // if both (non null) args fit as long
+ Number ln = asLongNumber(left);
+ Number rn = asLongNumber(right);
+ if (ln != null && rn != null) {
+ long l = ln.longValue();
+ long r = rn.longValue();
+ if (r == 0L) {
+ throw new ArithmeticException("/");
+ }
+ long result = l / r;
+ if (!(left instanceof Long || right instanceof Long) && (int) result == result) {
+ return (int) result;
+ }
+ return result;
+ }
// if either are bigdecimal use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
BigDecimal l = toBigDecimal(left);
@@ -760,6 +812,21 @@ public class JexlArithmetic {
if (left == null && right == null) {
return controlNullNullOperands();
}
+ // if both (non null) args fit as long
+ Number ln = asLongNumber(left);
+ Number rn = asLongNumber(right);
+ if (ln != null && rn != null) {
+ long l = ln.longValue();
+ long r = rn.longValue();
+ if (r == 0L) {
+ throw new ArithmeticException("%");
+ }
+ long result = l % r;
+ if (!(left instanceof Long || right instanceof Long) && (int) result == result) {
+ return (int) result;
+ }
+ return result;
+ }
// if either are bigdecimal use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
BigDecimal l = toBigDecimal(left);
@@ -790,6 +857,22 @@ public class JexlArithmetic {
}
/**
+ * Checks if the product of the arguments overflows a {@code long}.
+ * <p>see java8 Math.multiplyExact
+ * @param x the first value
+ * @param y the second value
+ * @param r the product
+ * @return true if product fits a long, false if it overflows
+ */
+ private static boolean isMultiplyExact(long x, long y, long r) {
+ long ax = Math.abs(x);
+ long ay = Math.abs(y);
+ return !(((ax | ay) >>> 31 != 0) &&
+ (((y != 0) && (r / y != x)) ||
+ (x == Long.MIN_VALUE && y == -1)));
+ }
+
+ /**
* Multiply the left value by the right.
*
* @param left left argument
@@ -800,6 +883,22 @@ public class JexlArithmetic {
if (left == null && right == null) {
return controlNullNullOperands();
}
+ // if both (non null) args fit as int
+ Number ln = asIntegerNumber(left);
+ Number rn = asIntegerNumber(right);
+ if (ln != null && rn != null) {
+ long x = ln.longValue();
+ long y = rn.longValue();
+ long r = x * y;
+ // detect overflow
+ if (!isMultiplyExact(x, y, r)) {
+ return BigInteger.valueOf(x).multiply(BigInteger.valueOf(y));
+ }
+ if (!(left instanceof Long || right instanceof Long) && (int) r == r) {
+ return (int) r;
+ }
+ return r;
+ }
// if either are bigdecimal use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
BigDecimal l = toBigDecimal(left);
@@ -831,6 +930,22 @@ public class JexlArithmetic {
if (left == null && right == null) {
return controlNullNullOperands();
}
+ // if both (non null) args fit as long
+ Number ln = asLongNumber(left);
+ Number rn = asLongNumber(right);
+ if (ln != null && rn != null) {
+ long x = ln.longValue();
+ long y = rn.longValue();
+ long r = x - y;
+ // detect overflow, see java8 Math.subtractExact
+ if (((x ^ y) & (x ^ r)) < 0) {
+ return BigInteger.valueOf(x).subtract(BigInteger.valueOf(y));
+ }
+ if ((int) r == r) {
+ return (int) r;
+ }
+ return r;
+ }
// if either are bigdecimal use that type
if (left instanceof BigDecimal || right instanceof BigDecimal) {
BigDecimal l = toBigDecimal(left);
@@ -1075,7 +1190,6 @@ public class JexlArithmetic {
if (object instanceof Map<?, ?>) {
return ((Map<?, ?>) object).isEmpty() ? Boolean.TRUE : Boolean.FALSE;
}
- // we can not determine for sure...
return def;
}
@@ -1109,7 +1223,7 @@ public class JexlArithmetic {
if (object instanceof Map<?, ?>) {
return ((Map<?, ?>) object).size();
}
- return null;
+ return def;
}
/**
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
index bd59ad4..245f64d 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
@@ -127,6 +127,16 @@ public class ArithmeticTest extends JexlTestCase {
asserter.assertExpression("right * left", new BigInteger("12"));
asserter.assertExpression("right / left", new BigInteger("3"));
asserter.assertExpression("right % left", new BigInteger("0"));
+ }
+
+ @Test
+ public void testOverflows() throws Exception {
+ asserter.assertExpression("1 + 2147483647", Long.valueOf("2147483648"));
+ asserter.assertExpression("-2147483648 - 1", Long.valueOf("-2147483649"));
+ asserter.assertExpression("1 + 9223372036854775807", new BigInteger("9223372036854775808"));
+ asserter.assertExpression("-1 + (-9223372036854775808)", new BigInteger("-9223372036854775809"));
+ asserter.assertExpression("-9223372036854775808 - 1", new BigInteger("-9223372036854775809"));
+ asserter.assertExpression("-1 - 9223372036854775808", new BigInteger("-9223372036854775809"));
}
/**