diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArithmeticFunctions.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArithmeticFunctions.java index 27c4952b1fcf..d27a3fa6cccd 100644 --- a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArithmeticFunctions.java +++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArithmeticFunctions.java @@ -40,11 +40,59 @@ public static double divide(double a, double b, double defaultValue) { return (b == 0) ? defaultValue : a / b; } + @ScalarFunction + public static long intDiv(double a, double b) { + return (long) Math.floor(a / b); + } + + @ScalarFunction + public static long intDivOrZero(double a, double b) { + //Same as intDiv but returns zero when dividing by zero or when dividing a minimal negative number by minus one. + return (b == 0 || (a == Long.MIN_VALUE && b == -1)) ? 0 : intDiv(a, b); + } + + @ScalarFunction + public static int isFinite(double value) { + return Double.isFinite(value) ? 1 : 0; + } + + @ScalarFunction + public static int isInfinite(double value) { + return Double.isInfinite(value) ? 1 : 0; + } + + @ScalarFunction + public static double ifNotFinite(double valueToCheck, double defaultValue) { + return Double.isFinite(valueToCheck) ? valueToCheck : defaultValue; + } + + @ScalarFunction + public static int isNaN(double value) { + return Double.isNaN(value) ? 1 : 0; + } + @ScalarFunction public static double mod(double a, double b) { return a % b; } + @ScalarFunction + public static double moduloOrZero(double a, double b) { + //Same as mod but returns zero when dividing by zero or when dividing a minimal negative number by minus one. + return (b == 0 || (a == Long.MIN_VALUE && b == -1)) ? 0 : mod(a, b); + } + + @ScalarFunction + public static double positiveModulo(double a, double b) { + double result = a % b; + return result >= 0 ? result : result + Math.abs(b); + } + + @ScalarFunction + public static double negate(double a) { + return -a; + } + @ScalarFunction public static double least(double a, double b) { return Double.min(a, b); @@ -117,7 +165,6 @@ public static double power(double a, double exponent) { return Math.pow(a, exponent); } - // Big Decimal Implementation has been used here to avoid overflows // when multiplying by Math.pow(10, scale) for rounding @ScalarFunction @@ -143,4 +190,33 @@ public static double truncate(double a, int scale) { public static double truncate(double a) { return Math.signum(a) * Math.floor(Math.abs(a)); } + + @ScalarFunction + public static long gcd(long a, long b) { + return a == 0 ? Math.abs(b) : gcd(b % a, a); + } + + @ScalarFunction + public static long lcm(long a, long b) { + if (a == 0 || b == 0) { + return 0; + } + return Math.abs(a) / gcd(a, b) * Math.abs(b); + } + + @ScalarFunction + public static double hypot(double a, double b) { + return Math.hypot(a, b); + } + + @ScalarFunction + public static int byteswapInt(int a) { + return Integer.reverseBytes(a); + } + + @ScalarFunction + public static long byteswapLong(long a) { + // Skip the heading 0s in the long value + return Long.reverseBytes(a); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/function/ArithmeticFunctionsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/function/ArithmeticFunctionsTest.java index 404444933e42..61d62e45318e 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/data/function/ArithmeticFunctionsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/function/ArithmeticFunctionsTest.java @@ -49,58 +49,360 @@ public void testArithmeticFunctions(String functionExpression, List expe @DataProvider(name = "arithmeticFunctionsDataProvider") public Object[][] arithmeticFunctionsDataProvider() { List inputs = new ArrayList<>(); + // test add + { + GenericRow row = new GenericRow(); + row.putValue("a", (byte) 1); + row.putValue("b", (char) 2); + inputs.add(new Object[]{"a + b", Lists.newArrayList("a", "b"), row, 3.0}); + inputs.add(new Object[]{"add(a, b)", Lists.newArrayList("a", "b"), row, 3.0}); + inputs.add(new Object[]{"plus(a, b)", Lists.newArrayList("a", "b"), row, 3.0}); + } + // test subtract + { + GenericRow row = new GenericRow(); + row.putValue("a", (short) 3); + row.putValue("b", 4); + inputs.add(new Object[]{"a - b", Lists.newArrayList("a", "b"), row, -1.0}); + } + // test multiply + { + GenericRow row = new GenericRow(); + row.putValue("a", 5); + row.putValue("b", 6); + inputs.add(new Object[]{"a * b", Lists.newArrayList("a", "b"), row, 30.0}); + inputs.add(new Object[]{"mult(a, b)", Lists.newArrayList("a", "b"), row, 30.0}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", 5L); + row.putValue("b", 6f); + inputs.add(new Object[]{"a * b", Lists.newArrayList("a", "b"), row, 30.0}); + inputs.add(new Object[]{"mult(a, b)", Lists.newArrayList("a", "b"), row, 30.0}); + } + // test divide + { + GenericRow row = new GenericRow(); + row.putValue("a", 7.0); + row.putValue("b", 8); + inputs.add(new Object[]{"a / b", Lists.newArrayList("a", "b"), row, 0.875}); + inputs.add(new Object[]{"div(a, b)", Lists.newArrayList("a", "b"), row, 0.875}); + inputs.add(new Object[]{"divide(a, b)", Lists.newArrayList("a", "b"), row, 0.875}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", 7.0); + row.putValue("b", "8"); + inputs.add(new Object[]{"a / b", Lists.newArrayList("a", "b"), row, 0.875}); + inputs.add(new Object[]{"div(a, b)", Lists.newArrayList("a", "b"), row, 0.875}); + inputs.add(new Object[]{"divide(a, b)", Lists.newArrayList("a", "b"), row, 0.875}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", 1.0); + row.putValue("b", "0.0001"); + inputs.add(new Object[]{"intdiv(a, b)", Lists.newArrayList("a", "b"), row, 10000L}); + inputs.add(new Object[]{"intDivOrZero(a, b)", Lists.newArrayList("a", "b"), row, 10000L}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", 1.0); + row.putValue("b", "0"); + inputs.add(new Object[]{"divide(a, b, 0)", Lists.newArrayList("a", "b"), row, 0.0}); + inputs.add(new Object[]{"intDivOrZero(a, b)", Lists.newArrayList("a", "b"), row, 0L}); + } + // test isFinite + { + GenericRow row = new GenericRow(); + row.putValue("a", 1.0); + inputs.add(new Object[]{"isFinite(a)", Lists.newArrayList("a"), row, 1}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", Double.POSITIVE_INFINITY); + inputs.add(new Object[]{"isFinite(a)", Lists.newArrayList("a"), row, 0}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", Double.NEGATIVE_INFINITY); + inputs.add(new Object[]{"isFinite(a)", Lists.newArrayList("a"), row, 0}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", Double.NaN); + inputs.add(new Object[]{"isFinite(a)", Lists.newArrayList("a"), row, 0}); + } + // test isInfinite + { + GenericRow row = new GenericRow(); + row.putValue("a", 1.0); + inputs.add(new Object[]{"isInfinite(a)", Lists.newArrayList("a"), row, 0}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", Double.POSITIVE_INFINITY); + inputs.add(new Object[]{"isInfinite(a)", Lists.newArrayList("a"), row, 1}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", Double.NEGATIVE_INFINITY); + inputs.add(new Object[]{"isInfinite(a)", Lists.newArrayList("a"), row, 1}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", Double.NaN); + inputs.add(new Object[]{"isInfinite(a)", Lists.newArrayList("a"), row, 0}); + } + // test ifNotFinite + { + GenericRow row = new GenericRow(); + row.putValue("a", 1.0); + inputs.add(new Object[]{"ifNotFinite(a, 2.0)", Lists.newArrayList("a"), row, 1.0}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", Double.POSITIVE_INFINITY); + inputs.add(new Object[]{"ifNotFinite(a, 2.0)", Lists.newArrayList("a"), row, 2.0}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", Double.NEGATIVE_INFINITY); + inputs.add(new Object[]{"ifNotFinite(a, 2.0)", Lists.newArrayList("a"), row, 2.0}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", Double.NaN); + inputs.add(new Object[]{"ifNotFinite(a, 2.0)", Lists.newArrayList("a"), row, 2.0}); + } + // test isNaN + { + GenericRow row = new GenericRow(); + row.putValue("a", 1.0); + inputs.add(new Object[]{"isNaN(a)", Lists.newArrayList("a"), row, 0}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", Double.POSITIVE_INFINITY); + inputs.add(new Object[]{"isNaN(a)", Lists.newArrayList("a"), row, 0}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", Double.NEGATIVE_INFINITY); + inputs.add(new Object[]{"isNaN(a)", Lists.newArrayList("a"), row, 0}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", Double.NaN); + inputs.add(new Object[]{"isNaN(a)", Lists.newArrayList("a"), row, 1}); + } + // test mod + { + GenericRow row = new GenericRow(); + row.putValue("a", 9); + row.putValue("b", 5); + inputs.add(new Object[]{"a % b", Lists.newArrayList("a", "b"), row, 4.0}); + inputs.add(new Object[]{"mod(a, b)", Lists.newArrayList("a", "b"), row, 4.0}); + inputs.add(new Object[]{"moduloOrZero(a, b)", Lists.newArrayList("a", "b"), row, 4.0}); + } + // test moduloOrZero + { + GenericRow row = new GenericRow(); + row.putValue("a", 9); + row.putValue("b", 0); + inputs.add(new Object[]{"moduloOrZero(a, b)", Lists.newArrayList("a", "b"), row, 0.0}); + } + // test positiveModulo + { + GenericRow row = new GenericRow(); + row.putValue("a", 9); + row.putValue("b", 5); + inputs.add(new Object[]{"positiveModulo(a, b)", Lists.newArrayList("a", "b"), row, 4.0}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", 9); + row.putValue("b", -5); + inputs.add(new Object[]{"positiveModulo(a, b)", Lists.newArrayList("a", "b"), row, 4.0}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", -9); + row.putValue("b", 5); + inputs.add(new Object[]{"positiveModulo(a, b)", Lists.newArrayList("a", "b"), row, 1.0}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", -9); + row.putValue("b", -5); + inputs.add(new Object[]{"positiveModulo(a, b)", Lists.newArrayList("a", "b"), row, 1.0}); + } + // test negate + { + GenericRow row = new GenericRow(); + row.putValue("a", 9); + inputs.add(new Object[]{"negate(a)", Lists.newArrayList("a"), row, -9.0}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", -9); + inputs.add(new Object[]{"negate(a)", Lists.newArrayList("a"), row, 9.0}); + } - GenericRow row0 = new GenericRow(); - row0.putValue("a", (byte) 1); - row0.putValue("b", (char) 2); - inputs.add(new Object[]{"a + b", Lists.newArrayList("a", "b"), row0, 3.0}); - - GenericRow row1 = new GenericRow(); - row1.putValue("a", (short) 3); - row1.putValue("b", 4); - inputs.add(new Object[]{"a - b", Lists.newArrayList("a", "b"), row1, -1.0}); - - GenericRow row2 = new GenericRow(); - row2.putValue("a", 5L); - row2.putValue("b", 6f); - inputs.add(new Object[]{"a * b", Lists.newArrayList("a", "b"), row2, 30.0}); - - GenericRow row3 = new GenericRow(); - row3.putValue("a", 7.0); - row3.putValue("b", "8"); - inputs.add(new Object[]{"a / b", Lists.newArrayList("a", "b"), row3, 0.875}); - - GenericRow row4 = new GenericRow(); - row4.putValue("a", 9); - row4.putValue("b", 5); - inputs.add(new Object[]{"a % b", Lists.newArrayList("a", "b"), row4, 4.0}); - - GenericRow row5 = new GenericRow(); - row5.putValue("a", 9); - row5.putValue("b", 5); - inputs.add(new Object[]{"least(a, b)", Lists.newArrayList("a", "b"), row5, 5.0}); - inputs.add(new Object[]{"greatest(a, b)", Lists.newArrayList("a", "b"), row5, 9.0}); - - GenericRow row6 = new GenericRow(); - row6.putValue("a", 9.5); - inputs.add(new Object[]{"floor(a)", Lists.newArrayList("a"), row6, 9.0}); - inputs.add(new Object[]{"ceil(a)", Lists.newArrayList("a"), row6, 10.0}); - inputs.add(new Object[]{"exp(a)", Lists.newArrayList("a"), row6, Math.exp(9.5)}); - inputs.add(new Object[]{"sqrt(a)", Lists.newArrayList("a"), row6, Math.sqrt(9.5)}); - inputs.add(new Object[]{"ln(a)", Lists.newArrayList("a"), row6, Math.log(9.5)}); - inputs.add(new Object[]{"log10(a)", Lists.newArrayList("a"), row6, Math.log10(9.5)}); - inputs.add(new Object[]{"log2(a)", Lists.newArrayList("a"), row6, Math.log(9.5) / Math.log(2.0)}); - - GenericRow row7 = new GenericRow(); - row7.putValue("a", -9.5); - inputs.add(new Object[]{"sign(a)", Lists.newArrayList("a"), row6, 1.0}); - inputs.add(new Object[]{"sign(a)", Lists.newArrayList("a"), row7, -1.0}); - - GenericRow row8 = new GenericRow(); - row8.putValue("a", 9.5); - row8.putValue("b", 0); - inputs.add(new Object[]{"divide(a, b, 0)", Lists.newArrayList("a", "b"), row8, 0.0}); + // test least/greatest + { + GenericRow row = new GenericRow(); + row.putValue("a", 9); + row.putValue("b", 5); + inputs.add(new Object[]{"least(a, b)", Lists.newArrayList("a", "b"), row, 5.0}); + inputs.add(new Object[]{"greatest(a, b)", Lists.newArrayList("a", "b"), row, 9.0}); + } + // test abs, sign, floor, ceil, exp, sqrt, ln, log10, log2, power + { + GenericRow row = new GenericRow(); + row.putValue("a", 9.5); + row.putValue("b", -9.5); + inputs.add(new Object[]{"abs(a)", Lists.newArrayList("a"), row, 9.5}); + inputs.add(new Object[]{"abs(b)", Lists.newArrayList("b"), row, 9.5}); + inputs.add(new Object[]{"sign(a)", Lists.newArrayList("a"), row, 1.0}); + inputs.add(new Object[]{"sign(b)", Lists.newArrayList("b"), row, -1.0}); + inputs.add(new Object[]{"floor(a)", Lists.newArrayList("a"), row, 9.0}); + inputs.add(new Object[]{"ceil(a)", Lists.newArrayList("a"), row, 10.0}); + inputs.add(new Object[]{"exp(a)", Lists.newArrayList("a"), row, Math.exp(9.5)}); + inputs.add(new Object[]{"sqrt(a)", Lists.newArrayList("a"), row, Math.sqrt(9.5)}); + inputs.add(new Object[]{"ln(a)", Lists.newArrayList("a"), row, Math.log(9.5)}); + inputs.add(new Object[]{"log10(a)", Lists.newArrayList("a"), row, Math.log10(9.5)}); + inputs.add(new Object[]{"log2(a)", Lists.newArrayList("a"), row, Math.log(9.5) / Math.log(2.0)}); + inputs.add(new Object[]{"power(a, 2)", Lists.newArrayList("a"), row, 9.5 * 9.5}); + } + // test roundDecimal + { + GenericRow row = new GenericRow(); + row.putValue("a", 9.5); + inputs.add(new Object[]{"roundDecimal(a)", Lists.newArrayList("a"), row, 10.0}); + inputs.add(new Object[]{"roundDecimal(a, 0)", Lists.newArrayList("a"), row, 10.0}); + inputs.add(new Object[]{"roundDecimal(a, 1)", Lists.newArrayList("a"), row, 9.5}); + inputs.add(new Object[]{"roundDecimal(a, 2)", Lists.newArrayList("a"), row, 9.5}); + inputs.add(new Object[]{"roundDecimal(a, 3)", Lists.newArrayList("a"), row, 9.5}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", 9.4); + inputs.add(new Object[]{"roundDecimal(a)", Lists.newArrayList("a"), row, 9.0}); + inputs.add(new Object[]{"roundDecimal(a, 0)", Lists.newArrayList("a"), row, 9.0}); + inputs.add(new Object[]{"roundDecimal(a, 1)", Lists.newArrayList("a"), row, 9.4}); + inputs.add(new Object[]{"roundDecimal(a, 2)", Lists.newArrayList("a"), row, 9.4}); + inputs.add(new Object[]{"roundDecimal(a, 3)", Lists.newArrayList("a"), row, 9.4}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", 9.6); + inputs.add(new Object[]{"roundDecimal(a)", Lists.newArrayList("a"), row, 10.0}); + inputs.add(new Object[]{"roundDecimal(a, 0)", Lists.newArrayList("a"), row, 10.0}); + inputs.add(new Object[]{"roundDecimal(a, 1)", Lists.newArrayList("a"), row, 9.6}); + inputs.add(new Object[]{"roundDecimal(a, 2)", Lists.newArrayList("a"), row, 9.6}); + inputs.add(new Object[]{"roundDecimal(a, 3)", Lists.newArrayList("a"), row, 9.6}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", 9.45); + inputs.add(new Object[]{"roundDecimal(a)", Lists.newArrayList("a"), row, 9.0}); + inputs.add(new Object[]{"roundDecimal(a, 1)", Lists.newArrayList("a"), row, 9.5}); + inputs.add(new Object[]{"roundDecimal(a, 2)", Lists.newArrayList("a"), row, 9.45}); + inputs.add(new Object[]{"roundDecimal(a, 3)", Lists.newArrayList("a"), row, 9.45}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", 9.46); + inputs.add(new Object[]{"roundDecimal(a)", Lists.newArrayList("a"), row, 9.0}); + inputs.add(new Object[]{"roundDecimal(a, 1)", Lists.newArrayList("a"), row, 9.5}); + inputs.add(new Object[]{"roundDecimal(a, 2)", Lists.newArrayList("a"), row, 9.46}); + inputs.add(new Object[]{"roundDecimal(a, 3)", Lists.newArrayList("a"), row, 9.46}); + } + // test truncate + { + GenericRow row = new GenericRow(); + row.putValue("a", 9.5); + inputs.add(new Object[]{"truncate(a)", Lists.newArrayList("a"), row, 9.0}); + inputs.add(new Object[]{"truncate(a, 0)", Lists.newArrayList("a"), row, 9.0}); + inputs.add(new Object[]{"truncate(a, 1)", Lists.newArrayList("a"), row, 9.5}); + inputs.add(new Object[]{"truncate(a, 2)", Lists.newArrayList("a"), row, 9.5}); + inputs.add(new Object[]{"truncate(a, 3)", Lists.newArrayList("a"), row, 9.5}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", 9.4); + inputs.add(new Object[]{"truncate(a)", Lists.newArrayList("a"), row, 9.0}); + inputs.add(new Object[]{"truncate(a, 0)", Lists.newArrayList("a"), row, 9.0}); + inputs.add(new Object[]{"truncate(a, 1)", Lists.newArrayList("a"), row, 9.4}); + inputs.add(new Object[]{"truncate(a, 2)", Lists.newArrayList("a"), row, 9.4}); + inputs.add(new Object[]{"truncate(a, 3)", Lists.newArrayList("a"), row, 9.4}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", 9.6); + inputs.add(new Object[]{"truncate(a)", Lists.newArrayList("a"), row, 9.0}); + inputs.add(new Object[]{"truncate(a, 0)", Lists.newArrayList("a"), row, 9.0}); + inputs.add(new Object[]{"truncate(a, 1)", Lists.newArrayList("a"), row, 9.6}); + inputs.add(new Object[]{"truncate(a, 2)", Lists.newArrayList("a"), row, 9.6}); + inputs.add(new Object[]{"truncate(a, 3)", Lists.newArrayList("a"), row, 9.6}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", 9.45); + inputs.add(new Object[]{"truncate(a)", Lists.newArrayList("a"), row, 9.0}); + inputs.add(new Object[]{"truncate(a, 1)", Lists.newArrayList("a"), row, 9.4}); + inputs.add(new Object[]{"truncate(a, 2)", Lists.newArrayList("a"), row, 9.45}); + inputs.add(new Object[]{"truncate(a, 3)", Lists.newArrayList("a"), row, 9.45}); + } + // test gcd, lcm + { + GenericRow row = new GenericRow(); + row.putValue("a", 9L); + row.putValue("b", 6L); + inputs.add(new Object[]{"gcd(a, b)", Lists.newArrayList("a", "b"), row, 3L}); + inputs.add(new Object[]{"lcm(a, b)", Lists.newArrayList("a", "b"), row, 18L}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", 9L); + row.putValue("b", 0L); + inputs.add(new Object[]{"gcd(a, b)", Lists.newArrayList("a", "b"), row, 9L}); + inputs.add(new Object[]{"lcm(a, b)", Lists.newArrayList("a", "b"), row, 0L}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", 0L); + row.putValue("b", 9L); + inputs.add(new Object[]{"gcd(a, b)", Lists.newArrayList("a", "b"), row, 9L}); + inputs.add(new Object[]{"lcm(a, b)", Lists.newArrayList("a", "b"), row, 0L}); + } + { + GenericRow row = new GenericRow(); + row.putValue("a", 0L); + row.putValue("b", 0L); + inputs.add(new Object[]{"gcd(a, b)", Lists.newArrayList("a", "b"), row, 0L}); + inputs.add(new Object[]{"lcm(a, b)", Lists.newArrayList("a", "b"), row, 0L}); + } + // test hypot + { + GenericRow row = new GenericRow(); + row.putValue("a", 3.0); + row.putValue("b", 4.0); + inputs.add(new Object[]{"hypot(a, b)", Lists.newArrayList("a", "b"), row, 5.0}); + } + // test byteswapInt + { + GenericRow row = new GenericRow(); + row.putValue("a", 0x12345678); + inputs.add(new Object[]{"byteswapInt(a)", Lists.newArrayList("a"), row, 0x78563412}); + } + // test byteswapLong + { + GenericRow row = new GenericRow(); + row.putValue("a", 0x1234567890abcdefL); + inputs.add(new Object[]{"byteswapLong(a)", Lists.newArrayList("a"), row, 0xefcdab9078563412L}); + } return inputs.toArray(new Object[0][]); } }