tc39 / proposal-measure

MIT License
12 stars 2 forks source link

Representing Measures

Stage: 1

Champion: Ben Allen @ben-allen

Author: Ben Allen @ben-allen

Goals and needs

Modeling units of measure is useful for any task that involves measurements from the physical world. It can also be useful for other types of measurement; for example, measurements of currency amounts.

We propose to create a new object for representing measurements, for producing formatted string representations of measurements, and for converting measurements between scales.

Common user needs that can be addressed by a robust API for measurements include, but are not limited to:

Description

We propose creating a new Measure API, with the following properties.

Note: ⚠️ All property/method names up for bikeshedding.

Precision

A big question is how we should handle precision. Currently this explainer assumes precision means fractional digits, not because it seems good but instead because it seems least-bad. The Java Units of Measurement API appears to resolve this problem by not handling precision at all.

Constructor

The object prototype would provide the following methods:

    let centimeters = new Measurement(30, {unit: "centimeter"})
    centimeters.toString()
    // "30 centimeters"

    footAndInch.toComponents()
    // [ {value: 5, unit: "foot"}, {value: 6, unit: "inch"}]

Mixed units

We absolutely must include mixed units in Measurement, because they're absolutely needed for Smart Units. We can't just include foot-and-inch in Smart Units and not Measurement, since that invites specifically the type of abuse of i18n tools for non-i18n purposes that we're trying to avoid with the Measure proposal

The value of mixed units should be expressed in terms of the largest unit in the mixed unit. (Alternately, the Measurement's value could be expressed in terms of the smallest unit. We're going with largest for the example below)

    let footAndInch = new Measurement(5.5, {unit: "foot-and-inch"})
    footAndInch.toComponents()
    // [ {value: 5, unit: "foot"}, {value: 6, unit: "inch"}]
    footAndInch.toString()
    // "5 feet and 6 inches"

Mathematical operations

Below is a list of mathematical operations that we should consider supporting. Assume that we use CLDR data for now, and that both our unit names and the conversion constants are as in CLDR.

Proposed mathematical operations

Raise a Measurement to an exponent:

    let measurement = new Measurement(10, {unit: "centimeter"});
    measurement.exp(3);
    // { value: 1000, unit: "cubic-centimeter"}
    let measurement = new Measurement(10, {unit: "centimeter"});
    measurement.multiply(20);
    // {value: 200, unit: "centimeter"};
    measurement.divide(10);
    // {value: 20, unit: "centimeter"};
    let measurement1 = new Measurement(10, {unit: "centimeter"});
    let measurement2 = new Measurement(5, {unit: "centimeter"});
    measurement1.add(measurement2);
    // {value: 15, unit: "centimeter"}

    let measurement3 = new Measurement(5, {unit: "meter"});
    measurement1.add(measurement3);
    // in the units of the Measurement that `add` is called on?
    // {value: 510, unit: "centimeter"}

    measurement1.subtract(measurement2);
    // {value: 505, unit: "centimeter"}

    // Precision is given in fractional digits. If doing calculation with units with
    // precision values, the precision should be set to the least precise (taking 
    // into account that units will have to be converted to the same scale)
    let measurementWithPrecision1 = new Measurement(10.12, {unit: "centimeter", precision: 2};
    let measurementWithPrecision2 = new Measurement(10.1234 {unit: "centimeter", precision: 4};
    measurementWithPrecision1.add(measurementWithPrecision2);;
    // {value: 20.24, unit: "centimeter", precision: 2}
    let gallons = new Measurement(2, {unit: "gallon"});
    let miles = new Measurement(30, {unit: "mile"});
    miles.divide(gallon);
    // {value: 15, unit: "miles-per-gallon"}

    let centimeters1 = new Measurement(10, {unit: "centimeter"});
    let centimeters2 = new Measurement(5, {unit: "centimeter"});
    centimeters1.multiply(centimeters2);
    // {value: 50, unit: "square-centimeter" }
    // alternately: {value: 50, unit: "centimeter", exponent: 2}
    let inches = new Measure(12, {unit: "inch"});
    inches.convertTo("centimeter");
    // {value: 30.48, unit: "centimeter}
    // using optional `precision` option
    inches.convertTo("centimeter", 1);
    // { value: 30.5, unit: "centimeter" }

User-defined units

Users can specify values for unit other than the ones we support. The only mathematical operations that apply to Measurements with non-standard units are the ones involving scalars.

convertToLocale method shifted to Smart Units

The method convertToLocale is shifted to Smart Units. This method can use a usage option for Measurements in order to properly localize measurements of certain types of thing.

    let centimeters = Measurement(30.48, {unit: "centimeter", usage: "person-height"});
    centimeters.convertToLocale("en");
    // {value: 5.5, unit: "foot-and-inch"}

    centimeters.toLocaleString('en');
    // "5 feet and 6 inches"