Operators.java
/*
* Copyright 2014 Frank Asseg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.objecthunter.exp4j.operator;
import java.io.Serial;
import static net.objecthunter.exp4j.operator.Operator.*;
import static net.objecthunter.exp4j.utils.Text.l10n;
/**
* This class implements all the built-in operators both arithmetic and
* boolean.
* <p>Boolean values will be treated as follows:</p><ol>
* <li>if the absolute value is less than 1e-12 it will be considered
* {@code false}. About this... I know is not the ideal approach, but we have a
* LOT of arithmetic "hacks" all around some projects and this behavior is
* actually expected.</li>
* <li>if the absolute value is bigger or equal than 1e-12 it will be considered
* {@code true}</li>
* <li>the boolean results will <b>always</b> be {@code 1.0} for {@code true}
* and {@code 0.0} for {@code false}</li>
* <li>boolean operations will always have lower precedence than arithmetic
* operations (i.e. they will be evaluated last)</li>
* <li>The precedence (order) of the boolean operators will always be ¬ &
* | (in accordance to the analogy with arithmetic operators), so that:<pre>
* a & b| b & ¬c -> ((a & b) | (b & (¬c)))</pre>
* </li>
* </ol>
*/
public final class Operators {
private static final Operator ADDITION = new OpAdd();
private static final Operator ADDITION_UN = new OpAddUnary();
private static final Operator SUBTRACTION = new OpMinus();
private static final Operator SUBTRACTION_UN = new OpMinusUnary();
private static final Operator MUTLIPLICATION = new OpMultiply();
private static final Operator DIVISION = new OpDivide();
private static final Operator MODULO = new OpModulo();
private static final Operator POWER = new OpPower();
private static final Operator AND = new OpAnd();
private static final Operator OR = new OpOr();
private static final Operator NOT = new OpNot();
private static final Operator FACTORIAL = new OpFactorial();
/**
* Retrieves a Built-in operator.
*
* @param symbol Symbol representing the operator
* @param numArguments Number of arguments of the operator
* @return An operator matching the criteria if one exists or {@code null}
* otherwise
* @see Operator
*/
public static Operator getBuiltinOperator(final char symbol, final int numArguments) {
switch(symbol) {
case '+':
if (numArguments != 1) {
return ADDITION;
} else{
return ADDITION_UN;
}
case '-':
if (numArguments != 1) {
return SUBTRACTION;
} else{
return SUBTRACTION_UN;
}
case '*': return MUTLIPLICATION;
case '/': return DIVISION;
case '^': return POWER;
case '%': return MODULO;
case '&': return AND;
case '|': return OR;
case '¬': return NOT;
case '!':
if (numArguments != 1) {
return FACTORIAL;
}
default:
return null;
}
}
private Operators() {
// Don't let anyone initialize this class
}
/**
* Array with all the available operators
*
* @return {@link Operator} array
* @see Operators#getBuiltinOperator(char, int)
*/
public static Operator[] getOperators() {
return new Operator[]{
ADDITION, ADDITION_UN, SUBTRACTION, SUBTRACTION_UN,
MUTLIPLICATION, DIVISION, MODULO, POWER,
AND, OR, NOT,
FACTORIAL
};
}
private static final class OpAdd extends Operator {
@Serial
private static final long serialVersionUID = -6902781239333016448L;
OpAdd() { super("+", 2, true, PRECEDENCE_ADDITION); }
@Override
public double apply(double... args) {
return args[0] + args[1];
}
}
private static final class OpAddUnary extends Operator {
@Serial
private static final long serialVersionUID = 793924203719717929L;
OpAddUnary() { super("+", 1, false, PRECEDENCE_UNARY_PLUS); }
@Override
public double apply(double... args) {
return args[0];
}
}
private static final class OpMinus extends Operator {
@Serial
private static final long serialVersionUID = -3511523899514942407L;
OpMinus() { super("-", 2, true, PRECEDENCE_ADDITION); }
@Override
public double apply(double... args) {
return args[0] - args[1];
}
}
private static final class OpMinusUnary extends Operator {
@Serial
private static final long serialVersionUID = -887228242398619895L;
OpMinusUnary() { super("-", 1, false, PRECEDENCE_UNARY_MINUS); }
@Override
public double apply(double... args) {
return -args[0];
}
}
private static final class OpMultiply extends Operator {
@Serial
private static final long serialVersionUID = 604402774847173166L;
OpMultiply() { super("*", 2, true, PRECEDENCE_MULTIPLICATION); }
@Override
public double apply(double... args) {
return args[0] * args[1];
}
}
private static final class OpDivide extends Operator {
@Serial
private static final long serialVersionUID = -1687653461890168296L;
OpDivide() { super("/", 2, true, PRECEDENCE_DIVISION); }
@Override
public double apply(double... args) {
if (args[1] == 0d) {
throw new ArithmeticException(l10n("Division by zero!"));
}
return args[0] / args[1];
}
}
private static final class OpPower extends Operator {
@Serial
private static final long serialVersionUID = 8176172587258190827L;
OpPower() { super("^", 2, false, PRECEDENCE_POWER); }
@Override
public double apply(double... args) {
return Math.pow(args[0], args[1]);
}
}
private static final class OpModulo extends Operator {
@Serial
private static final long serialVersionUID = -8657864901943257599L;
OpModulo() { super("%", 2, true, PRECEDENCE_MODULO); }
@Override
public double apply(double... args) {
if (args[1] == 0d) {
throw new ArithmeticException(l10n("Division by zero!"));
}
return args[0] % args[1];
}
}
private static final class OpAnd extends Operator {
@Serial
private static final long serialVersionUID = 7730531744867276402L;
OpAnd() { super("&", 2, true, PRECEDENCE_AND); }
@Override
public double apply(double... args) {
final boolean a = Math.abs(args[0]) >= BOOLEAN_THRESHOLD;
final boolean b = Math.abs(args[1]) >= BOOLEAN_THRESHOLD;
return (a & b) ? 1 : 0;
}
}
private static final class OpOr extends Operator {
@Serial
private static final long serialVersionUID = 4652717701575702240L;
OpOr() { super("|", 2, true, PRECEDENCE_OR); }
@Override
public double apply(double... args) {
final boolean a = Math.abs(args[0]) >= BOOLEAN_THRESHOLD;
final boolean b = Math.abs(args[1]) >= BOOLEAN_THRESHOLD;
return (a | b) ? 1 : 0;
}
}
private static final class OpNot extends Operator {
@Serial
private static final long serialVersionUID = -8848717292894659390L;
OpNot() { super("¬", 1, false, PRECEDENCE_NOT); }
@Override
public double apply(double... args) {
return (Math.abs(args[0]) < BOOLEAN_THRESHOLD) ? 1 : 0;
}
}
private static final class OpFactorial extends Operator {
@Serial
private static final long serialVersionUID = 9103176758714614115L;
OpFactorial() { super("!", 1, true, Operator.PRECEDENCE_POWER + 1); }
@Override
public double apply(double... args) {
final int arg = (int) args[0];
if ((double) arg != args[0]) {
String msg = "Operand for factorial has to be an integer";
throw new IllegalArgumentException(l10n(msg));
}
if (arg < 0) {
String msg = "The operand of the factorial can not be less than zero";
throw new IllegalArgumentException(l10n(msg));
}
if (arg > 170) {
String msg = "The operand of the factorial can not be more than 170";
throw new IllegalArgumentException(l10n(msg));
}
double result = 1;
for (int i = 1; i <= arg; i++) {
result *= i;
}
return result;
}
}
}