by Jean-Marie Dautelle

Introduction to JSR-275: Measures and Units

how-to
Oct 2, 200715 mins

New API specification removes uncertainty from programming with physical measurements

Because Java is a strongly typed language, the compiler can usually detect when you make a mistake regarding the kind of input or output a method expects. But in developing programs that involve physical measurements, programmers working together still often face uncertainty. Is the temperature stated in Fahrenheit or Celsius? Is the speed in miles per hour or meters per second? Coding standards can somewhat alleviate this uncertainty, but only within a single organization. In this article, you’ll read how JSR 275: Measures and Units delivers a general solution to this thorny issue.

Recently, working on an important customer project, I assumed that because all latitude and longitude data was entered in degrees, our projection routine expected degrees as well. Not true! The problem didn’t manifest itself until late in the project life cycle, when we discovered that screen positions were way off base. A thorough review revealed that half our code dealt with radians and the other half with degrees. As co-spec lead of JSR 275: Measures and Units, I couldn’t help but smile. I knew that this kind of problem would soon be a thing of the past.

A brief history of JSR 275
The story of JSR 275 began many years ago with JSR 108: Units API. JSR 108 dealt exclusively with units and was withdrawn by the spec lead for personal reasons. Unhappy about this abrupt termination, the expert group decided to start a new JSR with even wider goals in mind. Units were almost always associated with quantities, and providing only units and unit conversions was too limited. Also, they felt that new features in Java SE 5, such as annotations or parameterized types, could be taken advantage of without compromising performance. (The annotations road later proved to be impractical.) JSR 275 was approved in 2005, the first early draft review took place in July 2007, and the final draft is scheduled for early 2008.

Developers who work with physical quantities (in scientific, engineering, medical and manufacturing domains, for example) need to be able to handle measurements of those quantities in their programs. Inadequate models of physical measurements can lead to significant programmatic errors. In particular, the practice of modeling a measure as a simple number with no regard to the units it represents creates fragile code. Another developer or another part of the code might misinterpret the number as representing a different unit of measurement. For example, it might be unclear whether a person’s weight is expressed in pounds, kilograms, or stone. The issue is difficult to solve through testing and has already cost millions of dollars to society worldwide. (NASA lost a $125 million Mars orbiter because a Lockheed Martin engineering team used English units of measurement while the agency’s team used the more conventional metric system for a key spacecraft operation.)

Developers must either use inadequate models of measurements or create their own solutions. A standard solution can be safer, save development time for domain-specific work, and — most important — make code more readable and less error-prone. JSR 275 establishes such a framework for Java developers.

A simple framework

The Java namespace for JSR 275 is javax.measure, with one abstract class, Measure, and one interface, Measurable. A Measure is basically the product of a numerical value and a unit, such as “7.2 liters” or “24 hours,” whereas Measurable identifies quantities capable of being measured, such as “altitude” or “delay.”

Listing 1 shows how this is represented in Java terms (assuming a static import of the SI.* and NonSI.* units members).

Listing 1. Measure and Measurable

 Measure<Double, Volume> v = Measure.valueOf(7.2, LITER);
Measure<Integer, Duration> d = Measure.valueOf(24, HOUR);
class Altitude implements Measurable<Length> { ... }
class Delay implements Measurable<Duration> { ... }

I’ll go into detail about units and class parameterization later in this article. Suffice it to say now that primitive types such as int and double representing quantities arguments can be replaced by Measure or Measurable. Consider the legacy code in Listing 2.

Listing 2. Legacy code

 class Person {
    void setWeight(double weight);
}

Should the weight be in pounds or kilograms? Measurable leaves no room for error, as shown in Listing 3.

Listing 3. No room for error

 class Person {
    void setWeight(Measurable<Mass> weight);
}

Not only is the interface cleaner (weight must be of type Mass), but there’s also no confusion about the unit.

In Listing 4, the last statement results in a compile-time error, because weight is of type Mass and requires a unit of mass.

Listing 4. Unit mismatch causing compile-time error

double weightInKg = weight.doubleValue(KILOGRAM);
double weightInLb = weight.doubleValue(POUND);
double wrong = weight.doubleValue(LITER); // Compile Error!

Class parameterization

Class parameterization is arguably the single most important enhancement to the Java platform since its infancy. The parameterized collection framework (java.util.*) demonstrates only a small fragment of what can be done through parameterization. The JScience project has pushed the envelope even further by taking advantage of parameterization in new areas such as mathematical structures (Ring, Field, Group), vectors/matrices and functions.

