From ee08247f5faa8605c8b0db706818159ea27faadd Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Thu, 21 Nov 2024 07:05:12 -0800 Subject: [PATCH 1/5] Update to include mathematical operations --- README.md | 164 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 142 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 534b545..b4c675d 100644 --- a/README.md +++ b/README.md @@ -48,42 +48,162 @@ Note: ⚠️ All property/method names up for bikeshedding. * `precision`, a number indicating the precision of the measurement. This precision is (provisionally) represented in terms of the number of fractional digits displayed. -Note: ⚠️ It may be appropriate to instead represent precision in terms of number of significant digits. Feedback on this matter is desired. +* `exponent`, an integer representing the power to which the unit is raised. "centimeters squared" could be {unit: "centimeter", exponent: 2}. It may be preferable to use CLDR names for commonly-used units; "cubic-meter" instead of {unit: "meter", exponent: 3}, for example. -Constructor: +* `usage`, the type of thing being measured. Useful for localization. -* `Measure(value, unit, precision)`. Constructs a Measure with `value` as the numerical value of the Measure and `unit` as the unit of measurement, with the optional `precision` parameter used to specify the precision of the measurement. In the case of `unit` values indicating mixed units, the `value` is given in terms of the quantity of the *largest* unit. +## Constructor + +* `Measure(value, {unit, precision, exponent, usage})`. Constructs a Measure with `value` as the numerical value of the Measure and `unit` as the unit of measurement, with the optional `precision` parameter used to specify the precision of the measurement. In the case of `unit` values indicating mixed units, the `value` is given in terms of the quantity of the *largest* unit. The object prototype would provide the following methods: * `convertTo(unit, precision)`. This method returns a Measure in the scale indicated by the `unit` parameter, with the value of the new Measure being the value of the Measure it is called on converted to the new scale. The `precision` parameter is optional. -* `split()`. This method returns the measurement represented as an array of objects. The returned array for a Measure given in non-mixed units would contain one object with the following properties: +* `toString()`. This method returns a string representation of the unit. - - `value`, representing the numerical value - - `unit`, representing the unit of measurement. - - `precision`, representing the precision of the measurement. +* `toComponents()`. This method returns each component of the measurement as an object in an array. Intended for use +with mixed units. -The returned array for a Measure given in mixed units would contain one element for each component of the Measure. The `precision` property would only be present in the object representing the smallest unit. +```js + let centimeters = new Measurement(30, {unit: "centimeter"}) + centimeters.toString() + // "30 centimeters" -* `convertToLocale(locale, usage)`. This method returns a Measure in the customary scale and at the customary precision for `locale`. The optional `usage` parameter can be used to indicate that the Measure should use the (potentially idiosyncratic) locale-specific customary unit of measurement for measurements of specific types of things. If, for example, the value was a measurement of a person's height, the value of `usage` would be (following the CLDR names) `"person-height"`. If the measurement is of a distance traveled by road, the value of `usage` would be `"road"`. + footAndInch.toComponents() + // [ {value: 5, unit: "foot"}, {value: 6, unit: "inch"}] +``` -* `toLocaleString(locale, usage)`. This method returns an appropriately formatted string for the locale given by `locale` and the usage given by `usage`, with `usage` being an optional parameter. +### Mixed units -* `toString()`. This method returns a string representation of the unit. +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 -# Examples +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) ```js -let m = new Measure(1.8, "meter"); -m.convertTo('foot', 2); -// Returns a new Measure with the following properties: -// `{value: 5.905511811023621, unit: "foot", precision: 2}` -m.toString(); -// "5.91 feet" -m.localeConvert("en-CA", "person-height") -// `{value: 5.905511811023621, unit: "foot-and-inch", precision: 2}` -m.toLocaleString("en-CA", "person-height") -// "5 feet 11 inches" + 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](https://github.com/unicode-org/cldr/blob/main/common/supplemental/units.xml) +for now, and that both our unit names and the conversion constants are as in CLDR. + +This version places the options `unit`, `exponent`, and `precision` in an options bag. `unit` can be +a required unit, or could default to `unit: "dimensionless"` + +### 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](https://unitsofmeasurement.github.io/unit-api/) +appears to resolve this problem by not handling precision at all. + +### Proposed mathematical operations + +Raise a Measurement to an exponent: + +'''js + let measurement = new Measurement(10, {unit: "centimeter"}) + measurement.exp(3) + // { value: 1000, unit: "cubic-centimeter"} ``` + +* Multiply/divide a measurement by a constant + +```js + let measurement = new Measurement(10, {unit: "centimeter"}) + measurement.multiply(20) + // {value: 200, unit: "centimeter"} + measurement.divide(10) + // {value: 20, unit: "centimeter"} +``` + +* Add/subtract two measurements of the same dimension + +```js + 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} + +``` + +* Multiply / divide a Measurement by another Measurement + +```js + 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} + + centimeters1.divide(centimeters2) + // {value: 10, unit: "dimensionless"} +``` + +* Convert between scales + +```js + let inches = new Measure(12, {unit: "inch"}) + inches.convert("centimeter") + // {value: 30.48, unit: "centimeter} + + // using optional `precision` option + inches.convert("centimeter", 1); + // { value: 30.5, unit: "centimeter" } +``` + +* All of the above operations throw if incompatible dimensions are used, for example, adding +a measure of volume to a measure of speed. + +### Methods shifted to Smart Units + +All of the localization-related methods are shifted to Smart Units. There can be +a `usage` option added to the options bag for Measurements in order to track +what sort of thing is being measured. The `usage` option is used for the method below: + +* convertToLocale(locale) + +```js + + 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" + +``` + From cee5195d8048475f23c6aca4ea9a147f117d71ec Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Thu, 21 Nov 2024 07:11:55 -0800 Subject: [PATCH 2/5] fixup! Update to include mathematical operations --- README.md | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index b4c675d..d7b13e2 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ 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: * The need to convert measurements from one scale to another @@ -24,8 +26,6 @@ Common user needs that can be addressed by a robust API for measurements include * The need to represent and manipulate compound units, such as velocity expressed in kilometers per hour or density expressed in unit of mass per unit of volume -We propose to create a new object for representing measurements, for producing formatted string representations of measurements, and for converting measurements between scales. - * The need to keep track of the precision of measured values. A measurement value represented with a large number of significant figures can imply that the measurements themselves are more precise than the apparatus used to take the measurement can support. * The need to represent currency values. Often users will want to keep track of money values together with the currency in which those values are denominated. @@ -38,8 +38,6 @@ We propose to create a new object for representing measurements, for producing f We propose creating a new `Measure` API, with the following properties. -Note: ⚠️ Serious questions remain about how the API should handle mixed units. The current version allows for mixed unit output, but *not* mixed unit input. This simplifies the API, at the cost of certain infelicities. The decision to disallow mixed unit input is subject to change if clear use cases can be identified. - Note: ⚠️ All property/method names up for bikeshedding. * `unit`, a String representing the measurement unit. This could be expressed using the names used by [CLDR's units.xml](https://github.com/unicode-org/cldr/blob/main/common/supplemental/units.xml). The API design below presumes that these names are used. For example, if a user wanted to use the `convertTo` method to convert a length measurement to feet and inches, they would provide the string `"foot-and-inch"` as the value of the `unit` argument. @@ -54,7 +52,7 @@ Note: ⚠️ All property/method names up for bikeshedding. ## Constructor -* `Measure(value, {unit, precision, exponent, usage})`. Constructs a Measure with `value` as the numerical value of the Measure and `unit` as the unit of measurement, with the optional `precision` parameter used to specify the precision of the measurement. In the case of `unit` values indicating mixed units, the `value` is given in terms of the quantity of the *largest* unit. +* `Measure(value, {unit, precision, exponent, usage})`. Constructs a Measure with `value` as the numerical value of the Measure and `unit` as the unit of measurement, with the optional `precision` parameter used to specify the precision of the measurement. In the case of `unit` values indicating mixed units, the `value` is given in terms of the quantity of the *largest* unit. If no unit is provided, the value of unit is "dimensionless". The object prototype would provide the following methods: @@ -86,7 +84,6 @@ The value of mixed units should be expressed in terms of the largest unit in the going with largest for the example below) ```js - let footAndInch = new Measurement(5.5, {unit: "foot-and-inch"}) footAndInch.toComponents() // [ {value: 5, unit: "foot"}, {value: 6, unit: "inch"}] @@ -101,9 +98,6 @@ Below is a list of mathematical operations that we should consider supporting. Assume that we use [CLDR data](https://github.com/unicode-org/cldr/blob/main/common/supplemental/units.xml) for now, and that both our unit names and the conversion constants are as in CLDR. -This version places the options `unit`, `exponent`, and `precision` in an options bag. `unit` can be -a required unit, or could default to `unit: "dimensionless"` - ### 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](https://unitsofmeasurement.github.io/unit-api/) @@ -119,7 +113,7 @@ Raise a Measurement to an exponent: // { value: 1000, unit: "cubic-centimeter"} ``` -* Multiply/divide a measurement by a constant +* Multiply/divide a measurement by a scalar ```js let measurement = new Measurement(10, {unit: "centimeter"}) @@ -188,6 +182,11 @@ Raise a Measurement to an exponent: * All of the above operations throw if incompatible dimensions are used, for example, adding a measure of volume to a measure of speed. +### 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. + ### Methods shifted to Smart Units All of the localization-related methods are shifted to Smart Units. There can be @@ -197,13 +196,11 @@ what sort of thing is being measured. The `usage` option is used for the method * convertToLocale(locale) ```js - 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" - ``` From c796ed037e4144b50c8f327d23555dea8982fd8f Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Thu, 21 Nov 2024 07:16:56 -0800 Subject: [PATCH 3/5] fixup! fixup! Update to include mathematical operations --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d7b13e2..4be0c46 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,14 @@ Note: ⚠️ All property/method names up for bikeshedding. * `usage`, the type of thing being measured. Useful for localization. +### 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](https://unitsofmeasurement.github.io/unit-api/) +appears to resolve this problem by not handling precision at all. + ## Constructor -* `Measure(value, {unit, precision, exponent, usage})`. Constructs a Measure with `value` as the numerical value of the Measure and `unit` as the unit of measurement, with the optional `precision` parameter used to specify the precision of the measurement. In the case of `unit` values indicating mixed units, the `value` is given in terms of the quantity of the *largest* unit. If no unit is provided, the value of unit is "dimensionless". +* `Measure(value, {unit, precision, exponent, usage})`. Constructs a Measure with `value` as the numerical value of the Measure and `unit` as the unit of measurement, with the optional `precision` parameter used to specify the precision of the measurement. In the case of `unit` values indicating mixed units, the `value` is given in terms of the quantity of the *largest* unit. If no unit is provided, the value of unit is "dimensionless". Exponent indicates the power to which the underlying unit is raised; `exponent` of 2 on an object with "centimeter" as the `unit` value indicates centimeter-squared. The object prototype would provide the following methods: @@ -98,10 +103,6 @@ Below is a list of mathematical operations that we should consider supporting. Assume that we use [CLDR data](https://github.com/unicode-org/cldr/blob/main/common/supplemental/units.xml) for now, and that both our unit names and the conversion constants are as in CLDR. -### 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](https://unitsofmeasurement.github.io/unit-api/) -appears to resolve this problem by not handling precision at all. ### Proposed mathematical operations @@ -187,11 +188,11 @@ a measure of volume to a measure of speed. 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. -### Methods shifted to Smart Units +### `convertToLocale` method shifted to Smart Units -All of the localization-related methods are shifted to Smart Units. There can be -a `usage` option added to the options bag for Measurements in order to track -what sort of thing is being measured. The `usage` option is used for the method below: +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. * convertToLocale(locale) From 57faf471ca2e2af4b20412e394b7c7eb1422d9ea Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Thu, 21 Nov 2024 07:18:25 -0800 Subject: [PATCH 4/5] fixup! fixup! fixup! Update to include mathematical operations --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4be0c46..6ebfa0c 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ for now, and that both our unit names and the conversion constants are as in CLD Raise a Measurement to an exponent: -'''js +```js let measurement = new Measurement(10, {unit: "centimeter"}) measurement.exp(3) // { value: 1000, unit: "cubic-centimeter"} From 808d65afb94eda8e48f15e9c7a0972d8e07efe90 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Thu, 21 Nov 2024 07:22:08 -0800 Subject: [PATCH 5/5] fixup! fixup! fixup! fixup! Update to include mathematical operations --- README.md | 57 +++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 6ebfa0c..414e239 100644 --- a/README.md +++ b/README.md @@ -57,11 +57,11 @@ appears to resolve this problem by not handling precision at all. ## Constructor -* `Measure(value, {unit, precision, exponent, usage})`. Constructs a Measure with `value` as the numerical value of the Measure and `unit` as the unit of measurement, with the optional `precision` parameter used to specify the precision of the measurement. In the case of `unit` values indicating mixed units, the `value` is given in terms of the quantity of the *largest* unit. If no unit is provided, the value of unit is "dimensionless". Exponent indicates the power to which the underlying unit is raised; `exponent` of 2 on an object with "centimeter" as the `unit` value indicates centimeter-squared. +* `Measure(value, {unit, precision, exponent, usage})`. Constructs a Measure with `value` as the numerical value of the Measure and `unit` as the unit of measurement, with the optional `precision` parameter used to specify the precision of the measurement. In the case of `unit` values indicating mixed units, the `value` is given in terms of the quantity of the *largest* unit. If no unit is provided, the value of unit is `"dimensionless"`. `exponent` indicates the power to which the underlying unit is raised; `exponent` of 2 on an object with "centimeter" as the `unit` value indicates centimeter-squared. The object prototype would provide the following methods: -* `convertTo(unit, precision)`. This method returns a Measure in the scale indicated by the `unit` parameter, with the value of the new Measure being the value of the Measure it is called on converted to the new scale. The `precision` parameter is optional. +* `convertTo(unit, precision)`. This method returns a Measure in the scale indicated by the `unit` parameter, with the value of the new Measure being the value of the Measure it is called on converted to the new scale. * `toString()`. This method returns a string representation of the unit. @@ -80,7 +80,7 @@ with mixed units. ### 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 +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 @@ -109,31 +109,31 @@ for now, and that both our unit names and the conversion constants are as in CLD Raise a Measurement to an exponent: ```js - let measurement = new Measurement(10, {unit: "centimeter"}) - measurement.exp(3) + let measurement = new Measurement(10, {unit: "centimeter"}); + measurement.exp(3); // { value: 1000, unit: "cubic-centimeter"} ``` * Multiply/divide a measurement by a scalar ```js - let measurement = new Measurement(10, {unit: "centimeter"}) - measurement.multiply(20) - // {value: 200, unit: "centimeter"} - measurement.divide(10) - // {value: 20, unit: "centimeter"} + let measurement = new Measurement(10, {unit: "centimeter"}); + measurement.multiply(20); + // {value: 200, unit: "centimeter"}; + measurement.divide(10); + // {value: 20, unit: "centimeter"}; ``` * Add/subtract two measurements of the same dimension ```js - let measurement1 = new Measurement(10, {unit: "centimeter"}) - let measurement2 = new Measurement(5, {unit: "centimeter"}) - measurement1.add(measurement2) + 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) + 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"} @@ -143,9 +143,9 @@ Raise a Measurement to an exponent: // 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); + 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} ``` @@ -153,30 +153,29 @@ Raise a Measurement to an exponent: * Multiply / divide a Measurement by another Measurement ```js - let gallons = new Measurement(2, {unit: "gallon"}) - let miles = new Measurement(30, {unit: "mile"}) - miles.divide(gallon) + 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) + 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} - centimeters1.divide(centimeters2) + centimeters1.divide(centimeters2); // {value: 10, unit: "dimensionless"} ``` * Convert between scales ```js - let inches = new Measure(12, {unit: "inch"}) - inches.convert("centimeter") + let inches = new Measure(12, {unit: "inch"}); + inches.convertTo("centimeter"); // {value: 30.48, unit: "centimeter} - // using optional `precision` option - inches.convert("centimeter", 1); + inches.convertTo("centimeter", 1); // { value: 30.5, unit: "centimeter" } ```