A colleague, Denis Garneau of IBM, and I have been trying to come up with an implementation of physical units in Java for some time now, and we have come up with a partial list of requirements, based partly on our experience with business data types. One requirement that Denis is very keen on is that we must take as much advantage as possible of Java's compile-time checking. Suppose we build an elaborate type-checking mechanism that is only available at run-time - your system might run beautifully for years because it seldom executes a particular path in the code, and then crash unexpectedly in the middle of the night! Although this provides a lot of flexibility, from a practical point of view it does not seem to us to be acceptable.
On the other hand, there is no doubt that the run-time approach avoids a number of problems, some of which will be described in what follows. There is a very complete run-time package called "Java Addition to Default Environment" (described in JScience - Java Tools and Libraries for the Advancement of Sciences), developed by J-M. Dautelle, in collaboration with the JSR-108 experts. This provides a large number of units and sophisticated dimensional analysis - all at run-time. He is obviously an accomplished mathematician, unlike myself! The units supported by this package are grouped into two classes: SI and NonSI. SI also contains symbols for the 20 SI prefixes (positive and negative powers of 10). It is certainly convenient to be able to ask for the dimensions of, say, a coulomb, at run-time. However, as stated above, a strong argument can be made for a compile-time facility, even if not as sophisticated a one from a mathematical point of view. I am not comfortable with the idea that an error in the use of units may not be detected until the spacecraft is actually in the air, or beyond it!
One obvious problem with either type of facility is that one cannot support all the units in use worldwide, so we need a way for an application to add its own unit definitions. It seems to make sense to partition symbols by dimension, rather than lumping all non-SI symbols into a single class. When one collects all the colloquial measures in use in just a single language, it becomes rather an unwieldy collection. As J-M. Dautelle points out, the American and British measures are often different, e.g. ton, gallon, cup, and that is supposed to be the same language!
In what follows, I will be using very simple Java - hopefully someone who is more versed in the language will run with this and improve it!
Dimension is an abstract class with the following instance
variables:
double scalar; // value in reference units double units; // value in units (Unit unit) Unit unit; // reference to an object of class Unit |
It has two methods: toString(), which has the obvious function, and showInUnits().
We will hold a physical dimension in two forms (for performance reasons): a "reference" unit, which all values of that dimension will be converted into by their constructors, and an amount in the unit in which the data was originally created (on the assumption that the user will probably want to see it in that form at some point). E.g. if the user entered a distance as "6 inches", s/he may want to see it in that form again, not 15.24 cm.
Unit is an abstract class with three instance variables:
String name; // e.g. "Angstrom" double multFactor; // e.g. 1E-10 double addFactor = 0.0; // only used for temperatures |
and no methods.
Distance will extend Dimension, providing a number
of methods, and in addition adding definitions of a number of useful distance
units, defined as public static, using the DistanceUnit
class. Each such definition gives a public name for the unit, and
specifies how to convert it to the reference unit. Here is a possible
set of distance units:
public static final DistanceUnit m = new DistanceUnit("m",1.0);// this is the reference Unit
public static final DistanceUnit in = new DistanceUnit("in",0.0254);
public static final DistanceUnit km = new DistanceUnit("km",1.0e+3);
public static final DistanceUnit mile = new DistanceUnit("mile",1609.0);
public static final DistanceUnit angstrom = new DistanceUnit("angstrom",1.0e-10);
public static final DistanceUnit AU = new DistanceUnit("AU",1.5e+11);
public static final DistanceUnit parsec = new DistanceUnit("parsec",3.08e+16);
|
The DistanceUnit class has (at least) two constructors:
| Constructors |
|---|
| public DistanceUnit (String, double)
public DistanceUnit constructor specifying display String and multiple of reference unit |
| public DistanceUnit (String, DistanceUnit, double)
public DistanceUnit constructor specifying display String, DistanceUnit and multiple of that unit |
Here you will see the standardization problem. My colleague points out that the SI units have standard abbreviations, and these could certainly be used as variable names, except for the cases where the units are derived. However, many measures in common use are not only not standard, but also language-dependent. For instance, we cannot make "foot" a world-wide standard as the corresponding French term is "pied". Some symbols could be written using Unicode characters, as these are acceptable within Java identifiers, but this is somewhat unwieldy if these terms are going to be in common use. For instance, angstrom, shown in the example, is more correctly \u00C5ngstr\u00F6m, which represents "Ångström". I suggest that a practical compromise is to use the DistanceUnit class, to define "locally defined" units for an application - some of these might eventually become de facto standards, assuming that the concept of using units instead of dimensionless values catches on!
Note that the above definitions provide both a Java variable name, and a String for input/output, which would presumably support a wider choice of characters.
The DistanceUnit class only provides the constructor that allows objects like the ones shown above to be built - it has no other methods.
Here is a preliminary list of constructors and methods for the Distance class - as in all OO applications, I'm sure real-life applications will see the need for additional ones. Among the constructors you will notice one that has no unit defined - this is convenient when doing arithmetic with units, but it is defined as non-public, so it cannot be used outside the class.
Constructors for Distance:
| Constructors |
|---|
| public Distance (double,DistanceUnit)
public Distance constructor with value initialized from double and DistanceUnit |
| Distance (double)
non-public Distance constructor with value initialized from double using reference unit |
Initial set of methods (all public):
| returns | Methods |
|---|---|
| Distance | add (Distance)
add one distance to another, giving result in reference units |
| Distance | subtract (Distance)
subtract one distance from another, giving result in reference units |
| boolean | eq (Distance)
return true if one distance is equal to another |
| boolean | ne (Distance
return true if one distance is not equal to another |
| boolean | gt (Distance)
return true if one distance is greater than another |
| boolean | ge (Distance)
return true if one distance is greater than or equal to another |
| boolean | lt (Distance)
return true if one distance is less than another |
| boolean | le (Distance)
return true if one distance is less than or equal to another |
| Distance | multiply (double)
multiply a distance by a scalar, giving a distance |
| Distance | divide (double)
divide a distance by a scalar, giving a distance |
| Area | multiply (Distance)
multiply one distance to another, giving an area |
| Volume | multiply (Area)
multiply a distance by an area, giving a volume |
| Speed | divide (TimeInterval)
divide a distance by a time interval, giving a speed |
| TimeInterval | divide (Speed)
divide a distance by a speed, giving a time interval |
This may not be a complete set of methods, but it should be enough to give you a flavour of what I'm driving at. Notice that these methods seem to fall into groups:
Here is an example of how such units might be referenced. It seems a bit wordy to me - maybe there are Java facilities that I am not familiar with that would simplify this...? And my distances are totally arbitrary! Of course, in real life, the "trip leg" information would be read from a database.
final DistanceUnit klik = new DistanceUnit("klik", Distance.km, 1.0);
Trip trip = new Trip(2); // specify number of legs
trip.addLeg(0, new TripLeg("YKK", "NYC", new Distance(1200.0, klik)));
trip.addLeg(1, new TripLeg("NYC", "LAX", new Distance(5000.0, Distance.km)));
Distance totDist = new Distance(0.0, Distance.mile);
for (int i = 0; i < trip.tripleg.length; i++) {
totDist = totDist.add(trip.tripleg[i].getDist());
}
System.out.println(totDist.showInUnits(Distance.km,2)); // 2 is no. of decimal places
|
Notice that we have used the DistanceUnit class to define a "local usage" unit called a "klik", defined as 1.0 kilometres.
| returns | Methods |
|---|---|
| Weight | multiply (Acceleration)
multiply a mass by an acceleration, giving a weight |
However, in this case we would want 1 kilo mass to convert to 1 kilo
weight, so we must force the acceleration to be 1 - i.e. 1 gravity. In
fact, we all know that the relationship depends on what planet we are on,
or, more generally, what gravitational field we are in. So let us also
define a class called GFactor, with values such as "nullG" (0.0), "earthG"
(1.0), "moonG" (0.17), etc. Now we can define a more pragmatic relationship
between mass and weight, defining a method in the Mass class, as follows:
| returns | Methods |
|---|---|
| Weight | calcWeight (GFactor)
determine the weight for this mass, given a G factor |
and its converse (in the Weight class):
| returns | Methods |
|---|---|
| Mass | calcMass (GFactor)
determine the mass for this weight given a G factor |
In fact, we can keep Acceleration, with the same dimensions as GFactor, but with a different reference unit - Acceleration would use 1 metre/sec2, while GFactor would use 1 "G", which is defined to be 9.80665 metres/sec2.
Let us consider Speed, whose dimension is d/t (distance over time). In English, "miles per hour" is usually abbreviated "mph", but "m" is the standard abbreviation for "metres". Let us accept "mph" as an "ad hoc" abbreviation (and possibly also "kps"), but we still need a convention for arbitrary combinations of units. In the case of divisions, we could allow ...Per..., but this really violates the requirement for language-independence (although Latin is pretty close to a "lingua franca", especially in the scientific community). So we need a notation that is as language-independent as possible, but conforms to Java naming conventions for variables.
Now the Java convention for variable names allows alpha characters, including Unicode alpha characters from other (non-Roman) character sets, numbers in the non-initial position, underscore and dollar-sign. So I would like to put forward this humble suggestion (please feel free to throw brickbats):
public static final SpeedUnit m_sec$n1 = new SpeedUnit("m/sec",1.0);
// reference Unit
public static final SpeedUnit mph = new SpeedUnit("mph",1609.0 / 3600.0);
public static final SpeedUnit kps = new SpeedUnit("kps",10e-3);
public static final SpeedUnit km_sec$n1 = new SpeedUnit("km/sec",10e-3);
|
Some corresponding units for Acceleration:
public static final AccelerationUnit m_sec$n2 =
new AccelerationUnit("m/sec^2",1.0);
public static final AccelerationUnit km_sec$n2 =
new AccelerationUnit("km/sec^2",10e-3);
|
| 3,889 visits |