Parameterization has many advantages, not least being a “zero-cost at run time” because all the checks are done at compile time. JSR 275 takes advantage of class parameterization to ensure dimension consistency. As Listing 5 shows, classes such as Measurable, Measure and Unit are parameterized by their quantity type (such as Mass or Length). The dimensions of a unit (or measure) can be retrieved from the unit itself (at run time) of inferred from its quantity type (at compile time).

 

Listing 5. Parameterization for dimension consistency

 Unit<length> inch = CENTI(METER).times(2.54); // OK.
Unit<length> inch = CENTI(LITER).times(2.54); // Compile error.

Measurable<Duration> d = Measure.valueOf(2, MINUTE); // OK.
Measurable<Duration> d = Measure.valueOf(2, WATT); // Compile Error.

long milliseconds = d.longValue(MILLI(SECOND)); // OK.
long milliseconds = d.longValue(MILLI(HERTZ)); // Compile error.

When the compiler can’t ensure commensurability, it issues a warning that can easily be removed through an explicit run-time check of the dimensions or by using the ? wildcard parameterized type (see Listing 6).

Listing 6. Compiler warnings and how to remove them

 // Compiler Warnings
Unit<Acceleration> mps2 = Unit.valueOf("m/s²"); // Compiler Warning.
Unit<Power> WATT = JOULE.divide(SECOND); // Compiler Warning.

// Adding run-time check of dimension consistency removes the warning.
Unit<Acceleration> mps2 = Unit.valueOf("m/s²").asType(Acceleration.class); 
Unit<Power> WATT = JOULE.divide(SECOND).asType(Power.class);

// Use wildcard parameterized type (no warning and no check).
Unit<?> kelvinPerSec = KELVIN.divide(SECOND);

Units and units conversions

Units can be retrieved from constants held by various SystemOfUnits subclasses, such as SI (holding the “Système International d’Unités” standard units) or NonSI (holding nonstandard but common units). Custom systems of units can be created, and units can belong to several system of units. For example, the imperial system would contain many of the NonSI units. (You can use the SystemOfUnits.getUnits() member method to list all the units held by a system.)

Measurable or Measure?
Measurable is an interface allowing all kinds of implementations. It is basically equivalent to java.lang.Number and provides similarly named methods for conversion to primitive types (intValue(Unit), doubleValue(Unit)). Measure is the simple combination of a numerical value and a unit. Measurable is more flexible, but if you need to retrieve the original numeric value stated in its original unit (no conversion), then Measure is your only choice.

By using predefined units, users avoid many conversion pitfalls. For example, the dry gallon is different from the liquid gallon, which is different in the United States and in the United Kingdom. Differentiation can be done either through SystemOfUnits (an imperial system would contain the U.K. units exclusively) or through distinct names such as DAY_SIDEREAL and DAY_CALENDAR, which differentiate between the day corresponding to the rotation of the Earth (23 hours, 56 minutes, 4.09 seconds) and the 24-hour calendar day. All conversion factors have been established from the latest Bureau International des Poids et Mesures (BIPM) standard.

If a unit does not exist in the SI or NonSI system of units, you can create new units by applying algebraic operations on existing units. Table 1 shows the operations supported on unit instances.

 

Table 1. Units operations

Result with same dimensionResult with different dimension
Binary operations:Binary operations:
plus (double) or (long)root (int)
times (double) or (long)power (int)
divide (double) or (long)times (Unit)
compound (Unit)divide (Unit)
 Unary operations:
 inverse()

For convenience, the SI class provides static multiplier methods, such as KILO(Unit) or CENTI(Unit), equivalent to the multiplication or division of the unit by a power of 10. For example, KILO(METER) is equivalent to METER.times(1000), whereas CENTI(METER) is equivalent to METER.divide(100).

As Listing 7 shows, you can also create new units with a new symbol to distinguish between quantities of a different nature but of the same dimension (AlternateUnit) or by combining several units together (CompoundUnit). CompoundUnit is used exclusively for formatting purposes (see Parsing and formatting).

Listing 7. Creating new units and symbols

 Unit<Angle> RADIAN = ONE.alternate("rad");
Unit<Pressure> PASCAL = NEWTON.divide(METER.pow(2)).alternate("Pa");
Unit<Duration> HOUR_MINUTE_SECOND = HOUR.compound(MINUTE).compound(SECOND);
Unit<Angle> DEGREE_MINUTE_ANGLE = DEGREE_ANGLE.compound(MINUTE_ANGLE);

