From 45a3a26ef7b9e1271cdeabd03d94623d8787b6bb Mon Sep 17 00:00:00 2001 From: Navis Ryu Date: Wed, 4 May 2016 02:55:13 +0900 Subject: [PATCH] Add more math functions (#2822) * Add more math functions * added function list --- .../main/java/io/druid/math/expr/Expr.java | 2 +- .../java/io/druid/math/expr/Function.java | 615 +++++++++++++++++- .../main/java/io/druid/math/expr/Parser.java | 21 +- docs/content/misc/math-expr.md | 38 ++ 4 files changed, 648 insertions(+), 28 deletions(-) diff --git a/common/src/main/java/io/druid/math/expr/Expr.java b/common/src/main/java/io/druid/math/expr/Expr.java index 2ee2cdae7c6..ac7aa321137 100644 --- a/common/src/main/java/io/druid/math/expr/Expr.java +++ b/common/src/main/java/io/druid/math/expr/Expr.java @@ -122,7 +122,7 @@ class FunctionExpr implements Expr @Override public Number eval(Map bindings) { - return Parser.func.get(name).apply(args, bindings); + return Parser.func.get(name.toLowerCase()).apply(args, bindings); } } diff --git a/common/src/main/java/io/druid/math/expr/Function.java b/common/src/main/java/io/druid/math/expr/Function.java index 630642ba867..9ccd73fc34a 100644 --- a/common/src/main/java/io/druid/math/expr/Function.java +++ b/common/src/main/java/io/druid/math/expr/Function.java @@ -26,39 +26,608 @@ import java.util.Map; */ interface Function { + String name(); + Number apply(List args, Map bindings); -} -class SqrtFunc implements Function -{ - - @Override - public Number apply(List args, Map bindings) + abstract class SingleParam implements Function { - if (args.size() != 1) { - throw new RuntimeException("function 'sqrt' needs 1 argument"); + @Override + public Number apply(List args, Map bindings) + { + if (args.size() != 1) { + throw new RuntimeException("function '" + name() + "' needs 1 argument"); + } + Expr expr = args.get(0); + return eval(expr.eval(bindings)); } - Number x = args.get(0).eval(bindings); - return Math.sqrt(x.doubleValue()); + protected abstract Number eval(Number x); } -} -class ConditionFunc implements Function -{ - - @Override - public Number apply(List args, Map bindings) + abstract class DoubleParam implements Function { - if (args.size() != 3) { - throw new RuntimeException("function 'if' needs 3 argument"); + @Override + public Number apply(List args, Map bindings) + { + if (args.size() != 2) { + throw new RuntimeException("function '" + name() + "' needs 1 argument"); + } + Expr expr1 = args.get(0); + Expr expr2 = args.get(1); + return eval(expr1.eval(bindings), expr2.eval(bindings)); } - Number x = args.get(0).eval(bindings); - if (x instanceof Long) { - return x.longValue() > 0 ? args.get(1).eval(bindings) : args.get(2).eval(bindings); - } else { - return x.doubleValue() > 0 ? args.get(1).eval(bindings) : args.get(2).eval(bindings); + protected abstract Number eval(Number x, Number y); + } + + class Abs extends SingleParam + { + @Override + public String name() + { + return "abs"; + } + + @Override + protected Number eval(Number x) + { + return x instanceof Long ? Math.abs(x.longValue()) : Math.abs(x.doubleValue()); + } + } + + class Acos extends SingleParam + { + @Override + public String name() + { + return "acos"; + } + + @Override + protected Number eval(Number x) + { + return Math.acos(x.doubleValue()); + } + } + + class Asin extends SingleParam + { + @Override + public String name() + { + return "asin"; + } + + @Override + protected Number eval(Number x) + { + return Math.asin(x.doubleValue()); + } + } + + class Atan extends SingleParam + { + @Override + public String name() + { + return "atan"; + } + + @Override + protected Number eval(Number x) + { + return Math.atan(x.doubleValue()); + } + } + + class Cbrt extends SingleParam + { + @Override + public String name() + { + return "cbrt"; + } + + @Override + protected Number eval(Number x) + { + return Math.cbrt(x.doubleValue()); + } + } + + class Ceil extends SingleParam + { + @Override + public String name() + { + return "ceil"; + } + + @Override + protected Number eval(Number x) + { + return Math.ceil(x.doubleValue()); + } + } + + class Cos extends SingleParam + { + @Override + public String name() + { + return "cos"; + } + + @Override + protected Number eval(Number x) + { + return Math.cos(x.doubleValue()); + } + } + + class Cosh extends SingleParam + { + @Override + public String name() + { + return "cosh"; + } + + @Override + protected Number eval(Number x) + { + return Math.cosh(x.doubleValue()); + } + } + + class Exp extends SingleParam + { + @Override + public String name() + { + return "exp"; + } + + @Override + protected Number eval(Number x) + { + return Math.exp(x.doubleValue()); + } + } + + class Expm1 extends SingleParam + { + @Override + public String name() + { + return "expm1"; + } + + @Override + protected Number eval(Number x) + { + return Math.expm1(x.doubleValue()); + } + } + + class Floor extends SingleParam + { + @Override + public String name() + { + return "floor"; + } + + @Override + protected Number eval(Number x) + { + return Math.floor(x.doubleValue()); + } + } + + class GetExponent extends SingleParam + { + @Override + public String name() + { + return "getExponent"; + } + + @Override + protected Number eval(Number x) + { + return Math.getExponent(x.doubleValue()); + } + } + + class Log extends SingleParam + { + @Override + public String name() + { + return "log"; + } + + @Override + protected Number eval(Number x) + { + return Math.log(x.doubleValue()); + } + } + + class Log10 extends SingleParam + { + @Override + public String name() + { + return "log10"; + } + + @Override + protected Number eval(Number x) + { + return Math.log10(x.doubleValue()); + } + } + + class Log1p extends SingleParam + { + @Override + public String name() + { + return "log1p"; + } + + @Override + protected Number eval(Number x) + { + return Math.log1p(x.doubleValue()); + } + } + + class NextUp extends SingleParam + { + @Override + public String name() + { + return "nextUp"; + } + + @Override + protected Number eval(Number x) + { + return Math.nextUp(x.doubleValue()); + } + } + + class Rint extends SingleParam + { + @Override + public String name() + { + return "rint"; + } + + @Override + protected Number eval(Number x) + { + return Math.rint(x.doubleValue()); + } + } + + class Round extends SingleParam + { + @Override + public String name() + { + return "round"; + } + + @Override + protected Number eval(Number x) + { + return Math.round(x.doubleValue()); + } + } + + class Signum extends SingleParam + { + @Override + public String name() + { + return "signum"; + } + + @Override + protected Number eval(Number x) + { + return Math.signum(x.doubleValue()); + } + } + + class Sin extends SingleParam + { + @Override + public String name() + { + return "sin"; + } + + @Override + protected Number eval(Number x) + { + return Math.sin(x.doubleValue()); + } + } + + class Sinh extends SingleParam + { + @Override + public String name() + { + return "sinh"; + } + + @Override + protected Number eval(Number x) + { + return Math.sinh(x.doubleValue()); + } + } + + class Sqrt extends SingleParam + { + @Override + public String name() + { + return "sqrt"; + } + + @Override + protected Number eval(Number x) + { + return Math.sqrt(x.doubleValue()); + } + } + + class Tan extends SingleParam + { + @Override + public String name() + { + return "tan"; + } + + @Override + protected Number eval(Number x) + { + return Math.tan(x.doubleValue()); + } + } + + class Tanh extends SingleParam + { + @Override + public String name() + { + return "tanh"; + } + + @Override + protected Number eval(Number x) + { + return Math.tanh(x.doubleValue()); + } + } + + class ToDegrees extends SingleParam + { + @Override + public String name() + { + return "toDegrees"; + } + + @Override + protected Number eval(Number x) + { + return Math.toDegrees(x.doubleValue()); + } + } + + class ToRadians extends SingleParam + { + @Override + public String name() + { + return "toRadians"; + } + + @Override + protected Number eval(Number x) + { + return Math.toRadians(x.doubleValue()); + } + } + + class Ulp extends SingleParam + { + @Override + public String name() + { + return "ulp"; + } + + @Override + protected Number eval(Number x) + { + return Math.ulp(x.doubleValue()); + } + } + + class Atan2 extends DoubleParam + { + @Override + public String name() + { + return "atan2"; + } + + @Override + protected Number eval(Number x, Number y) + { + return Math.atan2(x.doubleValue(), y.doubleValue()); + } + } + + class CopySign extends DoubleParam + { + @Override + public String name() + { + return "copySign"; + } + + @Override + protected Number eval(Number x, Number y) + { + return Math.copySign(x.doubleValue(), y.doubleValue()); + } + } + + class Hypot extends DoubleParam + { + @Override + public String name() + { + return "hypot"; + } + + @Override + protected Number eval(Number x, Number y) + { + return Math.hypot(x.doubleValue(), y.doubleValue()); + } + } + + class Remainder extends DoubleParam + { + @Override + public String name() + { + return "remainder"; + } + + @Override + protected Number eval(Number x, Number y) + { + return Math.IEEEremainder(x.doubleValue(), y.doubleValue()); + } + } + + class Max extends DoubleParam + { + @Override + public String name() + { + return "max"; + } + + @Override + protected Number eval(Number x, Number y) + { + if (x instanceof Long && y instanceof Long) { + return Math.max(x.longValue(), y.longValue()); + } + return Double.compare(x.doubleValue(), y.doubleValue()) >= 0 ? x : y; + } + } + + class Min extends DoubleParam + { + @Override + public String name() + { + return "min"; + } + + @Override + protected Number eval(Number x, Number y) + { + if (x instanceof Long && y instanceof Long) { + return Math.min(x.longValue(), y.longValue()); + } + return Double.compare(x.doubleValue(), y.doubleValue()) <= 0 ? x : y; + } + } + + class NextAfter extends DoubleParam + { + @Override + public String name() + { + return "nextAfter"; + } + + @Override + protected Number eval(Number x, Number y) + { + return Math.nextAfter(x.doubleValue(), y.doubleValue()); + } + } + + class Pow extends DoubleParam + { + @Override + public String name() + { + return "pow"; + } + + @Override + protected Number eval(Number x, Number y) + { + return Math.pow(x.doubleValue(), y.doubleValue()); + } + } + + class Scalb extends DoubleParam + { + @Override + public String name() + { + return "scalb"; + } + + @Override + protected Number eval(Number x, Number y) + { + return Math.scalb(x.doubleValue(), y.intValue()); + } + } + + class ConditionFunc implements Function + { + @Override + public String name() + { + return "if"; + } + + @Override + public Number apply(List args, Map bindings) + { + if (args.size() != 3) { + throw new RuntimeException("function 'if' needs 3 argument"); + } + + Number x = args.get(0).eval(bindings); + if (x instanceof Long) { + return x.longValue() > 0 ? args.get(1).eval(bindings) : args.get(2).eval(bindings); + } else { + return x.doubleValue() > 0 ? args.get(1).eval(bindings) : args.get(2).eval(bindings); + } } } } diff --git a/common/src/main/java/io/druid/math/expr/Parser.java b/common/src/main/java/io/druid/math/expr/Parser.java index 9c962e11668..7e72fcc2dce 100644 --- a/common/src/main/java/io/druid/math/expr/Parser.java +++ b/common/src/main/java/io/druid/math/expr/Parser.java @@ -21,6 +21,8 @@ package io.druid.math.expr; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.metamx.common.logger.Logger; import io.druid.math.expr.antlr.ExprLexer; import io.druid.math.expr.antlr.ExprParser; import org.antlr.v4.runtime.ANTLRInputStream; @@ -28,17 +30,28 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeWalker; +import java.lang.reflect.Modifier; import java.util.Map; public class Parser { + static final Logger log = new Logger(Parser.class); static final Map func; static { - func = ImmutableMap.builder() - .put("sqrt", new SqrtFunc()) - .put("if", new ConditionFunc()) - .build(); + Map functionMap = Maps.newHashMap(); + for (Class clazz : Function.class.getClasses()) { + if (!Modifier.isAbstract(clazz.getModifiers()) && Function.class.isAssignableFrom(clazz)) { + try { + Function function = (Function)clazz.newInstance(); + functionMap.put(function.name().toLowerCase(), function); + } + catch (Exception e) { + log.info("failed to instantiate " + clazz.getName() + ".. ignoring", e); + } + } + } + func = ImmutableMap.copyOf(functionMap); } public static Expr parse(String in) diff --git a/docs/content/misc/math-expr.md b/docs/content/misc/math-expr.md index b338bf37c21..ff8565e4e65 100644 --- a/docs/content/misc/math-expr.md +++ b/docs/content/misc/math-expr.md @@ -26,3 +26,41 @@ Also, the following in-built functions are supported. |sqrt|sqrt(x) would return square root of x| |if|if(predicate,then,else) returns 'then' if 'predicate' evaluates to a positive number, otherwise it returns 'else'| +Built-in Math functions. See javadoc of java.lang.Math for detailed explanation for each function. + +|abs|abs(x) would return the absolute value of x| +|acos|acos(x) would return the arc cosine of x| +|asin|asin(x) would return the arc sine of x| +|atan|atan(x) would return the arc tangent of x| +|atan2|atan2(y, x) would return the angle theta from the conversion of rectangular coordinates (x, y) to polar * coordinates (r, theta)| +|cbrt|cbrt(x) would return the cube root of x| +|ceil|ceil(x) would return the smallest (closest to negative infinity) double value that is greater than or equal to x and is equal to a mathematical integer| +|copysign|copysign(x) would return the first floating-point argument with the sign of the second floating-point argument| +|cos|cos(x) would return the trigonometric cosine of x| +|cosh|cosh(x) would return the hyperbolic cosine of x| +|exp|exp(x) would return Euler's number raised to the power of x| +|expm1|expm1(x) would return e^x-1| +|floor|floor(x) would return the largest (closest to positive infinity) double value that is less than or equal to x and is equal to a mathematical integer| +|getExponent|getExponent(x) would return the unbiased exponent used in the representation of x| +|hypot|hypot(x, y) would return sqrt(x^2+y^2) without intermediate overflow or underflow| +|log|log(x) would return the natural logarithm of x| +|log10|log10(x) would return the base 10 logarithm of x| +|log1p|log1p(x) would the natural logarithm of x + 1| +|max|max(x, y) would return the greater of two values| +|min|min(x, y) would return the smaller of two values| +|nextafter|nextafter(x, y) would return the floating-point number adjacent to the x in the direction of the y| +|nextUp|nextUp(x) would return the floating-point value adjacent to x in the direction of positive infinity| +|pow|pow(x, y) would return the value of the x raised to the power of y| +|remainder|remainder(x, y) would return the remainder operation on two arguments as prescribed by the IEEE 754 standard| +|rint|rint(x) would return value that is closest in value to x and is equal to a mathematical integer| +|round|round(x) would return the closest long value to x, with ties rounding up| +|scalb|scalb(d, sf) would return d * 2^sf rounded as if performed by a single correctly rounded floating-point multiply to a member of the double value set| +|signum|signum(x) would return the signum function of the argument x| +|sin|sin(x) would return the trigonometric sine of an angle x| +|sinh|sinh(x) would return the hyperbolic sine of x| +|sqrt|sqrt(x) would return the correctly rounded positive square root of x| +|tan|tan(x) would return the trigonometric tangent of an angle x| +|tanh|tanh(x) would return the hyperbolic tangent of x| +|todegrees|todegrees(x) converts an angle measured in radians to an approximately equivalent angle measured in degrees| +|toradians|toradians(x) converts an angle measured in degrees to an approximately equivalent angle measured in radians| +|ulp|ulp(x) would return the size of an ulp of the argument x|