17. Quantities

A quantity is a number together with a unit. Clients should be able to treat quantities the same way they treat numbers: copy semantics, arithmetic, I/O, etc.

We can think of units as number types. Therefore, we can interpret units as classes:

Or as objects:

The last class diagram shows a qualified association between the Unit type and the Conversion type. This indicates that a Unit object has a table containing associations between Unit pointers and Conversion objects.

A C++ Implementation

class Quantity
{
public:
   Quantity(double amt = 0.0);
   double getAmount() const { return amount; }
   Unit* getUnit() const { return unit; }
   friend class Unit;
   friend istream& operator>>(istream& is, Quantity& q);
private:
   Quantity(double amt, Unit* u)
   {
      amount = amt;
      unit = u;
   }
   double amount;
   Unit* unit;
};

class Unit
{
public:
   Unit(string nm = "???");
   void addConversion(Unit* u, double factor);
   static Unit* findUnit(string nm);
   double findConversion(Unit* u) const;
   Quantity makeQuantity(double amt);
   Quantity makeQuantity(Quantity q);
   string getName() const { return name; }
   static Unit* scalar; // null unit
private:
   string name;
   map<string, double> conversions;
   static map<string, Unit*> nameToUnits;
};

Conversion

Quantity Unit::makeQuantity(Quantity q)
{
   Unit* u = q.getUnit();
   if (u == this) return q;
   double amt = q.getAmount();
   double factor = findConversion(u);
   return makeQuantity(amt * factor);
}

double Unit::findConversion(Unit* u) const
{
   double factor;
   if (!find(u->getName(), factor, conversions))
      throw runtime_error("conversion not found");
   return factor;
}

void Unit::addConversion(Unit* u, double factor)
{
   conversions[u->getName()] = factor;
   u->conversions[name] = 1/factor;
}

Quantity Arithmetic

Quantity operator+(const Quantity& q1, const Quantity& q2)
{
   Unit* u = q1.getUnit();
   Quantity q3 = u->makeQuantity(q2);
   return u->makeQuantity(q1.getAmount() + q3.getAmount());
}

Quantity I/O

ostream& operator<<(ostream& os, const Quantity& q)
{
   os << q.getAmount() << ' ' << *(q.getUnit());
   return os;
}

istream& operator>>(istream& is, Quantity& q)
{
   is >> q.amount;
   string name;
   is >> name;
   q.unit = Unit::findUnit(name);
   return is;
}

Unit* Unit::findUnit(string nm)
{
   Unit *u = 0;
   if (!find(nm, u, nameToUnits))
      throw runtime_error("unit not found");
   return u;
}

Unit::Unit(string nm)
{
   name = nm;
   nameToUnits[name] = this;
}

Example

#include "Quantity.h"

int main()
{
   try
   {
      Unit dm("DM"), usd("USD"), mp("MP");

      usd.addConversion(&dm, .5);
      usd.addConversion(&mp, .1);
      dm.addConversion(&mp, .5);

      Quantity q1 = dm.makeQuantity(34.3);
      Quantity q2 = usd.makeQuantity(51);
      Quantity q3 = mp.makeQuantity(603);
      Quantity q4 = q1 + q2;

      cout << "q1 = " << q1 << endl;
      cout << "q2 = " << q2 << endl;
      cout << "q3 = " << q3 << endl;
      cout << "q4 = " << q4 << endl;

      cout << "enter 2 units:";
      cin >> q1 >> q2;
      cout << "q1 = " << q1 << endl;
      cout << "q2 = " << q2 << endl;
      cout << "q1 + q2 = " << q1 + q2 << endl;
   }
   catch(runtime_error e)
   {
      cerr << e.what() << endl;
   }

   return 0;
}

Program Output

q1 = 34.3 DM
q2 = 51 USD
q3 = 603 MP
q4 = 136.3 DM
enter 2 units:3 USD 6DM
q1 = 3 USD
q2 = 6 DM
q1 + q2 = 6 USD

A Java Implementation

public class Quantity {

   private double amount;
   private Unit unit;
   
   Quantity(double amt, Unit u) {
      unit = u;
      amount = amt;
   }
   
   Quantity(Quantity q, Unit u) throws NoConversion {
      unit = u;
      amount = (u.makeQuantity(q)).getAmount();
   }
   
   public Unit getUnit() { return unit; }
   public double getAmount() { return amount; } // shouldn't need this
   
   public String toString() {
      return "" + amount + " " + unit + "(s)";
   }
   
   public boolean equals(Quantity q) {
      return amount == q.amount && unit.equals(q.unit);
   }
   
   public Quantity add(Quantity q) throws UnitMismatch {
      if (!unit.equals(q.getUnit()))
      throw new UnitMismatch();
      return unit.makeQuantity(amount + q.amount);
   }
   
   // etc.
   
   }

   class UnitMismatch extends Exception {

   }

   class NoConversion extends Exception {

   }

class Unit {
   private String name;
   private Map conversions = new Hashtable(); // or HashMap()
   
   public void put(Unit unit, double factor) {
      conversions.put(unit, new Double(factor));
      unit.conversions.put(this, new Double(1/factor));
   }
   
   public double get(Unit unit) throws NoConversion {
      Object obj = conversions.get(unit);
      if (obj == null) throw new NoConversion();
      return ((Double) obj).doubleValue();
   }   
   
   // factory methods (not necessary):
   
   public Quantity makeQuantity(double amt) {
      return new Quantity(amt, this);
   }
   
   public Quantity makeQuantity(Quantity q) throws NoConversion {
      Unit u = q.getUnit();
      if (u.equals(this)) return q;
      double factor = get(u);
      double amt = q.getAmount();
      return makeQuantity(factor * amt);
   }
      
   
   public Quantity makeQuantity(Quantity q, double factor) {
      Unit u = q.getUnit();
      if (u.equals(this)) return q;
      double amt = q.getAmount();
      return makeQuantity(factor * amt);
   }
      
   public String toString() { return name; }
   public boolean equals(Unit u) {
      return name.equals(u.name);
   }
   public Unit(String nm) { name = nm; }
}

Problem

Implement the Quantity class discussed in lecture. For example, users must be able to add, multiply, subtract, divide, print, read, and compare quantities using the standard C++ operators. Conversion between quantities measured in different units must be automatic.

Prove your implementation works by creating several unit objects representing different types of currencies:

USD = US Dollars
DM = Deutsche Marks
JY = Japanese Yen
MP = Mexican Pesos
BP = British Ponds

Consult the web to find out the exchange rates between these currencies.

Next, write a simple test harness that reads two currencies, then displays their sum:

cout << "enter two currencies: ";
cin >> q1 >> q2;
cout << "q1 + q2 = " << q1 + q2 << endl;

Here is what a sample output should look like:

enter two currencies: 3 USD 5 DM
q1 + q2 = 4.25 USD