/* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. */ /* * MyDouble.java * * Created on 07. Oktober 2001, 12:23 */ package org.geogebra.common.kernel.arithmetic; import java.math.BigDecimal; import java.util.HashSet; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.commands.EvalInfo; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.geos.GeoVec2D; import org.geogebra.common.main.Localization; import org.geogebra.common.main.MyError; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.MyMath; import org.geogebra.common.util.MyMath2; import org.geogebra.common.util.StringUtil; import org.geogebra.common.util.lang.Unicode; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * * @author Markus Hohenwarter */ public class MyDouble extends ValidExpression implements NumberValue, Comparable<Object> { private double val; private int angleDim = 0; /** * kernel */ protected Kernel kernel; /** * Do not use integer operations beyond this bound */ public static final double LARGEST_INTEGER = 9007199254740992.0; // 0x020000000000000 /** * @param kernel * kernel */ public MyDouble(Kernel kernel) { this(kernel, 0.0); } /** * Creates new MyDouble * * @param kernel * kernel * @param x * value */ public MyDouble(Kernel kernel, double x) { this.kernel = kernel; val = x; } /** * @param d * MyDouble to copy */ public MyDouble(MyDouble d) { kernel = d.kernel; val = d.val; angleDim = d.angleDim; } /** * called from the parser power must be a string of unicode superscript * digits * * @param kernel * kernel * @param power * superscript power */ public MyDouble(Kernel kernel, String power) { this.kernel = kernel; int sign = 1; int start = 0; if (power.charAt(0) == Unicode.Superscript_Minus) { start = 1; sign = -1; } val = 0; for (int i = 0; i < power.length() - start; i++) { switch (power.charAt(power.length() - 1 - i)) { case Unicode.Superscript_0: // val+= 0; break; case Unicode.Superscript_1: val += Math.pow(10, i); break; case Unicode.Superscript_2: val += Math.pow(10, i) * 2; break; case Unicode.Superscript_3: val += Math.pow(10, i) * 3; break; case Unicode.Superscript_4: val += Math.pow(10, i) * 4; break; case Unicode.Superscript_5: val += Math.pow(10, i) * 5; break; case Unicode.Superscript_6: val += Math.pow(10, i) * 6; break; case Unicode.Superscript_7: val += Math.pow(10, i) * 7; break; case Unicode.Superscript_8: val += Math.pow(10, i) * 8; break; case Unicode.Superscript_9: val += Math.pow(10, i) * 9; break; default: // unexpected character val = Double.NaN; return; } } val = val * sign; } @Override public MyDouble deepCopy(Kernel kernel1) { MyDouble ret = new MyDouble(this); ret.kernel = kernel1; return ret; } /** * @param x * new value */ public void set(double x) { val = x; } @Override public void resolveVariables(EvalInfo info) { // do nothing } @Override public String toString(StringTemplate tpl) { if (angleDim == 1) { // // convert to angle value first, see issue 87 // // http://code.google.com/p/geogebra/issues/detail?id=87 // double angleVal = Kernel.convertToAngleValue(val); // return kernel.formatAngle(angleVal, tpl, false).toString(); return kernel.formatAngle(val, tpl, true).toString(); } // String ret = kernel.format(Kernel.checkDecimalFraction(val), tpl); String ret = kernel.format((val), tpl); if (tpl.isNumeric()) { return ret; } // convert eg 0.125 to exact(0.125) so that Giac does an exact // calculation with it // numbers entered in the CAS View are handled by MySpecialDoule // this code is just used when accessing a GeoGebra object // eg Input Bar: f(x)=x^-0.5 // CAS View: Integral[f,1,Infinity] if (exactEqual(val, Math.PI)) { return "pi"; } if (exactEqual(val, Math.E)) { return "e"; } // Note: exact(0.3333333333333) gives 1/3 if (ret.indexOf('.') > -1) { return StringUtil.wrapInExact(ret, tpl); } return ret; } @Override final public String toValueString(StringTemplate tpl) { return toString(tpl); } @Override final public String toLaTeXString(boolean symbolic, StringTemplate tpl) { return toString(tpl); } /** * Switches to angle mode (to use degrees) * * @return reference to self */ public MyDouble setAngle() { angleDim = 1; return this; } /** * @return whether this is angle */ public boolean isAngle() { return angleDim == 1; } @Override public int getAngleDim() { return angleDim; } /** * @return random MyDouble */ final public MyDouble random() { val = kernel.getApplication().getRandomNumber(); angleDim = 0; return this; } /** * c = a + b * * @param a * 1st summand * @param b * 2nd summand * @param c * result */ final public static void add(MyDouble a, NumberValue b, MyDouble c) { c.angleDim = a.angleDim == b.getAngleDim() ? a.angleDim : 0; c.set(a.val + b.getDouble()); } /** * c = a - b * * @param a * subtrahend * @param b * minuend * @param c * result */ final public static void sub(MyDouble a, NumberValue b, MyDouble c) { c.angleDim = a.angleDim == b.getAngleDim() ? a.angleDim : 0; c.set(a.val - b.getDouble()); } /** * c = a * b * http://functions.wolfram.com/Constants/ComplexInfinity/introductions * /Symbols/ShowAll.html * * @param a * 1st factor * @param b * 2nd factor * @param c * result */ final public static void mult(MyDouble a, NumberValue b, MyDouble c) { c.angleDim = a.angleDim + b.getAngleDim(); double bval = b.getDouble(); // ? * anything = ? if (Double.isNaN(a.val) || Double.isNaN(bval)) { c.set(Double.NaN); return; } // (infinity) * (-infinity) = ? if (Double.isInfinite(a.val) && Double.isInfinite(bval) && Math.signum(a.val) != Math.signum(bval)) { c.set(Double.NaN); return; } // gives correct answer for eg -3 * infinity c.set(a.val * bval); } /** * c = a * b * http://functions.wolfram.com/Constants/ComplexInfinity/introductions * /Symbols/ShowAll.html * * @param a * 1st factor * @param b * 2nd factor * @param c * result */ final public static void mult(MyDouble a, double b, MyDouble c) { c.angleDim = a.angleDim; // ? * anything = ? if (Double.isNaN(a.val) || Double.isNaN(b)) { c.set(Double.NaN); return; } // (infinity) * (-infinity) = ? if (Double.isInfinite(a.val) && Double.isInfinite(b) && Math.signum(a.val) != Math.signum(b)) { c.set(Double.NaN); return; } // gives correct answer for eg -3 * infinity c.set(a.val * b); } /** * c = a / b * * @param a * dividend * @param b * divisor * @param c * result */ final public static void div(MyDouble a, MyDouble b, MyDouble c) { c.angleDim = a.angleDim - b.angleDim; c.set(a.val / b.val); } /** * c = pow(a,b) * * @param a * base * @param b * exponent * @param c * result */ final public static void pow(MyDouble a, MyDouble b, MyDouble c) { c.angleDim = b.angleDim > 0 ? 0 : a.angleDim; // Infinity ^ 0 -> NaN // http://functions.wolfram.com/Constants/ComplexInfinity/introductions/Symbols/ShowAll.html if (Kernel.isZero(b.val) && (Double.isInfinite(a.val) || Double.isNaN(a.val))) { c.set(Double.NaN); return; } c.set(Math.pow(a.val, b.val)); } /** * Like Math.pow, but Infinity ^ 0 -> NaN * * @param a * base * @param b * exponent * @return power a^b */ final public static double pow(double a, double b) { // Infinity ^ 0 -> NaN // http://functions.wolfram.com/Constants/ComplexInfinity/introductions/Symbols/ShowAll.html if (Kernel.isZero(b) && (Double.isInfinite(a) || Double.isNaN(a))) { return Double.NaN; } return Math.pow(a, b); } /** * c = -pow(-a,b) * * @param a * base * @param b * exponent * @param c * result */ final public static void powDoubleSgnChange(MyDouble a, MyDouble b, MyDouble c) { c.angleDim = b.angleDim > 0 ? 0 : a.angleDim; // Infinity ^ 0 -> NaN // http://functions.wolfram.com/Constants/ComplexInfinity/introductions/Symbols/ShowAll.html if (Kernel.isZero(b.val) && (Double.isInfinite(a.val) || Double.isNaN(a.val))) { c.set(Double.NaN); return; } c.set(-Math.pow(-a.val, b.val)); } /** * @return cos(this) */ final public MyDouble cos() { val = Math.cos(val); angleDim = 0; checkZero(); return this; } /** * @return sin(this) */ final public MyDouble sin() { val = Math.sin(val); angleDim = 0; checkZero(); return this; } /* * make sure cos(2790 degrees) gives zero */ private void checkZero() { if (Kernel.isZero(val)) { val = 0; } } /** * Tangens function * * @return tangens of value */ final public MyDouble tan() { // Math.tan() gives a very large number for tan(pi/2) // but should be undefined for pi/2, 3pi/2, 5pi/2, etc. if (Kernel.isEqual(Math.abs(val) % Math.PI, Kernel.PI_HALF)) { val = Double.NaN; } else { val = Math.tan(val); checkZero(); } angleDim = 0; return this; } /** * @return acos(this) * @param deg * whether result should be degrees */ final public MyDouble acos(boolean deg) { angleDim = deg ? 1 : 0; set(Math.acos(val)); return this; } /** * @return asin(this) * @param deg * whether result should be degrees */ final public MyDouble asin(boolean deg) { angleDim = deg ? 1 : 0; set(Math.asin(val)); return this; } /** * @return atan(this) * @param deg * whether result should be degrees */ final public MyDouble atan(boolean deg) { angleDim = deg ? 1 : 0; set(Math.atan(val)); return this; } /** * @param y * y * @param deg * whether result should be degrees * @return atan2(this,y) */ final public MyDouble atan2(NumberValue y, boolean deg) { angleDim = deg ? 1 : 0; set(Math.atan2(val, y.getDouble())); return this; } /** * @return log(this) */ final public MyDouble log() { val = Math.log(val); angleDim = 0; return this; } /** * @param base * logarithm base * @return log_base(this) */ final public MyDouble log(NumberValue base) { val = Math.log(val) / Math.log(base.getDouble()); angleDim = 0; return this; } /** * @return erf(this) */ final public MyDouble erf() { val = MyMath2.erf(0.0, 1.0, val); angleDim = 0; return this; } /** * @return inverf(this) */ final public MyDouble inverf() { val = MyMath2.inverf(val); angleDim = 0; return this; } /** * @param order * order * @return polygamma(this,order) */ final public MyDouble polygamma(NumberValue order) { val = MyMath2.polyGamma(order, val); angleDim = 0; return this; } /** * @return psi(this) */ final public MyDouble psi() { val = MyMath2.psi(val); angleDim = 0; return this; } /** * @return log_10(this) */ final public MyDouble log10() { val = Math.log(val) / MyMath.LOG10; angleDim = 0; return this; } /** * @return log_2(this) */ final public MyDouble log2() { val = Math.log(val) / MyMath.LOG2; angleDim = 0; return this; } /** * @return exp(this) */ final public MyDouble exp() { val = Math.exp(val); angleDim = 0; return this; } /** * @return sqrt(this) */ final public MyDouble sqrt() { val = Math.sqrt(val); angleDim = 0; return this; } /** * @return cbrt(this) */ final public MyDouble cbrt() { val = MyMath.cbrt(val); angleDim = 0; return this; } /** * @param a * difference to be added to value */ final public void add(double a) { val += a; } /** * @return abs(this) */ final public MyDouble abs() { val = Math.abs(val); return this; } /** * @param angleUnit * angle unit, eg Kernel.ANGLE_DEGREE * @return floor(this) */ final public MyDouble floor(int angleUnit) { // angle in degrees // kernel.checkInteger() needed otherwise floor(60degrees) gives // 59degrees if (angleDim == 1 && angleUnit == Kernel.ANGLE_DEGREE) { set(Kernel.PI_180 * Math .floor(Kernel.checkInteger(val * Kernel.CONST_180_PI))); } else { // number or angle in radians set(Math.floor(Kernel.checkInteger(val))); } return this; } /** * @param angleUnit * angle unit, eg Kernel.ANGLE_DEGREE * @return ceil(this) */ final public MyDouble ceil(int angleUnit) { // angle in degrees // kernel.checkInteger() needed otherwise ceil(241deg) fails if (angleDim == 1 && angleUnit == Kernel.ANGLE_DEGREE) { set(Kernel.PI_180 * Math .ceil(Kernel.checkInteger(val * Kernel.CONST_180_PI))); } else { // number or angle in radians set(Math.ceil(Kernel.checkInteger(val))); } return this; } /** * @param angleUnit * angle unit, eg Kernel.ANGLE_DEGREE * @return round(this) */ final public MyDouble round(int angleUnit) { // angle in degrees if (angleDim == 1 && angleUnit == Kernel.ANGLE_DEGREE) { set(Kernel.PI_180 * MyDouble.doRound(val * Kernel.CONST_180_PI)); } else { // number or angle in radians set(MyDouble.doRound(val)); } return this; } /** * For 12.34 round(1) rounds to 1 DP (yields 12.3), round(-1) yields 10 * * @param digits * number of digits * @param angleUnit * angle unit, eg Kernel.ANGLE_DEGREE * @return rounded value */ final public MyDouble round(double digits, int angleUnit) { if (!Kernel.isInteger(digits)) { set(Double.NaN); } double pow = Math.pow(10, digits); set(val * pow); round(angleUnit); set(val / pow); return this; } /** * Java quirk/bug Round(NaN) = 0 */ final private static double doRound(double x) { // if (!(Double.isInfinite(x) || Double.isNaN(x))) // changed from Math.round(x) as it uses (long) so fails for large // numbers // also means the check for Infinity / NaN not needed return Math.floor(x + 0.5d); // else // return x; } /** * @return sgn(this) */ final public MyDouble sgn() { val = MyMath.sgn(val); angleDim = 0; return this; } /** * @return cosh(this) */ final public MyDouble cosh() { val = MyMath.cosh(val); angleDim = 0; return this; } /** * @return sinh(this) */ final public MyDouble sinh() { val = MyMath.sinh(val); angleDim = 0; return this; } /** * @return tanh(this) */ final public MyDouble tanh() { val = MyMath.tanh(val); angleDim = 0; return this; } /** * @return acosh(this) */ final public MyDouble acosh() { val = MyMath.acosh(val); angleDim = 0; return this; } /** * @return asinh(this) */ final public MyDouble asinh() { val = MyMath.asinh(val); angleDim = 0; return this; } /** * @return csc(this) */ final public MyDouble csc() { val = MyMath.csc(val); angleDim = 0; return this; } /** * @return sec(this) */ final public MyDouble sec() { val = MyMath.sec(val); angleDim = 0; return this; } /** * @return cot(this) */ final public MyDouble cot() { val = MyMath.cot(val); angleDim = 0; return this; } /** * @return csch(this) */ final public MyDouble csch() { val = MyMath.csch(val); angleDim = 0; return this; } /** * @return sech(this) */ final public MyDouble sech() { val = MyMath.sech(val); angleDim = 0; return this; } /** * @return coth(this) */ final public MyDouble coth() { val = MyMath.coth(val); angleDim = 0; return this; } /** * @return atanh(this) */ final public MyDouble atanh() { val = MyMath.atanh(val); angleDim = 0; return this; } /** * @return atanh(this) */ final public MyDouble cosineIntegral() { val = MyMath2.ci(val); angleDim = 0; return this; } /** * @return atanh(this) */ final public MyDouble sineIntegral() { val = MyMath2.si(val); angleDim = 0; return this; } /** * @return atanh(this) */ final public MyDouble expIntegral() { val = MyMath2.ei(val); angleDim = 0; return this; } /** * @return this! */ final public MyDouble factorial() { val = MyMath2.factorial(val); angleDim = 0; return this; } /** * @return gamma(this) */ final public MyDouble gamma() { val = MyMath2.gamma(val); angleDim = 0; return this; } /** * @param lt * function to evaluate * @return value of lt(this) */ final public MyDouble apply(Evaluatable lt) { val = lt.value(val); angleDim = 0; // want function to return numbers eg f(x) = sin(x), // f(45^o) return this; } /* * interface NumberValue */ @Override final public MyDouble getNumber() { return new MyDouble(this); /* * Michael Borcherds 2008-05-20 removed unstable optimisation fails for * eg -2 sin(x) - 5 cos(x) if (isInTree()) { // used in expression node * tree: be careful return new MyDouble(this); } else { // not used * anywhere: reuse this object return this; } */ } @Override public boolean isConstant() { return true; } @Override final public HashSet<GeoElement> getVariables() { return null; } @Override final public boolean isLeaf() { return true; } @Override final public double getDouble() { return val; } @Override final public GeoElement toGeoElement(Construction cons) { GeoNumeric num = new GeoNumeric(cons, val); return num; } @Override public boolean isNumberValue() { return true; } @Override final public boolean contains(ExpressionValue ev) { return ev == this; } /** * parse eg 3.45645% -> 3.45645/100 * * @param app * application for showing errors * @param str * string representation ending with % * @return value as fraction */ public static double parsePercentage(Localization app, String str) { return parseDouble(app, str.substring(0, str.length() - 1)) / 100; } /** * extension of StringUtil.parseDouble() to cope with unicode digits eg * Arabic * * @param str * string to be parsed * @param app * application for showing errors * @return value */ public static double parseDouble(Localization app, String str) { StringBuilder sb = new StringBuilder(); sb.setLength(0); for (int i = 0; i < str.length(); i++) { int ch = str.charAt(i); if (ch <= 0x30) { sb.append(str.charAt(i)); // eg . continue; } // check roman first (most common) else if (ch <= 0x39) { ch -= 0x30; // Roman (normal) } else if (ch <= 0x100) { sb.append(str.charAt(i)); // eg E continue; } else if (ch <= 0x669) { ch -= 0x660; // Arabic-Indic } else if (ch == 0x66b) { // Arabic decimal point sb.append("."); continue; } else if (ch <= 0x6f9) { ch -= 0x6f0; } else if (ch <= 0x96f) { ch -= 0x966; } else if (ch <= 0x9ef) { ch -= 0x9e6; } else if (ch <= 0xa6f) { ch -= 0xa66; } else if (ch <= 0xaef) { ch -= 0xae6; } else if (ch <= 0xb6f) { ch -= 0xb66; } else if (ch <= 0xbef) { ch -= 0xbe6; // Tamil } else if (ch <= 0xc6f) { ch -= 0xc66; } else if (ch <= 0xcef) { ch -= 0xce6; } else if (ch <= 0xd6f) { ch -= 0xd66; } else if (ch <= 0xe59) { ch -= 0xe50; // Thai } else if (ch <= 0xed9) { ch -= 0xed0; } else if (ch <= 0xf29) { ch -= 0xf20; // Tibetan } else if (ch <= 0x1049) { ch -= 0x1040; // Mayanmar (Burmese) } else if (ch <= 0x17e9) { ch -= 0x17e0; // Khmer } else if (ch <= 0x1819) { ch -= 0x1810; // Mongolian } else if (ch <= 0x1b59) { ch -= 0x1b50; // Balinese } else if (ch <= 0x1bb9) { ch -= 0x1bb0; // Sudanese } else if (ch <= 0x1c49) { ch -= 0x1c40; // Lepcha } else if (ch <= 0x1c59) { ch -= 0x1c50; // Ol Chiki } else if (ch <= 0xa8d9) { ch -= 0xa8d0; // Saurashtra } else { sb.append(str.charAt(i)); // eg - continue; } sb.append(ch); } try { return StringUtil.parseDouble(sb.toString()); } catch (Exception e) { // eg try to parse "1.2.3", "1..2" throw new MyError(app, new String[] { "InvalidInput", str }); } /* * "\u0030"-"\u0039", "\u0660"-"\u0669", "\u06f0"-"\u06f9", * "\u0966"-"\u096f", "\u09e6"-"\u09ef", "\u0a66"-"\u0a6f", * "\u0ae6"-"\u0aef", "\u0b66"-"\u0b6f", "\u0be7"-"\u0bef", * "\u0c66"-"\u0c6f", "\u0ce6"-"\u0cef", "\u0d66"-"\u0d6f", * "\u0e50"-"\u0e59", "\u0ed0"-"\u0ed9", "\u1040"-"\u1049" */ } /** * @param lt * lt * @return gammaIncompleteRegularized(lt,this) */ public ExpressionValue gammaIncompleteRegularized(NumberValue lt) { val = MyMath2.gammaIncompleteRegularized(lt.getDouble(), val); angleDim = 0; return this; } /** * @param lt * lt * @return gammaIncomplete(lt,this) */ public ExpressionValue gammaIncomplete(NumberValue lt) { val = MyMath2.gammaIncomplete(lt.getDouble(), val); angleDim = 0; return this; } /** * @param lt * lt * @return beta(lt,this) */ public ExpressionValue beta(NumberValue lt) { val = MyMath2.beta(val, lt.getDouble()); angleDim = 0; return this; } /** * @param lt * lt * @return betaIncomplete(lt,this) */ public ExpressionValue betaIncomplete(VectorValue lt) { GeoVec2D vec = lt.getVector(); val = MyMath2.betaIncomplete(vec.getX(), vec.getY(), val); angleDim = 0; return this; } /** * @param lt * lt * @return betaIncompleteRegularized(lt,this) */ public ExpressionValue betaIncompleteRegularized(VectorValue lt) { GeoVec2D vec = lt.getVector(); val = MyMath2.betaIncompleteRegularized(vec.getX(), vec.getY(), val); angleDim = 0; return this; } @Override public String toOutputValueString(StringTemplate tpl) { return toValueString(tpl); } /* * needed for AlgoUnique (non-Javadoc) so that Kernel.isZero() is used */ @Override public int compareTo(Object arg0) { if (arg0 instanceof MyDouble) { MyDouble d = (MyDouble) arg0; if (Kernel.isEqual(val, d.getDouble())) { return 0; } return val - d.getDouble() < 0 ? -1 : 1; } return 0; } @Override public boolean equals(Object d) { if (d == null) { return false; } if (d instanceof MyDouble) { return Kernel.isEqual(((MyDouble) d).getDouble(), val); } return false; } @Override public int hashCode() { return Double.valueOf(val).hashCode(); } @Override public boolean isDefined() { return !Double.isNaN(val); } /** * @return fractional part using Wolfram's convention * (fractionalPart(-0.6)=-0.6) */ public ExpressionValue fractionalPart() { return new MyDouble(kernel, val > 0 ? val - Math.floor(val) : val - Math.ceil(val)); } /** * @return rieman zeta of this number */ public ExpressionValue zeta() { return new MyDouble(kernel, MyMath2.zeta(val)); } @Override public ExpressionValue derivative(FunctionVariable fv, Kernel kernel0) { return new MyDouble(kernel0, 0); } @Override public ExpressionValue integral(FunctionVariable fv, Kernel kernel0) { return new ExpressionNode(kernel0, this, Operation.MULTIPLY, fv); } /** * @param d * number * @return whether d is valid finite real number */ public static boolean isFinite(double d) { return !Double.isNaN(d) && !Double.isInfinite(d); } @Override public ExpressionNode wrap() { return new ExpressionNode(kernel, this); } /** * #5149 Double.toString(x) gives e not E in GWT (2.7 at least) (important * as GeoGebra parses 1e2 as 1 * e * 2) * * @param x * x * @return x as String */ public static String toString(double x) { String ret = Double.toString(x); if (ret.indexOf('e') > -1) { return ret.replace('e', 'E'); } return ret; } /** * #5149 may not be needed, but best to be safe * * @param bd * number * @return bd as String with e replaced by E */ public static String toString(BigDecimal bd) { String ret = bd.toString(); if (ret.indexOf('e') > -1) { return ret.replace('e', 'E'); } return ret; } @Override public String getLabel(StringTemplate tpl) { return toValueString(tpl); } @Override public ValueType getValueType() { return ValueType.NUMBER; } /** * * @param col * color component * @return col transformed from double[0,1] to int[0,255] but truncated if * outside this range */ public static int normalize0to255(double col) { return truncate0to255((int) (col * 255)); } /** * * @param col * color component * @return col truncated to the range int[0,255] */ public static int truncate0to255(int col) { if (col < 0) { return 0; } else if (col > 255) { return 255; } return col; } /** * Compares two numbers for EXACT equality [without causing a FindBugs * warning]. In most cases you should use Kernel.isEqual() * * @param a * first number * @param b * second number * @return whether those are bitwise equal */ @SuppressFBWarnings({ "FE_FLOATING_POINT_EQUALITY", "OK to compare floats, as even tiny differences should trigger update" }) public static boolean exactEqual(double a, double b) { return a == b; } /** * works for positive and negative numbers see * http://findbugs.sourceforge.net/bugDescriptions.html#IM_BAD_CHECK_FOR_ODD * * @param i * tested number * @return true if i is odd */ public static boolean isOdd(int i) { return (i % 2) != 0; } @Override public double evaluateDouble() { return getDouble(); } }