Figure 1 shows a UML diagram of the unit package.

A UML diagram of the unit package
Figure 1. Unit package (click for a larger image)

Given two units of same dimensions, you can retrieve the UnitConverter between the two units. Then you can use the converter to perform conversions of double values, as shown in Listing 8.

Listing 8. Converting double values with UnitConverter

 double distanceInMiles = 23.0;
UnitConverter mileToKilometer = MILE.getConverterTo(KILO(METER));
double distanceInKilometers = mileToKilometer.convert(distanceInMiles);

For more complex numeric types such as BigDecimal, conversions are more elaborate and are performed by the Measure implementation directly. For example, the Measure implementation for BigDecimal takes into account the MathContext holding the precision and the rounding mode. It should be noted that most UnitConverters between predefined units are instances of RationalConverter (the ratio of two integer numbers) and as such do not introduce conversion errors of their own. When you create new derived units, it’s recommended that you use integer multipliers or divisors whenever possible instead of double-value multipliers. Often decimal numbers don’t have an exact binary representation (IEE754 format for double), and units derived from such inexact numbers could introduce numeric error artifacts during conversions. Listing 9 shows a 128-bit conversion from meters to feet using BigDecimal.

Listing 9. BigDecimal conversion

 Unit<Length> FOOT = METER.times(3048).divide(10000); // Exact.
BigDecimal meters = new BigDecimal("9192631770.03345677");
MathContext ctx = MathContext.DECIMAL128;
Measure<BigDecimal, Length> m = Measure.valueOf(meters, METER, ctx);
BigDecimal feet = m.to(FOOT).getValue();

The numeric value for Measure does not need to be a scalar. It can be an array, for example, in which case the conversion of the whole array can be performed with a single operation (which is also efficient, because the converter is calculated only once). Listing 10 shows the conversion of a velocity vector.

Listing 10. Vector conversion

 Measure<double[], Velocity> velocity3D 
     = Measure.valueOf(new double[] { 0.2, -0.3, 0.5 }, METER_PER_SECOND);
double[] milesPerHour = velocity3D.to(MILE.divide(HOUR)).getValue();

Unit converters

Unit converters have two purposes: converting between units and creating new derived units (TransformedUnit).

Most users don’t need to instantiate UnitConverter, because convenience methods such as Unit.times(double) or Unit.divide(long) do this for you. The predefined converters in the converter package (shown in Figure 2) should be sufficient for most applications. However, you can create custom converters. For example, embedded systems might use a device-specific converter whose value changes with temperature. It is safe for an application to work with the device units (not corrected) and/or the standard units (corrected). Measures stated in both units cannot be confused or mixed without explicit conversions.

Figure 2. Converter package (click for a larger image)

Parsing and formatting

Unit and Measure parsing and formatting follow the standard Java paradigm for formatting locale-sensitive information such as dates, messages and numbers. (A Locale object represents a specific geographical, political or cultural region. An operation that requires a Locale to perform its task is called locale-sensitive and uses the Locale to tailor information for the user.) Two abstract classes derived from java.util.FormatUnitFormat and MeasureFormat — provide the locale-sensitive instances as well as locale-insensitive instances to facilitate unambiguous electronic communication of quantities together with their units (UCUM format). The default format used by the Unit.toString() and Unit.valueOf(String) methods is locale-sensitive and should not be used to send information over the Atlantic; otherwise a measure such as 1.0 Gal (about 3.78 liters in the United States) would be interpreted as about 4.54 liters in the United Kingdom (1 UK gallon)!

As a minimum, the unit format recognizes the 20 SI prefixes used to form decimal multiples and submultiples of SI units, as shown in Listing 11.

Listing 11. Using SI prefixes

 Unit.valueOf("m°C").equals(SI.MILLI(SI.CELSIUS)) // True
Unit.valueOf("kW").equals(SI.KILO(SI.WATT)) // True
Unit.valueOf("ft").equals(SI.METER.multiply(3048).divide(10000)) // True

Unit.valueOf("ft").equals(SI.METER.multiply(0.3048)) // True!

In Listing 11, the last assertion is true, which might be a little puzzling, given that the double 0.3048 is about 0.3048000000000000154 (when represented using 19 digits), and the foot international unit (ft) has been standardized to be exactly 3048/10000 meters. The assertion is true because two converters are considered equals if their quotient is the identity converter. The concatenation of a rational converter and a multiply converter (or its inverse) is a multiply converter (inexact) for which equality comparison is not strict to account for numeric errors.

