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