// file: Quantity.java // author: Robert Keller // purpose: Provide Unicalc Quantity class import OpenList; /** * Quantity is a class of objects representing Unicalc quantities. * Each quality is a numeric factor times a symbolic numerator divided * by a symbolic denominator. * * The equivalent rex definitions of various functions have been embedded * as comments. */ public class Quantity { /** * the multiply operator in syntax trees returned by the parser */ static final String multiplyOp = "*"; /** * the divide operator in syntax trees returned by the parser */ static final String divideOp = "/"; /** * the exponentiation operator in syntax trees returned by the parser */ static final String powerOp = "^"; /** * the empty list (for local convenience) */ static OpenList nil = OpenList.nil; /** * the Quantity representing dimensionless 1, for convenience */ final static Quantity one = new Quantity(1.0f, nil, nil); /** Factor is the numeric portion of the quantity. */ private float Factor; /** Num is a list of units representing the numerator. */ private OpenList Num; /** Denom is a list of units representing the denominator. */ private OpenList Denom; /** * Construct a Quantity from a factor, numerator list, and denominator list. */ Quantity(float Factor, OpenList Num, OpenList Denom) { this.Factor = Factor; this.Num = Num; this.Denom = Denom; } /** * Return the Factor of this Quantity. * @return the Factor of this Quantity */ float getFactor() { return Factor; } /** * Return the numerator of this Quantity. * @return the numerator of this Quantity */ OpenList getNum() { return Num; } /** * Return the denominator of this Quantity. * @return the denominator of this Quantity */ OpenList getDenom() { return Denom; } /** * Construct a Quantity from a syntax tree represented using OpenList. * @param tree An Object representing a syntax tree, as returned by * the Unicalc parser * @return a Quantity representing the value of the syntax tree */ public static Quantity makeQuantity(Object tree) { // A tree consisting of a single numeric node becomes a quantity // with the numeric value as multiplier. if( tree instanceof Number ) { return new Quantity(((Number)tree).floatValue(), nil, nil); } // A tree consisting of a single string becomes a quantity with // a that string in the numerator, an empty denominator, and 1 as // the multiplier. if( tree instanceof String ) { return new Quantity(1.0f, list(tree), nil); } // A tree consisting of an OpenList represents a binary operator // joining two sub-trees. Quantities are made from the sub-trees // and the Quantities are combined using the appropriate operators. if( tree instanceof OpenList ) { OpenList treeAsList = (OpenList)tree; String operator = (String)treeAsList.first(); Object leftSubtree = treeAsList.second(); Object rightSubtree = treeAsList.third(); if( operator.equals(multiplyOp) ) // multiply quantities { return multiply(makeQuantity(leftSubtree), makeQuantity(rightSubtree)); } if( operator.equals(divideOp) ) // divide quantities { return divide(makeQuantity(leftSubtree), makeQuantity(rightSubtree)); } if( operator.equals(powerOp) ) // raise to integer power { return raise(makeQuantity(leftSubtree), ((Integer)rightSubtree).intValue()); } } return null; // should not occur, but keeps compiler happy } /** * Raise a Quantity to an integer power. * The power can be negative, positive, or 0. * @param quantity the Quantity to be raised to a power * @param power the power to which the Quantity is to be raised * @return the Quantity raised to the indicated power */ static Quantity raise(Quantity quantity, int power) { if( power == 0 ) return one; if( power > 0 ) return multiply(quantity, raise(quantity, power-1)); else return divide(quantity, raise(quantity, power+1)); } /** * local cons for convenience */ static OpenList cons(Object F, OpenList R) { return OpenList.cons(F, R); } /** * local list of 1 argument, for convenience */ static OpenList list(Object X1) { return OpenList.list(X1); } /** * local list of 2 arguments, for convenience */ static OpenList list(Object X1, Object X2) { return OpenList.list(X1, X2); } /** * local list of 3 arguments, for convenience */ static OpenList list(Object X1, Object X2, Object X3) { return OpenList.list(X1, X2, X3); } /** * simplify simplifies a quantity by cancelling any terms common to * numerator and denominotor. *
 * simplify([F, N, D]) => [F, cancelFrom(N, D), cancelFrom(D, N)];
 *
