/******************************************************************************* * LastCalc - The last calculator you'll ever need * Copyright (C) 2011, 2012 Uprizer Labs LLC * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Affero General Public License for more * details. ******************************************************************************/ package com.lastcalc.parsers.math; import java.util.*; import javax.measure.unit.Unit; import com.google.common.collect.*; import org.jscience.mathematics.number.*; import org.jscience.physics.amount.Amount; import com.lastcalc.TokenList; import com.lastcalc.parsers.*; public class MathBiOp extends Parser { private static final long serialVersionUID = 4507416694168613210L; private static final ArrayList<String> opsRequiringSameUnit = Lists.newArrayList("+", "-", "<", ">", "<=", ">=", "==", "!="); private static Map<String, Integer> precidenceMap = Maps.newHashMap(); static { precidenceMap.put("^", 2); precidenceMap.put("xor", 2); precidenceMap.put("*", 3); precidenceMap.put("/", 3); precidenceMap.put("mod", 3); precidenceMap.put("+", 4); precidenceMap.put("-", 4); precidenceMap.put("<", 6); precidenceMap.put("<=", 6); precidenceMap.put(">", 6); precidenceMap.put(">=", 6); precidenceMap.put("==", 7); precidenceMap.put("!=", 7); } private static TokenList template = TokenList.createD( Object.class, Lists.<Object> newArrayList(precidenceMap.keySet()), Object.class); @Override public TokenList getTemplate() { return template; } @Override public String toString() { return ""; } @SuppressWarnings("rawtypes") @Override public ParseResult parse(final TokenList tokens, final int templatePos) { Object a = tokens.get(templatePos); Object b = tokens.get(templatePos + 2); int outputRadix = 10; if (a instanceof Radix && b instanceof Radix) { final Radix ra = (Radix) a; final Radix rb = (Radix) b; if (ra.radix == rb.radix) { outputRadix = ra.radix; } } if (a instanceof Radix) { a = LargeInteger.valueOf(((Radix) a).integer); } if (b instanceof Radix) { b = LargeInteger.valueOf(((Radix) b).integer); } if ((!(a instanceof org.jscience.mathematics.number.Number) && !(a instanceof Amount)) || (!(b instanceof org.jscience.mathematics.number.Number) && !(b instanceof Amount))) return ParseResult.fail(); final String op = (String) tokens.get(templatePos + 1); final Integer opPrecidence = precidenceMap.get(op); boolean samePrecidenceOk = false; for (final Object token : PreParser.enclosedByStructure(tokens, templatePos)) { if (token == op) { // Operators of the same precedence are permitted *after* this // operator samePrecidenceOk = true; continue; } final Integer tPrecidence = precidenceMap.get(token); if (samePrecidenceOk) { if (tPrecidence != null && tPrecidence < opPrecidence) return Parser.ParseResult.fail(); } else if (tPrecidence != null && tPrecidence <= opPrecidence) return Parser.ParseResult.fail(); } Object result; if (a instanceof Amount || b instanceof Amount) { final boolean opSameUnit = opsRequiringSameUnit.contains(op); if (a instanceof org.jscience.mathematics.number.Number) { final Unit<?> unit = opSameUnit ? ((Amount) b).getUnit() : Unit.ONE; a = numToAmount((org.jscience.mathematics.number.Number) a, unit); } else if (b instanceof org.jscience.mathematics.number.Number) { final Unit<?> unit = opSameUnit ? ((Amount) a).getUnit() : Unit.ONE; b = numToAmount((org.jscience.mathematics.number.Number) b, unit); } result = calcAmount((Amount) a, op, (Amount) b); } else { // If either a or b is FloatingPoint, ensure both are if (a instanceof FloatingPoint || b instanceof FloatingPoint) { if (!(a instanceof FloatingPoint)) { a = FloatingPoint.valueOf(((org.jscience.mathematics.number.Number) a).doubleValue()); } if (!(b instanceof FloatingPoint)) { b = FloatingPoint.valueOf(((org.jscience.mathematics.number.Number) b).doubleValue()); } } // If either a or b is Rational, ensure both are if (a instanceof Rational || b instanceof Rational) { if (!(a instanceof Rational)) { a = Rational.valueOf(((org.jscience.mathematics.number.Number) a).longValue(), 1); } if (!(b instanceof Rational)) { b = Rational.valueOf(((org.jscience.mathematics.number.Number) b).longValue(), 1); } } result = calcNumber((org.jscience.mathematics.number.Number) a, op, (org.jscience.mathematics.number.Number) b); if (outputRadix != 10 && result instanceof LargeInteger) { result = new Radix(((LargeInteger) result).longValue(), outputRadix); } } if (result == null) return ParseResult.fail(); else return ParseResult.success(tokens.replaceWithTokens(templatePos, templatePos + template.size(), result)); } @SuppressWarnings("rawtypes") private Amount numToAmount(final org.jscience.mathematics.number.Number num, final Unit<?> unit) { if (num.doubleValue() == num.longValue()) return Amount.valueOf(num.longValue(), unit); else return Amount.valueOf(num.doubleValue(), unit); } @SuppressWarnings({ "rawtypes", "unchecked" }) private Object calcAmount(final Amount a, final String op, final Amount b) { if (op.equals("+")) return a.plus(b); else if (op.equals("-")) return a.minus(b); else if (op.equals("*")) return a.times(b); else if (op.equals("/") && b.getEstimatedValue() != 0){ return a.divide(b); } else if (op.equals("^")) { if (b.getExactValue() != b.getEstimatedValue() || b.getExactValue() > Integer.MAX_VALUE) return null; else return a.pow((int) b.getExactValue()); } else if (op.equals(">")) return a.isGreaterThan(b); else if (op.equals("<")) return a.isLessThan(b); else if (op.equals(">=")) return a.approximates(b) || a.isGreaterThan(b); else if (op.equals("<=")) return a.approximates(b) || a.isLessThan(b); else if (op.equals("=")) return a.approximates(b); else if (op.equals("!=")) return !a.approximates(b); else return null; } @SuppressWarnings("unchecked") private Object calcNumber(final org.jscience.mathematics.number.Number a, final String op, final org.jscience.mathematics.number.Number b) { if (op.equals("+")) return a.plus(b); else if (op.equals("-")) return a.minus(b); else if (op.equals("*")) { if (a instanceof LargeInteger && b instanceof LargeInteger) return a.times(b); else return FloatingPoint.valueOf(a.doubleValue()).times(FloatingPoint.valueOf(b.doubleValue())); } else if (op.equals("/")) { if (b.doubleValue() == 0.0) return null; if (a instanceof LargeInteger && b instanceof LargeInteger) return org.jscience.mathematics.number.Rational.valueOf((LargeInteger) a, (LargeInteger) b); else return FloatingPoint.valueOf(a.doubleValue() / b.doubleValue()); } else if (op.equals("^")) { if (!(b instanceof LargeInteger) || ((LargeInteger) b).isNegative()) return FloatingPoint.valueOf(Math.pow(a.doubleValue(), b.doubleValue())); else return a.pow(b.intValue()); } else if (op.equals(">")) return a.isGreaterThan(b); else if (op.equals(">=")) return a.equals(b) || a.isGreaterThan(b); else if (op.equals("<")) return a.isLessThan(b); else if (op.equals("<=")) return a.equals(b) || a.isLessThan(b); else if (op.equals("mod")) { if (a instanceof LargeInteger && b instanceof LargeInteger) { final LargeInteger ali = (LargeInteger) a; final LargeInteger bli = (LargeInteger) b; return ali.mod(bli); } else return null; } else return null; } @Override public int hashCode() { return "MathBiOp".hashCode(); } @Override public boolean equals(final Object obj) { return obj instanceof MathBiOp; } }