forked from mpusz/mp-units
Merge pull request #375 from chiphogg/chiphogg/docs
Update prefix documentation
This commit is contained in:
@@ -87,6 +87,7 @@ set(unitsSphinxDocs
|
|||||||
"${CMAKE_CURRENT_SOURCE_DIR}/framework/constants.rst"
|
"${CMAKE_CURRENT_SOURCE_DIR}/framework/constants.rst"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/framework/conversions_and_casting.rst"
|
"${CMAKE_CURRENT_SOURCE_DIR}/framework/conversions_and_casting.rst"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/framework/dimensions.rst"
|
"${CMAKE_CURRENT_SOURCE_DIR}/framework/dimensions.rst"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/framework/magnitudes.rst"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/framework/quantities.rst"
|
"${CMAKE_CURRENT_SOURCE_DIR}/framework/quantities.rst"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/framework/quantity_kinds.rst"
|
"${CMAKE_CURRENT_SOURCE_DIR}/framework/quantity_kinds.rst"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/framework/quantity_like.rst"
|
"${CMAKE_CURRENT_SOURCE_DIR}/framework/quantity_like.rst"
|
||||||
|
@@ -17,6 +17,7 @@ Framework Basics
|
|||||||
framework/quantity_points
|
framework/quantity_points
|
||||||
framework/quantity_kinds
|
framework/quantity_kinds
|
||||||
framework/dimensions
|
framework/dimensions
|
||||||
|
framework/magnitudes
|
||||||
framework/units
|
framework/units
|
||||||
framework/arithmetics
|
framework/arithmetics
|
||||||
framework/constants
|
framework/constants
|
||||||
|
121
docs/framework/magnitudes.rst
Normal file
121
docs/framework/magnitudes.rst
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
.. namespace:: units
|
||||||
|
|
||||||
|
Magnitudes
|
||||||
|
==========
|
||||||
|
|
||||||
|
The ratio of two Units of the same Dimension---say, ``inches`` and ``centimeters``---is some
|
||||||
|
constant number, which is known at compile time. It's a positive real number---a *Magnitude*.
|
||||||
|
|
||||||
|
We also use Magnitudes for *Dimensionless* Units. ``percent`` has a Magnitude of :math:`1/100`, and
|
||||||
|
``dozen`` would have a Magnitude of :math:`12`.
|
||||||
|
|
||||||
|
Interestingly, it turns out that the usual numeric types are not up to this task. We need a
|
||||||
|
Magnitude representation that can do everything Units can do. This means, among other things:
|
||||||
|
|
||||||
|
1. We need *exact* symbolic computation of the core operations of Quantity Calculus (i.e., products
|
||||||
|
and rational powers).
|
||||||
|
|
||||||
|
2. We must support *irrational* Magnitudes, because they frequently occur in practice (e.g.,
|
||||||
|
consider the ratio between ``degrees`` and ``radians``).
|
||||||
|
|
||||||
|
3. We should *avoid overflow* wherever possible (note that ``std::intmax_t`` can't even handle
|
||||||
|
certain simple SI prefixes, such as ``yotta``, representing :math:`10^{24}`).
|
||||||
|
|
||||||
|
Integers' inadequacies are clear enough, but even floating point falls short. Imagine if we
|
||||||
|
implemented all angular units in terms of ``radians``: then both ``degrees`` and ``revolutions``
|
||||||
|
pick up a factor of :math:`\pi`. The arithmetic with *its floating point representation* is
|
||||||
|
unlikely to cancel *exactly*.
|
||||||
|
|
||||||
|
Another common alternative choice is ``std::ratio``, but this fails the first requirement: rational
|
||||||
|
numbers are (`rather infamously <https://hsm.stackexchange.com/a/7>`_!) *not* closed under rational
|
||||||
|
powers.
|
||||||
|
|
||||||
|
The only viable solution we have yet encountered is the *vector space representation*. The
|
||||||
|
implementation is fascinating---but, for purposes of this present page, it's also a distraction.
|
||||||
|
*Here,* we're more focused on how to *use* these Magnitudes.
|
||||||
|
|
||||||
|
One type per Magnitude, one value per type
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
Each typical numeric type (``double``, ``int64_t``, ...) can represent a wide variety of values: the
|
||||||
|
more, the better. However, Magnitudes are **not** like that. Instead, they comprise a *variety* of
|
||||||
|
types, and each type can hold only *one* value.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
A given Magnitude represents the *same* number, whether you use it as a *type*, or as a *value*
|
||||||
|
(i.e., an *instance* of that type).
|
||||||
|
|
||||||
|
Use whichever is more convenient. (In practice, this is usually the *value*: especially for end
|
||||||
|
users rather than library developers.)
|
||||||
|
|
||||||
|
If these types can only represent one value each, why would we bother to instantiate them? Because
|
||||||
|
*values are easier to use*.
|
||||||
|
|
||||||
|
- ``mag<N>()`` gives the Magnitude value corresponding to any integer ``N``. - You can combine
|
||||||
|
values in the usual way using ``*``, ``/``, ``==``, and ``!=``, as well as ``pow<N>(m)`` and
|
||||||
|
``root<N>(m)`` for any Magnitude value ``m``.
|
||||||
|
|
||||||
|
Traits: integers and rational Magnitudes
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
If you have a Magnitude instance ``m``, we provide traits to help you reason about integers and
|
||||||
|
rational numbers, or manipulate integer or rational parts.
|
||||||
|
|
||||||
|
- ``is_integral(m)``: indicates whether ``m`` represents an *integral* Magnitude.
|
||||||
|
- ``is_rational(m)``: indicates whether ``m`` represents a *rational* Magnitude.
|
||||||
|
|
||||||
|
The above traits indicate what kind of Magnitude we already have. The next traits *manipulate* a
|
||||||
|
Magnitude, letting us break it apart into *constituent* Magnitudes which may be more meaningful.
|
||||||
|
(For example, imagine going from ``inches`` to ``feet``. Naively, we might multiply by the floating
|
||||||
|
point representation of ``1.0 / 12.0``. However, if we broke this apart into separate numerator and
|
||||||
|
denominator, it would let us simply *divide by 12*, yielding **exact** results for inputs that
|
||||||
|
happen to be multiples of 12.)
|
||||||
|
|
||||||
|
- ``numerator(m)`` (value): a Magnitude representing the "numerator", i.e., the largest integer
|
||||||
|
which divides ``m``, without turning any of its base powers' exponents negative (or making any
|
||||||
|
previously-negative exponents *more* negative). - ``denominator(m)`` (value): the "numerator" of
|
||||||
|
the *inverse* of ``m``.
|
||||||
|
|
||||||
|
These traits interact as one would hope. For example, ``is_rational(m)`` is exactly equivalent to
|
||||||
|
``m == numerator(m) / denominator(m)``.
|
||||||
|
|
||||||
|
Why these particular definitions? Because they are equivalent to the numerator and denominator when
|
||||||
|
we have a rational number, and they are compatible with how humans write numbers when we don't.
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- :math:`m1 = \frac{27 \pi^2}{25}`. Then ``numerator(m1) == mag<27>()``, and
|
||||||
|
``denominator(m1) == mag<25>()``.
|
||||||
|
- :math:`m2 = \sqrt{m1}`. Then ``numerator(m2) == mag<3>()``, and ``denominator(m2) == mag<5>()``.
|
||||||
|
Note that this is consistent with how humans would typically write ``m2``, as
|
||||||
|
:math:`\frac{3\sqrt{3} \pi}{5}`.
|
||||||
|
|
||||||
|
Getting values out
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Magnitude types represent numbers in non-numeric types. They've got some amazing strengths (exact
|
||||||
|
rational powers!), and some significant weaknesses (no support for basic addition!). So what if you
|
||||||
|
just want to turn a Magnitude ``m`` into a traditional numeric type ``T``?
|
||||||
|
|
||||||
|
You call ``get_value<T>(m)``.
|
||||||
|
|
||||||
|
This does what it looks like it does, and it does it at compile time. Any intermediate computations
|
||||||
|
take place in the "widest type in category"---``long double`` for floating point, and
|
||||||
|
``std::intmax_t`` or ``std::uintmax_t`` for signed or unsigned integers---before ultimately being
|
||||||
|
cast back to the target type. For ``T = float``, say, this means we get all the precision we'd have
|
||||||
|
with something like ``long double``, but without any speed penalty at runtime!
|
||||||
|
|
||||||
|
``get_value<T>(m)`` also has the protections you would hope: for example, if ``T`` is an integral
|
||||||
|
type, we require ``is_integral(m)``.
|
||||||
|
|
||||||
|
How to use Magnitudes
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
- First, start with your basic inputs: this will typically be ``mag<N>()`` for any integer ``N``, or
|
||||||
|
the built-in Magnitude constant ``pi``. (Again, these are all *values*, not types.)
|
||||||
|
|
||||||
|
- Next, combine and manipulate these using the various "Magnitude math" operations, all of which are
|
||||||
|
**exact**: ``*``, ``/``, ``pow<N>``, ``root<N>``, ``numerator()``, ``denominator()``.
|
||||||
|
|
||||||
|
- If you need to translate a Magnitude ``m`` to a "real" numeric type ``T``, call
|
||||||
|
``get_value<T>(m)``.
|
@@ -203,26 +203,27 @@ complete list of all the :term:`SI` prefixes supported by the library::
|
|||||||
|
|
||||||
namespace si {
|
namespace si {
|
||||||
|
|
||||||
struct yocto : prefix<yocto, "y", ratio(1, 1, -24)> {};
|
struct yocto : prefix<yocto, "y", pow<-24>(mag<10>())> {};
|
||||||
struct zepto : prefix<zepto, "z", ratio(1, 1, -21)> {};
|
struct zepto : prefix<zepto, "z", pow<-21>(mag<10>())> {};
|
||||||
struct atto : prefix<atto, "a", ratio(1, 1, -18)> {};
|
struct atto : prefix<atto, "a", pow<-18>(mag<10>())> {};
|
||||||
struct femto : prefix<femto, "f", ratio(1, 1, -15)> {};
|
struct femto : prefix<femto, "f", pow<-15>(mag<10>())> {};
|
||||||
struct pico : prefix<pico, "p", ratio(1, 1, -12)> {};
|
struct pico : prefix<pico, "p", pow<-12>(mag<10>())> {};
|
||||||
struct nano : prefix<nano, "n", ratio(1, 1, -9)> {};
|
struct nano : prefix<nano, "n", pow<-9>(mag<10>())> {};
|
||||||
struct micro : prefix<micro, "µ", ratio(1, 1, -6)> {};
|
struct micro : prefix<micro, basic_symbol_text{"\u00b5", "u"},
|
||||||
struct milli : prefix<milli, "m", ratio(1, 1, -3)> {};
|
pow<-6>(mag<10>())> {};
|
||||||
struct centi : prefix<centi, "c", ratio(1, 1, -2)> {};
|
struct milli : prefix<milli, "m", pow<-3>(mag<10>())> {};
|
||||||
struct deci : prefix<deci, "d", ratio(1, 1, -1)> {};
|
struct centi : prefix<centi, "c", pow<-2>(mag<10>())> {};
|
||||||
struct deca : prefix<deca, "da",ratio(1, 1, 1)> {};
|
struct deci : prefix<deci, "d", pow<-1>(mag<10>())> {};
|
||||||
struct hecto : prefix<hecto, "h", ratio(1, 1, 2)> {};
|
struct deca : prefix<deca, "da", pow<1>(mag<10>())> {};
|
||||||
struct kilo : prefix<kilo, "k", ratio(1, 1, 3)> {};
|
struct hecto : prefix<hecto, "h", pow<2>(mag<10>())> {};
|
||||||
struct mega : prefix<mega, "M", ratio(1, 1, 6)> {};
|
struct kilo : prefix<kilo, "k", pow<3>(mag<10>())> {};
|
||||||
struct giga : prefix<giga, "G", ratio(1, 1, 9)> {};
|
struct mega : prefix<mega, "M", pow<6>(mag<10>())> {};
|
||||||
struct tera : prefix<tera, "T", ratio(1, 1, 12)> {};
|
struct giga : prefix<giga, "G", pow<9>(mag<10>())> {};
|
||||||
struct peta : prefix<peta, "P", ratio(1, 1, 15)> {};
|
struct tera : prefix<tera, "T", pow<12>(mag<10>())> {};
|
||||||
struct exa : prefix<exa, "E", ratio(1, 1, 18)> {};
|
struct peta : prefix<peta, "P", pow<15>(mag<10>())> {};
|
||||||
struct zetta : prefix<zetta, "Z", ratio(1, 1, 21)> {};
|
struct exa : prefix<exa, "E", pow<18>(mag<10>())> {};
|
||||||
struct yotta : prefix<yotta, "Y", ratio(1, 1, 24)> {};
|
struct zetta : prefix<zetta, "Z", pow<21>(mag<10>())> {};
|
||||||
|
struct yotta : prefix<yotta, "Y", pow<24>(mag<10>())> {};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,12 +232,14 @@ domain::
|
|||||||
|
|
||||||
namespace iec80000 {
|
namespace iec80000 {
|
||||||
|
|
||||||
struct kibi : prefix<kibi, "Ki", ratio( 1'024)> {};
|
struct kibi : prefix<kibi, "Ki", pow<10>(mag<2>())> {};
|
||||||
struct mebi : prefix<mebi, "Mi", ratio( 1'048'576)> {};
|
struct mebi : prefix<mebi, "Mi", pow<20>(mag<2>())> {};
|
||||||
struct gibi : prefix<gibi, "Gi", ratio( 1'073'741'824)> {};
|
struct gibi : prefix<gibi, "Gi", pow<30>(mag<2>())> {};
|
||||||
struct tebi : prefix<tebi, "Ti", ratio( 1'099'511'627'776)> {};
|
struct tebi : prefix<tebi, "Ti", pow<40>(mag<2>())> {};
|
||||||
struct pebi : prefix<pebi, "Pi", ratio( 1'125'899'906'842'624)> {};
|
struct pebi : prefix<pebi, "Pi", pow<50>(mag<2>())> {};
|
||||||
struct exbi : prefix<exbi, "Ei", ratio(1'152'921'504'606'846'976)> {};
|
struct exbi : prefix<exbi, "Ei", pow<60>(mag<2>())> {};
|
||||||
|
struct zebi : prefix<zebi, "Zi", pow<70>(mag<2>())> {};
|
||||||
|
struct yobi : prefix<yobi, "Yi", pow<80>(mag<2>())> {};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -401,6 +401,11 @@ struct pi_base {
|
|||||||
static constexpr long double value = std::numbers::pi_v<long double>;
|
static constexpr long double value = std::numbers::pi_v<long double>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A convenient Magnitude constant for pi, which we can manipulate like a regular number.
|
||||||
|
*/
|
||||||
|
inline constexpr Magnitude auto pi = magnitude<base_power<pi_base>{}>{};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Magnitude equality implementation.
|
// Magnitude equality implementation.
|
||||||
|
|
||||||
|
@@ -193,7 +193,6 @@ TEST_CASE("magnitude converts to numerical value")
|
|||||||
|
|
||||||
SECTION("pi to the 1 supplies correct values")
|
SECTION("pi to the 1 supplies correct values")
|
||||||
{
|
{
|
||||||
constexpr auto pi = pi_to_the<1>();
|
|
||||||
check_same_type_and_value(get_value<float>(pi), std::numbers::pi_v<float>);
|
check_same_type_and_value(get_value<float>(pi), std::numbers::pi_v<float>);
|
||||||
check_same_type_and_value(get_value<double>(pi), std::numbers::pi_v<double>);
|
check_same_type_and_value(get_value<double>(pi), std::numbers::pi_v<double>);
|
||||||
check_same_type_and_value(get_value<long double>(pi), std::numbers::pi_v<long double>);
|
check_same_type_and_value(get_value<long double>(pi), std::numbers::pi_v<long double>);
|
||||||
|
Reference in New Issue
Block a user