The unit format recognizes product units. The following strings, for example, all represent the same meter per square second units: "m*s-2", "m/s²", "m·s-²", and "m*s**-2" .

As I mentioned earlier, compound units can be used to format Measure in a canonical (unique) form using integer-value coefficients, as shown in Listing 12.

Listing 12. Compound units

 Unit<Duration> HOUR_MINUTE_SECOND = HOUR.compound(MINUTE).compound(SECOND);

Measure<Integer, Duration> d = Measure.valueOf(12345, SECOND);
System.out.println(d.to(HOUR_MINUTE_SECOND));

>> 3h25min45s

Dimensional analysis

A question often asked is why Unit, Measure and Measurable are parameterized by the quantity type and not some dimension type.

The first easy answer is that there is no such thing as a “dimension type.” You’ll find dimension instances that can be operated upon (product/quotient of dimensions), but class parameterization works only with classes, not instances. Also, using a dimension class for parameterization would lead to the following problems:

  • Dimensions change with the model. For example, the dimension of the watt unit is [L]²·[M]/[T]³ in the standard model, but it becomes [M]/[T] in the relativistic model. JSR 275 supports custom models for which the number of independent dimensions can be decreased (from seven in the standard model) or increased. (You could add an angular dimension or a monetary dimension, for example.) Modern physics often works with natural units, for which the number of independent dimensions is reduced to zero (all quantities are dimensionless)!
  • Units can have the same dimension and still apply to different quantities. For example both torque and energy have a dimension of [L]²·[M]/[T]² in the standard model. Nevertheless it is convenient and safer to consider them as two different quantities with their own units. Other examples are sea water salinity (PSS-78), which is some kind of concentration, and angles, which are all dimensionless but still convenient to treat as different kinds of quantities. A unit’s dimension can be easily retrieved using the Unit.getDimension() method. For example: ELECTRON_VOLT.getDimension() returns [L]²·[M]/[T]². But the physical “type” of a unit can be identified only by its standard units. Consider the REVOLUTION.divide(MINUTE) unit. Its dimension is 1/[T] — basically the same dimension as frequency. But REVOLUTION.divide(MINUTE).getStandardUnit() returns rad/s, which unequivocally identifies the unit as an angular-velocity unit.

See the Resources section to learn more about dimensional analysis.

A case study: The monetary system

The monetary system is out of JSR 275’s scope, but it illustrates how easily the framework can be extended to nonphysical quantities. Such extension can be valuable in the sense that it not only leverages the specification’s capabilities (formatting, conversions, and so on) but it compounds its usefulness as well. For example, you can now work with hybrid quantities such as dollar per gallon (gas price) or Euro per square meter (material price) or Yen per kilogram (food price). Figure 3 shows (in green) the new classes and how they attach to the framework.

Figure 3. Monetary-system extension (click for a larger image)

There’s no need to subclass Measure or Measurable. The statements in Listing 13 are supported without further customizations.

Listing 13. Monetary-system integration

 Measure<Double, ?> gazPrice  
    = Measure.valueOf(1.2, EUR.divide(LITRE)); // 1.2 €/L
System.out.println(gazPrice.getDimension());
>> [$]/[L]³

Measure<Double, Money> wallet = Measure.valueOf(13.2, EUR);
Unit<Money> DOLLAR_CENT = USD.compound(CENTI(USD));
System.out.println(wallet.to(DOLLAR_CENT));
>> 18$10¢

Conclusion

Just as parameterization enhanced the Java Collections Framework, the Measures and Units API adds a new dimension to Java primitive types. Whether or not JSR 275 will be included in the Java 7 platform has not yet been decided. We can certainly hope so, because it provides a common framework allowing unit-safe integration of higher-level libraries (such as JSR 310: Date and Time API). In the meantime, you can download the reference implementation from the JScience project.

Acknowledgment

Thanks to Martin Desruisseaux, who wrote the first draft of the specification, and to the expert group for its valuable input and feedback.

Jean-Marie Dautelle holds a master’s degree in electrical engineering (Orsay University, France) and a postgraduate degree in information processing and simulation. He is spec lead for JSR 275 and has been a Java Executive Committee member since November 2006. Jean-Marie works for Raytheon in Marlborough, MA on Java-based technology for safety-critical systems. Jean-Marie is the main author of the Javolution and JScience Project libraries.