*/ static Quantity simplify(Quantity Q) { return new Quantity(Q.getFactor(), cancelFrom(Q.getNum(), Q.getDenom()), cancelFrom(Q.getDenom(), Q.getNum())); } /** * cancelFrom cancels from the first argument any units that are present * in the second argument. It assumes that both lists are sorted and * guarantees that the result is similarly sorted. * *
 * cancelFrom([], M) => [];
 *
 *  cancelFrom(L, []) => L;
 *
 *  cancelFrom([A | L], [A | M]) => cancelFrom(L, M);
 *
 *  cancelFrom([A | L], [B | M]) => A < B ? [A | cancelFrom(L, [B | M])] 
 *                                        : cancelFrom([A | L], M);
 *
* * @param L the sorted list from which units are cancelled. * @param M the sorted list of units to be cancelled. * @return the sorted list containing the units in L with the units of M * cancelled out */ static OpenList cancelFrom(OpenList L, OpenList M) { if( L.isEmpty() ) return nil; if( M.isEmpty() ) return L; String A = (String)L.first(); String B = (String)M.first(); if( A.equals(B) ) return cancelFrom(L.rest(), M.rest()); if( A.compareTo(B) < 0 ) return cons(A, cancelFrom(L.rest(), M)); else return cancelFrom(L, M.rest()); } /** * Multiply two Quantities, returning a simplified result. * *
 * multiply([M1, N1, D1], [M2, N2, D2]) => 
 *   simplify([M1*M2, merge(N1, N2), merge(D1, D2)]);
 *
* * @param Q1 one of the Quantities to be multiplied * @param Q2 the other of the Quantities to be multiplied * @return the product of the two Quantities */ static Quantity multiply(Quantity Q1, Quantity Q2) { return simplify(new Quantity(Q1.getFactor() * Q2.getFactor(), OpenList.merge(Q1.getNum(), Q2.getNum()), OpenList.merge(Q1.getDenom(), Q2.getDenom()))); } /** * Divide first Quantity by Second Quantity, returning a simplified result. * *
 * divide([M1, N1, D1], [M2, N2, D2]) => 
 *   simplify([M1/M2, merge(N1, D2), merge(D1, N2)]);
 *
* * @param Q1 the dividend Quantity * @param Q2 the divisor Quantity * @return the quotient of Q1 divided by Q2 */ static Quantity divide(Quantity Q1, Quantity Q2) { return simplify(new Quantity(Q1.getFactor() / Q2.getFactor(), OpenList.merge(Q1.getNum(), Q2.getDenom()), OpenList.merge(Q1.getDenom(), Q2.getNum()))); } /** * Invert the Quantity, that is, divide one by it. * * @param Q the Quantity to be inverted * @return the quotient of 1 divided by Q */ static Quantity invert(Quantity Q) { return divide(one, Q); } /** * Normalize a Quantity, with respect to a Database, * returning a simplified Quantity. * *
 * norm([M, N, D], DB) => 
 *   multiply([M, [], []], divide(normalizeAll(N, DB), normalizeAll(D, DB)));
 *
* * @param Q the Quantity to be normalized * @param DB the conversion database * @return a normalized Quantity equivalent to Q */ static Quantity norm(Quantity Q, OpenList DB) { return multiply(new Quantity(Q.getFactor(), nil, nil), divide(normalizeAll(Q.getNum(), DB), normalizeAll(Q.getDenom(), DB))); } /** * Return the normalized producet of a list of Quantities. * *
 * normalizeAll(L, DB) = multiplyAll(map((X)=>normalizeUnit(X, DB), L));
 *
* * @param L a list of units (strings) * @param DB the conversion database * @return a normalized Quantity representing the product of the units */ static Quantity normalizeAll(OpenList L, OpenList DB) { return multiplyAll(L.map(new NormalizeUnit(DB))); } /** * Normalize a single unit with respect to a database. * *
 * normalizeUnit(Unit, DB) =
 *   Found = assoc(Unit, DB),
 *   Found == [] ? 
 *     [1., [Unit], []]             // If not found, create unit quantity.
 *   : norm(second(Found), DB);     // Otherwise normalize the right-hand side.
 *
* * @param Unit the unit to be normalized * @param DB the conversion database * @return a normalized Quantity equivalent to Unit */ static Quantity normalizeUnit(String Unit, OpenList DB) { OpenList Found = OpenList.assoc(Unit, DB); if( Found.isEmpty() ) { return new Quantity(1.0f, OpenList.list(Unit), nil); } else { return norm((Quantity)(Found.second()), DB); } } /** * Multiply a list of Quantities together to get a single Quantity. * *
 * multiplyAll(L) = reduce(multiply, [1., [], []], L);
 *
* * @param L a list of Quantities * @return the product of the Quantities in the argument list */ static Quantity multiplyAll(OpenList L) { return (Quantity)L.reduce(new Multiply(), one); } /** * Convert the first Quantity to the Second, returning a normalized result * with respect to the database in the third argument. * *
 * convert(A, B, DB) = norm(divide(A, B), db);
 *
* * @param A the Quantity from which conversion is to be done * @param B the Quantity to which conversion is to be done * @param DB the database used for normalization * @return the Quantity representing the conversion from A to B * If the Quantities are inter-convertible, this will have * empty numerator and denominator and the factor represents * the conversion factor */ static Quantity convert(Quantity A, Quantity B, OpenList DB) { return norm(divide(A, B), DB); } /** * Convert this Quantity to String (printable form). * @return String to which this Quantity is converted */ public String toString() { if( Denom.isEmpty() ) { if( Num.isEmpty() ) { return "" + Factor; } else { return Factor + " " + Num.toString("", " ", ""); } } else { if( Num.isEmpty() ) { return Factor + "/" + Denom.toString("", " ", "");; } else { return Factor + " " + Num.toString("", " ", "") + "/" + Denom.toString("", " ", ""); } } } }