From 8fa4e3845ad546d63659075a874b27b17900016a Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Mon, 27 Nov 2023 18:17:08 -0500 Subject: [PATCH] Add implementation and docs for `Constant` (#200) This is the basic machinery for representing physical constants. Later on, we plan to include `Constant` instances in the library out of the box, but first we want to explore the feature in production inside Aurora's internal repo. The first set of basic features is to multiply and divide a wide variety of types. When we do this, the operations always take place _at compile time_, and symbolically. The next set of features is various kinds of conversions to `Quantity` types and/or raw numbers. The standout feature here is the _perfect conversion policy_: since each `Constant` fully encodes its value in the type, we know exactly which `Quantity` instances it can convert to. Doc updates are included: I added a new reference page for `Constant`. I also updated the alternatives page. A couple libraries that were previously "good" are now "fair", because they use quantities for their constants, which is sub-optimal. mp-units moved from "best" to "good" because I think on balance we're now tied. On the one hand, they have actually included pre-defined constants. On the other hand, our core implementation is better because we also support converting to `Quantity` types. Once we include constants out of the box, I expect we'll be "best". Finally, I re-alphabetized the BUILD rules. This basically meant simply moving `//au:operators` to its rightful place; not sure how it ended up way up in the "C" section in the first place. Helps #90. --- au/BUILD.bazel | 32 ++++- au/constant.hh | 105 ++++++++++++++ au/constant_test.cc | 266 +++++++++++++++++++++++++++++++++++ docs/alternatives/index.md | 19 ++- docs/reference/constant.md | 278 +++++++++++++++++++++++++++++++++++++ docs/reference/index.md | 4 + 6 files changed, 693 insertions(+), 11 deletions(-) create mode 100644 au/constant.hh create mode 100644 au/constant_test.cc create mode 100644 docs/reference/constant.md diff --git a/au/BUILD.bazel b/au/BUILD.bazel index bb05771c..8d34a91d 100644 --- a/au/BUILD.bazel +++ b/au/BUILD.bazel @@ -150,17 +150,23 @@ cc_test( ) cc_library( - name = "operators", - hdrs = ["operators.hh"], + name = "constant", + hdrs = ["constant.hh"], + deps = [ + ":quantity", + ":unit_of_measure", + ":wrapper_operations", + ], ) cc_test( - name = "operators_test", + name = "constant_test", size = "small", - srcs = ["operators_test.cc"], + srcs = ["constant_test.cc"], deps = [ - ":operators", + ":constant", ":testing", + ":units", "@com_google_googletest//:gtest_main", ], ) @@ -263,6 +269,22 @@ cc_test( ], ) +cc_library( + name = "operators", + hdrs = ["operators.hh"], +) + +cc_test( + name = "operators_test", + size = "small", + srcs = ["operators_test.cc"], + deps = [ + ":operators", + ":testing", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "packs", hdrs = ["packs.hh"], diff --git a/au/constant.hh b/au/constant.hh new file mode 100644 index 00000000..84fc788b --- /dev/null +++ b/au/constant.hh @@ -0,0 +1,105 @@ +// Copyright 2023 Aurora Operations, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "au/quantity.hh" +#include "au/quantity_point.hh" +#include "au/stdx/type_traits.hh" +#include "au/unit_of_measure.hh" +#include "au/wrapper_operations.hh" + +namespace au { + +// +// A monovalue type to represent a constant value, including its units, if any. +// +// Users can multiply or divide `Constant` instances by raw numbers or `Quantity` instances, and it +// will perform symbolic arithmetic at compile time without affecting the stored numeric value. +// `Constant` also composes with other constants, and with `QuantityMaker` and other related types. +// +// Although `Constant` does not have any specific numeric type associated with it (as opposed to +// `Quantity`), it can easily convert to any appropriate `Quantity` type, with any rep. Unlike +// `Quantity`, these conversions support _exact_ safety checks, so that every conversion producing a +// correctly representable value will succeed, and every unrepresentable conversion will fail. +// +template +struct Constant : detail::MakesQuantityFromNumber, + detail::ScalesQuantity, + detail::ComposesWith, + detail::ComposesWith, + detail::ComposesWith, + detail::ForbidsComposingWith, + detail::ForbidsComposingWith, + detail::CanScaleByMagnitude { + // Convert this constant to a Quantity of the given rep. + template + constexpr auto as() const { + return make_quantity(static_cast(1)); + } + + // Convert this constant to a Quantity of the given unit and rep, ignoring safety checks. + template + constexpr auto coerce_as(OtherUnit u) const { + return as().coerce_as(u); + } + + // Convert this constant to a Quantity of the given unit and rep. + template + constexpr auto as(OtherUnit u) const { + static_assert(can_store_value_in(u), "Cannot represent constant in this unit/rep"); + return coerce_as(u); + } + + // Get the value of this constant in the given unit and rep, ignoring safety checks. + template + constexpr auto coerce_in(OtherUnit u) const { + return as().coerce_in(u); + } + + // Get the value of this constant in the given unit and rep. + template + constexpr auto in(OtherUnit u) const { + static_assert(can_store_value_in(u), "Cannot represent constant in this unit/rep"); + return coerce_in(u); + } + + // Implicitly convert to any quantity type which passes safety checks. + template + constexpr operator Quantity() const { + return as(U{}); + } + + // Static function to check whether this constant can be exactly-represented in the given rep + // `T` and unit `OtherUnit`. + template + static constexpr bool can_store_value_in(OtherUnit other) { + return representable_in(unit_ratio(Unit{}, other)); + } +}; + +// Make a constant from the given unit. +// +// Note that the argument is a _unit slot_, and thus can also accept things like `QuantityMaker` and +// `SymbolFor` in addition to regular units. +template +constexpr Constant> make_constant(UnitSlot) { + return {}; +} + +// Support using `Constant` in a unit slot. +template +struct AssociatedUnit> : stdx::type_identity {}; + +} // namespace au diff --git a/au/constant_test.cc b/au/constant_test.cc new file mode 100644 index 00000000..12fd2b00 --- /dev/null +++ b/au/constant_test.cc @@ -0,0 +1,266 @@ +// Copyright 2023 Aurora Operations, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "au/constant.hh" + +#include + +#include "au/testing.hh" +#include "au/units/joules.hh" +#include "au/units/meters.hh" +#include "au/units/newtons.hh" +#include "au/units/radians.hh" +#include "au/units/seconds.hh" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::testing::AnyOf; +using ::testing::StaticAssertTypeEq; +using ::testing::StrEq; + +namespace au { +namespace { + +template +std::string stream_to_string(Quantity q) { + std::ostringstream oss; + + // Set the precision to the full precision of R. + oss.precision(std::numeric_limits::digits10 + 1); + + oss << q; + return oss.str(); +} + +// Ad hoc Constant for the speed of light, along with associated variables. +constexpr auto C_MPS = mag<299'792'458>(); +struct SpeedOfLight : decltype(Meters{} / Seconds{} * C_MPS) { + static constexpr const char label[] = "c"; +}; +constexpr const char SpeedOfLight::label[]; +constexpr auto speed_of_light = QuantityMaker{}; +constexpr auto c = make_constant(speed_of_light); + +// Ad hoc Constant for Planck's constant. +constexpr auto H_JS = mag<6'626'070'15>() / mag<1'000'000'00>() * pow<-34>(mag<10>()); +struct PlancksConstant : decltype(Joules{} * Seconds{} * H_JS) { + static constexpr const char label[] = "h"; +}; +constexpr auto plancks_constant = QuantityMaker{}; +constexpr auto h = make_constant(plancks_constant); +} // namespace + +TEST(MakeConstant, MakesConstantFromUnit) { + StaticAssertTypeEq>(); +} + +TEST(MakeConstant, MakesConstantFromQuantityMaker) { + StaticAssertTypeEq>(); +} + +TEST(MakeConstant, MakesAdHocConstantFromQuantityMaker) { + constexpr auto ad_hoc_c = make_constant(meters / second * mag<299'792'458>()); + EXPECT_THAT((1.0 * ad_hoc_c).in(meters / second), SameTypeAndValue(299'792'458.0)); + + auto foo = [](Quantity, int> q) { std::cout << q << std::endl; }; + foo(c); +} + +TEST(MakeConstant, MakesConstantFromSymbol) { + constexpr auto m = symbol_for(meters); + constexpr auto s = symbol_for(seconds); + constexpr auto ad_hoc_c = mag<299'792'458>() * make_constant(m / s); + EXPECT_THAT(123 * ad_hoc_c, QuantityEquivalent(123 * c)); +} + +TEST(Constant, CanGetQuantityBySpecifyingRep) { + EXPECT_THAT(c.as(), SameTypeAndValue(c * 1.0f)); + EXPECT_THAT(c.as(), SameTypeAndValue(c * 1)); +} + +TEST(Constant, CanGetQuantityInSpecifiedUnitAndRep) { + EXPECT_THAT(c.in(meters / second), SameTypeAndValue(get_value(C_MPS))); + EXPECT_THAT(c.as(meters / second), + SameTypeAndValue((meters / second)(get_value(C_MPS)))); +} + +TEST(Constant, UsesExactSafetyChecksInsteadOfHeuristics) { + EXPECT_THAT(c.in(meters / second), SameTypeAndValue(get_value(C_MPS))); + EXPECT_THAT(c.as(meters / second), + SameTypeAndValue((meters / second)(get_value(C_MPS)))); + + // The following code must not compile, since the speed of light in m/s can't fit in `int16_t`. + // + // Uncomment the following to test: + // c.as(meters / second); +} + +TEST(Constant, CanCoerce) { + EXPECT_THAT(c.coerce_in(kilo(meters) / second), SameTypeAndValue(299'792)); + EXPECT_THAT(c.coerce_as(kilo(meters) / second), + SameTypeAndValue((kilo(meters) / second)(299'792))); +} + +TEST(Constant, MakesQuantityWhenPostMultiplyingNumericValue) { + EXPECT_THAT(3.f * c, SameTypeAndValue(speed_of_light(3.f))); +} + +TEST(Constant, MakesQuantityWhenPreMultiplyingNumericValue) { + EXPECT_THAT((c * 2).coerce_as(meters / second), + SameTypeAndValue((meters / second)(get_value(mag<2>() * C_MPS)))); +} + +TEST(Constant, MakesQuantityWhenDividingIntoNumericValue) { + EXPECT_THAT(20u / c, SameTypeAndValue(inverse(speed_of_light)(20u))); +} + +TEST(Constant, MakesQuantityWhenDividedByNumericValue) { + EXPECT_THAT((c / 2.0).as(meters / second), + SameTypeAndValue((meters / second)(get_value(C_MPS / mag<2>())))); + + // The following must not compile, because it would use integer division with an implicit + // numerator of `1`, and would therefore always be zero. + // + // Uncomment to make sure the compilation fails: + // c / 2; +} + +TEST(Constant, AppliesConstantSymbolToUnitLabel) { + constexpr auto lambda = nano(meters)(512.0); + EXPECT_THAT(stream_to_string(h * c / lambda), + AnyOf(StrEq("0.001953125 (h * c) / nm"), StrEq("0.001953125 (c * h) / nm"))); +} + +TEST(Constant, MakesScaledConstantWhenPostMultipliedByMagnitude) { + StaticAssertTypeEq()), Constant())>>(); +} + +TEST(Constant, MakesScaledConstantWhenDividedByMagnitude) { + StaticAssertTypeEq()), Constant())>>(); +} + +TEST(Constant, MakesScaledConstantWhenPreMultipliedByMagnitude) { + StaticAssertTypeEq>(); +} + +TEST(Constant, MakesScaledInverseConstantWhenDividedIntoMagnitude) { + StaticAssertTypeEq{} * PI)>>(); +} + +TEST(Constant, ChangesUnitsForQuantityWhenPostMultiplying) { + EXPECT_THAT(newtons(5.f) * c, SameTypeAndValue((newton * speed_of_light)(5.f))); +} + +TEST(Constant, ChangesUnitsForQuantityWhenDividingIt) { + EXPECT_THAT(joules(8) / c, SameTypeAndValue((joules / speed_of_light)(8))); +} + +TEST(Constant, ChangesUnitsForQuantityWhenPreMultiplying) { + EXPECT_THAT(c * seconds(5.f), SameTypeAndValue((speed_of_light * seconds)(5.f))); +} + +TEST(Constant, ChangesUnitsForQuantityWhenDividedIntoIt) { + EXPECT_THAT(c / meters(4.0), SameTypeAndValue((speed_of_light / meter)(0.25))); + + // The following must not compile, because it would use integer division with an implicit + // numerator of `1`, and would therefore always be zero. + // + // Uncomment to make sure the compilation fails: + // EXPECT_THAT(c / meters(4), SameTypeAndValue((speed_of_light / meter)(0.25))); +} + +TEST(Constant, ChangesUnitsForQuantityMakerWhenPostMultiplying) { + StaticAssertTypeEq>(); +} + +TEST(Constant, ChangesUnitsForQuantityMakerWhenDividingIt) { + StaticAssertTypeEq>(); +} + +TEST(Constant, ChangesUnitsForQuantityMakerWhenPreMultiplying) { + StaticAssertTypeEq>(); +} + +TEST(Constant, ChangesUnitsForQuantityMakerWhenDividedIntoIt) { + StaticAssertTypeEq>(); +} + +TEST(Constant, ChangesUnitsForSingularNameWhenPostMultiplying) { + StaticAssertTypeEq>(); +} + +TEST(Constant, ChangesUnitsForSingularNameWhenDividingIt) { + StaticAssertTypeEq>(); +} + +TEST(Constant, ChangesUnitsForSingularNameWhenPreMultiplying) { + StaticAssertTypeEq>(); +} + +TEST(Constant, ChangesUnitsForSingularNameWhenDividedIntoIt) { + StaticAssertTypeEq>(); +} + +TEST(Constant, ComposesViaMultiplication) { + StaticAssertTypeEq>(); +} + +TEST(Constant, SupportsMultiplyingConstantByItself) { + StaticAssertTypeEq>(); +} + +TEST(Constant, ComposesViaDivision) { + StaticAssertTypeEq>(); +} + +TEST(Constant, FailsToCompileWhenMultiplyingOrDividingWithQuantityPoint) { + // Uncomment each line below individually to verify. + + // make_constant(meters) * meters_pt(1); + // make_constant(meters) / meters_pt(1); + // meters_pt(1) * make_constant(meters); + // meters_pt(1) / make_constant(meters); + + // make_constant(meters) * meters_pt; + // make_constant(meters) / meters_pt; + // meters_pt * make_constant(meters); + // meters_pt / make_constant(meters); +} + +TEST(Constant, ImplicitlyConvertsToAppropriateQuantityTypes) { + constexpr QuantityI32 v = c; + EXPECT_THAT(v, SameTypeAndValue((meters / second)(get_value(C_MPS)))); + + // The following must not compile. Uncomment inside the scope to check. + { + // constexpr Quantity v = c; + // (void)c; + } +} + +TEST(Constant, SupportsUnitSlotAPIs) { + constexpr auto three_c_mps = (3.f * c).as(meters / second); + EXPECT_THAT(three_c_mps.in(c), SameTypeAndValue(3.f)); +} + +TEST(CanStoreValueIn, ChecksRangeOfTypeForIntegers) { + EXPECT_TRUE(decltype(c)::can_store_value_in(meters / second)); + EXPECT_FALSE(decltype(c)::can_store_value_in(meters / second)); +} + +} // namespace au diff --git a/docs/alternatives/index.md b/docs/alternatives/index.md index 9f5e12b3..584dbf10 100644 --- a/docs/alternatives/index.md +++ b/docs/alternatives/index.md @@ -888,17 +888,24 @@ features. - Includes built-in constants as quantities - Includes built-in constants as quantities + Includes built-in constants as quantities + Includes built-in constants as quantities - + "Faster than lightspeed" constants - - Plan to support someday; see - #90. + +
    +
  • Constants as types
  • +
  • Perfect conversion policy
  • +
  • Implicit Quantity conversion
  • +
  • + No built-in values yet (see #90) +
  • +
diff --git a/docs/reference/constant.md b/docs/reference/constant.md new file mode 100644 index 00000000..aaf3ba8e --- /dev/null +++ b/docs/reference/constant.md @@ -0,0 +1,278 @@ +# Constant + +`Constant` is a family of template types, each of which represents a single specific quantity value. + +Recall that the usual way to represent quantity values is with a [`Quantity`](./quantity.md) +type. This holds a numeric variable of type `Rep` inside, letting it represent +many possible quantity values. By contrast, `Constant` is an empty class and has no "rep": the +single value it can represent is fully encoded in its type. This makes it an example of +a [monovalue type](./detail/monovalue_types.md). + +Because the value is always fully known at compile time, we do not need to use a heuristic like the +overflow safety surface to determine which conversions are allowed. Instead, we can achieve +a perfect conversion policy: we allow converting to any `Quantity` that can represent the value +exactly, and disallow all other conversions. + +The main use of `Constant` is to multiply and divide raw numbers or `Quantity` values. When we do +this, the constant is applied _symbolically_, and affects the _units_ of the resulting quantity. +For example, multiplying a duration in seconds by a constant representing the speed of light +produces a _length_, measured in units of _light-seconds_. Notably, _the underlying stored numeric +value does not change_: whether a duration of `5` seconds, or a length of `5` light-seconds, we +still store `5` under the hood. + +This approach means that if subsequent operations cancel out the constant, this cancellation is both +_exact_ and has _zero runtime cost_. + +## Constructing `Constant` + +`Constant` encodes all information about the value in its type. Moreover, it has only a single +template parameter, which is a [unit](./unit.md). Therefore, the first step is to encode your +quantity as a unit --- that is, to define the unit "U" such that your quantity has a value of +"1 U". + +To do this, follow [the usual instructions for creating new units](../howto/new-units.md). Note +that you can use a much simpler definition that omits most of the optional features. The only +important ones are those labeled `[1]` (the strong type definition) and `[2]` (the unit label). + +Having defined your unit, you can pass an instance to the `make_constant` function. If the unit you +defined above is called `YourUnits`, and the constant is called `YOUR_CONSTANT`, then the constant +definition will look like this: + +```cpp +constexpr auto YOUR_CONSTANT = make_constant(YourUnits{}); +``` + +Finally, note that the argument to `make_constant()` is a [unit +slot](../discussion/idioms/unit-slots.md), so you can pass "unit-like" alternatives such as +`QuantityMaker` or `SymbolFor` instances as well. + +??? example "Full worked example: speed of light" + Let's look at an example of defining a constant for the speed of light. Both the name of the + instance and the label will be `c`. + + === "C++14" + + ```cpp + // In .hh file: + struct SpeedOfLight : decltype(Meters{} / Seconds{} * mag<299'792'458>()) { + static constexpr const char label[] = "c"; + }; + + constexpr auto c = make_constant(SpeedOfLight{}); + ``` + + ```cpp + // In .cc file: + constexpr const char SpeedOfLight::label[]; + ``` + + === "C++17 or later" + + ```cpp + // In .hh file: + struct SpeedOfLight : decltype(Meters{} / Seconds{} * mag<299'792'458>()) { + static constexpr inline const char label[] = "c"; + }; + + constexpr auto c = make_constant(SpeedOfLight{}); + ``` + +### Ad hoc constants + +You can obtain many of the benefits of `Constant` even if you don't formally define a new unit. +Because `make_constant` has a unit slot API, you can pass an ad hoc expression to it. For example: + +```cpp +constexpr auto c = make_constant(meters / second * mag<299'792'458>()); +``` + +The main advantage of doing this is its conciseness: the constant definition is a single, readable +line. The built constant also has all of the multiplication and division operators types that +`Constant` supports, as well as its perfect conversion policy to any `Quantity` type. + +The only disadvantage is the missing label, which will make printed quantities hard to understand +because the constant will be represented as `[UNLABELED_UNIT]` in the compound label. + +If the constant is used in multiple translation units, or if it leads to values that are printed +out, we believe this disadvantage outweighs the benefits, and we recommend a full definition with +a new unit. Otherwise, the ad hoc constant approach may be called for. + +## `Constant` and unit slots + +`Constant` can be passed to any API that takes a [unit slot](../discussion/idioms/unit-slots.md). + +## Converting to `Quantity` + +`Constant` can be converted to any `Quantity` type of the same dimension. + +By default, this conversion policy is _perfect_. This means that it permits converting to any +`Quantity` that can represent the value exactly, and disallows all other conversions. Users can +also override this policy by choosing the "coerce" variant of any API (say, using `.coerce_as()` +instead of `.as()`). + +Finally, it's important to appreciate that `Constant` has no rep, no underlying numeric type. +Therefore, every `Quantity` conversion API requires an explicit template parameter to specify the +desired rep. + +### `.as()` + +This function expresses the constant as a `Quantity` in "units of this constant". Therefore, the +underlying stored value will be `T{1}`, and the rep will be `T`. + +### `.as(unit)` {#as-T-unit} + +This function expresses the constant as a `Quantity` in the requested unit, using a rep of `T`. It +has a perfect conversion policy, which means that it compiles if and only if the constant's value in +the requested unit can be exactly represented in the type `T`. + +The argument `unit` is a [unit slot](../discussion/idioms/unit-slots.md) API, so it accepts a unit +instance, quantity maker instance, or any other instance compatible with a unit slot. + +### `.coerce_as(unit)` + +This function expresses the constant as a `Quantity` in the requested unit, using a rep of `T`. It +is similar to [`.as(unit)`](#as-T-unit), except that it will ignore the safety checks that +prevent truncation and overflow. + +!!! warning + Because `.as(unit)` has a perfect conversion policy, we know that this function either + produces the exact same result (in which case you could simply _call_ `.as(unit)`), _or_ it + produces a result which is **guaranteed to be lossy**. Therefore, be very judicious in using + this function. + +### `.in(unit)` {#in-T-unit} + +This function produces a raw numeric value, of type `T`, holding the value of the constant in the +requested unit. It has a perfect conversion policy, which means that it compiles if and only if the +constant's value in the requested unit can be exactly represented in the type `T`. + +The argument `unit` is a [unit slot](../discussion/idioms/unit-slots.md) API, so it accepts a unit +instance, quantity maker instance, or any other instance compatible with a unit slot. + +### `.coerce_in(unit)` + +This function produces a raw numeric value, of type `T`, holding the value of the constant in the +requested unit. It is similar to [`.in(unit)`](#in-T-unit), except that it will ignore the +safety checks that prevent truncation and overflow. + +!!! warning + Because `.in(unit)` has a perfect conversion policy, we know that this function either + produces the exact same result (in which case you could simply _call_ `.in(unit)`), _or_ it + produces a result which is **guaranteed to be lossy**. Therefore, be very judicious in using + this function. + +### Implicit `Quantity` conversion + +`Constant` will implicitly convert to any `Quantity` type which passes the safety checks on +truncation and overflow. Essentially: any time [`.as(unit)`](#as-T-unit) produces a result, that +same result can be obtained via implicit conversion. + +This provides great flexibility and confidence in passing `Constant` values to APIs that take +`Quantity`. + +!!! note + The fact that `Constant` has a perfect conversion policy means that we can use it with APIs + where the corresponding `Quantity` would not work, because `Quantity` is forced to use the + overflow safety surface, which is a more conservative heuristic. + + For example, suppose you have an API accepting `Quantity, int>`, + and a constant `c` representing the speed of light. + + You will be able to pass `c` to this API, because the constant-to-quantity conversion operation + knows the exact value at compile time, and can verify that it fits in an `int`. + + By contrast, you would not be able to pass `c.as()` (which is a `Quantity`). Even though + it would work for _this specific value_ (which is `1`), this quantity-to-quantity conversion is + too dangerous for `int` in general. + +## Operations + +Each operation with a `Constant` consists in multiplying or dividing with some other family of +types. + +### Raw numeric type `T` + +Multiplying or dividing `Constant` with a raw numeric type `T` produces a `Quantity` whose rep +is `T`, and whose unit is derived from `Unit`. + +In the following table, we will use `x` to represent the value that was stored in the input of type +`T`. + +| Operation | Resulting Type | Underlying Value | Notes | +| --------- | -------------- | ---------------- | ----- | +| `Constant * T` | `Quantity` | `x` | | +| `Constant / T` | `Quantity` | `T{1} / x` | Disallowed for integral `T` | +| `T * Constant` | `Quantity` | `x` | | +| `T / Constant` | `Quantity, T>` | `x` | | + +### `Quantity` + +Multiplying or dividing `Constant` with a `Quantity` produces a `Quantity` whose rep is +`R`, and whose unit is derived from `Unit` and `U`. + +In the following table, we will use `x` to represent the underlying value in the input quantity --- +that is, if the input quantity was `q`, then `x` is `q.in(U{})`. + +| Operation | Resulting Type | Underlying Value | Notes | +| --------- | -------------- | ---------------- | ----- | +| `Constant * Quantity` | `Quantity, R>` | `x` | | +| `Constant / Quantity` | `Quantity, R>` | `R{1} / x` | Disallowed for integral `R` | +| `Quantity * Constant` | `Quantity, R>` | `x` | | +| `Quantity / Constant` | `Quantity, R>` | `x` | | + +### `Constant` + +Constants compose: the product or quotient of two `Constant` instances is a new `Constant` instance. + +| Operation | Resulting Type | +| --------- | -------------- | +| `Constant * Constant` | `Constant>` | +| `Constant / Constant` | `Constant>` | + +### `QuantityMaker` + +Multiplying or dividing `Constant` with a `QuantityMaker` produces a new `QuantityMaker` +whose unit is derived from `Unit` and `U`. + +| Operation | Resulting Type | +| --------- | -------------- | +| `Constant * QuantityMaker` | `QuantityMaker>` | +| `Constant / QuantityMaker` | `QuantityMaker>` | +| `QuantityMaker * Constant` | `QuantityMaker>` | +| `QuantityMaker / Constant` | `QuantityMaker>` | + +### `SingularNameFor` + +Multiplying or dividing `Constant` with a `SingularNameFor` produces a new +`SingularNameFor` whose unit is derived from `Unit` and `U`. + +| Operation | Resulting Type | +| --------- | -------------- | +| `Constant * SingularNameFor` | `SingularNameFor>` | +| `Constant / SingularNameFor` | `SingularNameFor>` | +| `SingularNameFor * Constant` | `SingularNameFor>` | +| `SingularNameFor / Constant` | `SingularNameFor>` | + +### `Magnitude` + +Multiplying or dividing `Constant` with a `Magnitude` produces a new `Constant` which is +scaled by that magnitude. + +In the following table, let `m` be an instance of `Magnitude`. + +| Operation | Resulting Type | +| --------- | -------------- | +| `Constant * Magnitude` | `Constant` | +| `Constant / Magnitude` | `Constant` | +| `Magnitude * Constant` | `Constant` | +| `Magnitude / Constant` | `Constant{} * m)>` | + +### `QuantityPointMaker` (deleted) + +Multiplying or dividing `Constant` with a `QuantityPointMaker` is explicitly deleted, +because quantity points do not support multiplication. + +### `QuantityPoint` (deleted) + +Multiplying or dividing `Constant` with a `QuantityPoint` is explicitly deleted, +because quantity points do not support multiplication. diff --git a/docs/reference/index.md b/docs/reference/index.md index 137f155e..c6b655a3 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -16,6 +16,10 @@ Here's a guide to the main reference pages. a _displacement_). Practically speaking, this is **essential for dealing with temperatures**, and useful for a couple other dimensions such as pressures and distances. +- **[`Constant`](./constant.md).** A constant quantity which is known at compile time, and + represented by a symbol. Supports exact symbolic arithmetic at compile time, and a perfect + conversion policy to `Quantity` types. + - **[`Unit`](./unit.md).** A type which represents a _unit of measure_. - **[`Magnitude`](./magnitude.md).** A special kind of compile-time number, which we use to