Introducing Absolute Quantities¶
Until now, mp-units forced users to choose between points (no arithmetic) and deltas (no physical semantics) — missing the most common case: a non-negative absolute amount.
An absolute quantity represents an absolute amount of a physical property — measured from a true, physically meaningful zero. Examples include mass in kilograms, temperature in Kelvin, or length in meters (as a size, not a position). Such quantities live on a ratio scale and are anchored at a physically meaningful zero; negative values are typically meaningless.
Absolute quantities stand in contrast to:
- Affine points (e.g., \(20\ \mathrm{°C}\), \(100\ \mathrm{m}\ \mathrm{AMSL}\)) — values measured relative to an arbitrary or conventional origin.
- Deltas (e.g., \(10\ \mathrm{K}\), \(–5\ \mathrm{kg}\)) — differences between two values.
Arithmetic on absolute quantities behaves like ordinary algebra: addition, subtraction, and scaling are well-defined and map naturally to physical reasoning. This article proposes making absolute quantities the default abstraction in mp-units V3, reflecting how scientists express equations in practice.
Note: Revised on March 23, 2026 for clarity, accuracy, and completeness.
Background¶
Affine Space Recap¶
Until now, mp-units modeled two fundamental abstractions:
- Points – represented by
quantity_pointin V2 - Deltas – represented by
quantity
Info
More information on this subject can be found in the Affine Space chapter.
This design works but is sometimes awkward: users often misuse quantity_point to
represent absolute magnitudes (e.g., total mass), losing arithmetic and printability.
Conversely, using deltas everywhere hides physical intent and allows nonsensical operations.
The new absolute quantity abstraction aims to bridge that gap.
Quantity abstractions in physics¶
Below is a summary table comparing the three main quantity abstractions:
| Feature | Point | Absolute | Delta |
|---|---|---|---|
| Physical Model | Interval Scale | Ratio Scale | Difference |
| Example | \(20\ \mathrm{°C}\), \(100\ \mathrm{m}\ \mathrm{AMSL}\) | \(293.15\ \mathrm{K}\), \(100\ \mathrm{kg}\) | \(10\ \mathrm{K}\), \(-5\ \mathrm{kg}\) |
| Has True Zero? | No (arbitrary/conventional) | Yes (physical) | N/A |
| Allows Negative? | Yes (below arbitrary origin) | No (opt-in) | Yes (direction matters) |
| Addition (A + A) | ❌ Error (\(20\ \mathrm{°C} + 10\ \mathrm{°C}\)) | ✅ Absolute (\(10\ \mathrm{kg} + 5\ \mathrm{kg}\)) | ✅ Delta |
| Subtraction (A − A) | ✅ Delta (\(30\ \mathrm{°C} - 10\ \mathrm{°C}\)) | ✅ Delta (\(50\ \mathrm{kg} - 5\ \mathrm{kg}\)) | ✅ Delta |
| Multiplication (k×A) | ❌ Error (\(2 \times 20\ \mathrm{°C}\)) | ✅ Absolute (\(2 \times 10\ \mathrm{kg}\)) | ✅ Delta (\(-2 \times 5\ \mathrm{m}\)) |
| Division (A / A) | ❌ Error | ✅ Scalar (\(10\ \mathrm{kg} / 5\ \mathrm{kg}\)) | ✅ Scalar (ratio) |
| A + Delta | ✅ Point (shift) | ✅ Delta | ✅ Delta |
| API | quantity<point<...>> |
quantity<...> |
quantity<delta<...>> |
This table summarizes the key differences in semantics, API, and physical meaning for each abstraction. Use it as a quick reference when deciding which concept to use in your code.
Motivation¶
This section explains the driving reasons for introducing absolute quantities. It highlights the practical pain points, limitations, and sources of confusion in the current model, motivating the need for a new abstraction.
Current Pain Points¶
- Limited arithmetic for points – Points can’t be multiplied, divided, or accumulated.
This often forces the users to convert the quantity point to a delta with either
qp.quantity_from_unit_zero()orqp.quantity_from(some_origin)member functions, which is at least cumbersome. - No text output for points – A point's textual representation depends on its origin, which is often implicit or user-defined. As of today, we do not have the means to provide a text symbol for the point origin. Moreover, points may contain both an absolute and a relative origin at the same time, which makes it harder to determine which origin to use for printing. Also, the same point may be represented in many ways (different deltas from various origins). Should such a point have the same or a different textual output for each representation?
- Error-prone simplifications – Developers frequently replace points with deltas for
convenience, losing safety and physical clarity. This is the most dangerous pain point,
because it compiles silently. In V2, both
quantity<K>andquantity<deg_C>are deltas and are numerically interchangeable. A function acceptingquantity<K>will silently accept20 * deg_C, using the numeric value20instead of293.15. Thermodynamic expressions such as Carnot efficiency will then produce results that are off by a factor of roughly 15, with no compiler warning and no runtime error — the bug is invisible until you check the numbers.
Example¶
Mass Balance in Drying Process
A food technologist is drying several samples of a specific product to estimate its change in moisture content (on a wet basis) during the drying process.
quantity<percent> moisture_content_change(quantity<kg> water_lost, // delta
quantity<kg> total) // absolute
{
gsl_Expects(is_gt_zero(total));
return water_lost / total;
}
quantity<kg> initial[] = { 2.34 * kg, 1.93 * kg, 2.43 * kg };
quantity<kg> dried[] = { 1.89 * kg, 1.52 * kg, 1.92 * kg };
quantity total_initial = std::reduce(std::cbegin(initial), std::cend(initial));
quantity total_dried = std::reduce(std::cbegin(dried), std::cend(dried));
std::cout << "Initial product mass: " << total_initial << "\n";
std::cout << "Dried product mass: " << total_dried << "\n";
std::cout << "Moisture content change: " << moisture_content_change(total_initial - total_dried, total_initial) << "\n";
quantity<percent> moisture_content_change(quantity<kg> water_lost,
quantity_point<kg> total)
{
gsl_Expects(is_gt_zero(total));
return water_lost / total.quantity_from_unit_zero();
}
quantity_point<kg> initial[] = { point<kg>(2.34), point<kg>(1.93), point<kg>(2.43) };
quantity_point<kg> dried[] = { point<kg>(1.89), point<kg>(1.52), point<kg>(1.92) };
auto point_plus = [](QuantityPoint auto a, QuantityPoint auto b){ return a + b.quantity_from_unit_zero(); };
quantity_point total_initial = std::reduce(std::cbegin(initial), std::cend(initial), point<kg>(0.), point_plus);
quantity_point total_dried = std::reduce(std::cbegin(dried), std::cend(dried), point<kg>(0.), point_plus);
std::cout << "Initial product mass: " << total_initial.quantity_from_unit_zero() << "\n";
std::cout << "Dried product mass: " << total_dried.quantity_from_unit_zero() << "\n";
std::cout << "Moisture content change: " << moisture_content_change(total_initial - total_dried, total_initial) << "\n";
In the above example:
water_lostshould be a delta (difference in mass),totalshould conceptually be non-negative absolute amount of mass, yet the type system doesn’t enforce it.
Semantics¶
This section details the semantics of absolute quantities, their relationship to other abstractions, and how they interact in code and mathematics. It clarifies the rules, conversions, and algebraic properties that underpin the new model.
Position of Absolute Quantities Among Abstractions¶
| Feature | Point | Absolute | Delta |
|---|---|---|---|
| Interpolation | ✓ | ✓ | ✓ |
| Multiplication / Division | ✗ | ✓ | ✓ |
| Addition | ✗ | ✓ | ✓ |
| Subtraction | ✓ | ✓ | ✓ |
| May be non‑negative | ✓ | ✓ | ✗ |
| Zero reference | Explicit origin | Anchored at true zero | N/A |
| Can use offset units | ✓ | ✗ | ✓ |
| Convertible to offset units | Via offset | ✗ | No offset |
| Text output | ✗ | ✓ | ✓ |
Absolute quantities sit logically between points and deltas: they behave like deltas algebraically, yet conceptually reference a true zero. This design simplifies arithmetic, improves printing, and preserves physical meaning.
As we can see above, absolute quantities have only two limitations, and both are connected to the use of offset units. They can't use those because they must remain absolute instead of being measured relative to some custom origin.
Simplified API in V3¶
As I mentioned in my previous post,
we are seriously considering removing the quantity_point class template and replacing it
with a quantity_spec point wrapper. For example, quantity_point<isq::altitude[m]> will
become quantity<point<isq::altitude[m]>>.
I initially planned quantity<isq::mass> to be the same as quantity<delta<isq::mass>>,
but it turns out that deltas probably should not be the default. It is consistent with how
we write physical expressions on paper, right? The delta symbol (∆) is always "verbose"
in physical equations. It would be nice for the C++ code to do the same. So, starting with
mp-units V3, deltas will always need to be explicit.
And this brings us to absolute quantities. As we stated in the previous chapter, they could be considered:
- deltas against nothing, and
- points from a well-established zero.
This leads us to the observation that they are the perfect default we are looking for. If we take any physics books, such quantities are the ones that we see in most of the physical equations. This is why we will not need any specifier to denote them in mp-units V3.
Here are some simple examples:
quantity q1 = 20 * kg; // absolute quantity (measured from true zero)
quantity q2 = delta<kg>(20); // delta quantity (difference)
quantity q3 = point<kg>(20); // point quantity (relative to an origin)
The above will produce the following types:
static_assert(std::is_same_v<decltype(q1), quantity<kg>>);
static_assert(std::is_same_v<decltype(q2), quantity<delta<kg>>>);
static_assert(std::is_same_v<decltype(q3), quantity<point<kg>>>);
This mirrors the way physicists write equations: absolute values by default, with explicit Δ when needed.
Alignment with scientific practice¶
The proposed abstractions mirror the way quantities are treated in physics and engineering textbooks:
- Absolute quantities (e.g., mass, energy, length) are always measured from a natural zero and are mostly non-negative.
- Points (e.g., position, temperature on a relative scale) are always defined relative to an origin.
- Deltas (differences) are the result of subtracting two points or two absolutes.
The following table maps each abstraction to its measurement scale, mathematical structure, physical meaning, and typical C++ representation:
| Concept | Measurement Scale | Mathematical Structure | Physical Meaning | Example |
|---|---|---|---|---|
| Point | Interval Scale | Affine Space | A location on a scale or in space. | point<mass>, point<time>, point<altitude>, point<position_vector> (vector), point<velocity> (vector) |
| Delta | — | Vector Space | A change, displacement, or interval. | delta<mass>, delta<duration>, delta<height>, displacement (vector), velocity (vector) |
| Absolute | Ratio Scale | Convex Cone (\(\ge 0\)) | A magnitude measured from a true zero. | mass, duration, height, distance (scalar), speed (scalar) |
This correspondence ensures that code written with mp-units is not only type-safe, but also directly maps to the equations and reasoning found in scientific literature. This makes code easier to review, verify, and maintain.
Scalar and Vector Quantities¶
Absolute quantities are always scalar. Vector quantities — those with direction — are inherently signed and therefore always modeled as deltas in mp-units. There is no such thing as an "absolute vector quantity".
Named vector quantities such as displacement and velocity are already deltas by
their physical nature, so the delta<> wrapper is neither needed nor used for them:
| Quantity | Scalar | Vector |
|---|---|---|
| Point | point<time>, point<altitude>, point<mass> |
point<position_vector>, point<velocity> |
| Delta | delta<duration>, delta<height>, delta<mass> |
displacement, velocity (no delta<> wrapper needed) |
| Absolute | duration, height, mass |
(none — use norm() to obtain a scalar absolute) |
To obtain a scalar absolute from a vector delta, take its norm:
quantity<displacement[m]> v = ...; // vector delta
quantity<distance[m]> d = norm(v); // scalar absolute
For signed scalar deltas, abs() (or equivalently .absolute()) converts a delta
to an absolute:
quantity d1 = delta<height[m]>(-3); // signed scalar delta
quantity d2 = abs(d1); // scalar absolute — same as d1.absolute()
Conversions Between Abstractions¶
As absolute quantities share properties of both deltas and points with implicit point origins, they should be explicitly convertible to those:
or
The opposite is not always true:
- not every delta will be a positive amount against nothing,
- not every point on the scale will be positive (e.g., altitudes).
This is why it is better only to expose named functions to convert points and deltas to absolute quantities:
quantity<kg> q3 = q1.absolute(); // may fail the pre-condition check if negative
quantity<kg> q4 = q2.absolute(); // may fail the pre-condition check if negative
It is important to note that conversions between absolute quantities and points should be available only when there is no point origin specified for a point (the point uses an implicit point origin — no explicit origin).
If the user provided an explicit origin, then such a quantity can only be used as a delta:
inline constexpr struct nhn_sea_level final : absolute_point_origin<isq::altitude> {} nhn_sea_level;
quantity<m> alt1 = 42 * m;
quantity<point<m>> alt2(alt1); // OK
// quantity<point<m>, nhn_sea_level> alt3(alt1); // Compile-time error
quantity<point<m>, nhn_sea_level> alt4 = nhn_sea_level + alt1.delta();
If the user used an offset unit, then a conversion should work fine as long as the point origin of the offset unit is defined in terms of the implicit point origin:
quantity<K> temp1 = 300 * K;
quantity<point<K>> temp2(temp1); // OK
quantity<point<C>> temp3(temp1); // OK
To summarize:
| From \ To | Point | Absolute | Delta |
|---|---|---|---|
| Point | Identity | .absolute() |
point - point → delta |
| Absolute | Explicit or .point() |
Identity | Explicit or .delta() |
| Delta | origin + delta → point | .absolute() |
Identity |
Arithmetic Semantics¶
Affine space arithmetic is well-defined and commonly used in physics and engineering. With the introduction of absolute quantities, it is important to clarify the meaning of arithmetic operations involving points, absolutes, and deltas.
Addition¶
Adding two absolute quantities (e.g., \(10\ \mathrm{kg} + 5\ \mathrm{kg}\)) produces another absolute quantity. This is simply the sum of two non-negative amounts, both measured from the true zero of the physical property.
Adding a delta to an absolute quantity (e.g., \(10\ \mathrm{kg} + (-2\ \mathrm{kg})\))
yields a (potentially negative) delta. The library cannot statically determine at compile
time whether a delta is positive, so the result is conservatively typed as a delta.
If you need an absolute quantity as a result, you must explicitly convert via .absolute(),
which checks the non-negativity precondition at runtime and may fail if the value is
negative. This ensures that negative results are always intentional and checked,
increasing code safety.
Adding an absolute quantity or delta to a point yields a point shifted by the given amount.
Here is the summary of all the addition operations:
| Lhs \ Rhs | Point | Absolute | Delta |
|---|---|---|---|
| Point | Point | Point | |
| Absolute | Point | Absolute | Delta |
| Delta | Point | Delta | Delta |
Info
Here are some numerical examples that present the available operations and their results:
Subtraction¶
Subtraction in the context of physical quantities is best understood as finding the difference between two amounts.
Subtracting two absolute quantities (e.g., \(50\ \mathrm{kg} - 5\ \mathrm{kg}\)) yields a delta (difference), which may be negative or positive. This is consistent with the mathematical and physical meaning of subtraction.
If an absolute result is needed (e.g., remaining mass), it should be obtained via an explicit conversion:
This rule prevents invalid negative absolutes while remaining explicit when needed.
Subtracting a delta from an absolute quantity (e.g., \(10\ \mathrm{kg} - 2\ \mathrm{kg}\))
yields a delta. If you need an absolute quantity as a result, you must explicitly convert
the delta to an absolute quantity (i.e., using .absolute()), which will check the
non-negativity precondition at runtime and may fail if the value is negative. This
approach ensures that negative results are always intentional and checked, increasing
code safety.
Subtracting an absolute quantity from a point yields a point, and subtracting a point from an absolute quantity is not meaningful.
Subtracting an absolute quantity from a delta (e.g., \(2\ \mathrm{kg} - 10\ \mathrm{kg}\)) also yields a delta. The result has no guarantee of positivity — the library cannot determine at compile time whether the delta exceeds the absolute — so the type conservatively remains a delta.
Here is a summary of all of the subtraction operations
| Lhs \ Rhs | Point | Absolute | Delta |
|---|---|---|---|
| Point | Delta | Point | Point |
| Absolute | Delta | Delta | |
| Delta | Delta | Delta |
Info
Here are some numerical examples that present the available operations and their results:
quantity abs = isq::height(42 * m);
quantity pt = point<isq::altitude[m]>(10);
quantity d = delta<isq::height[m]>(2);
quantity res1 = pt - abs; // Point
// quantity res2 = abs - pt; // Compile-time error
quantity res3 = abs - pt.absolute(); // Delta or quantity_difference
quantity res4 = abs - d; // Delta
quantity res5 = d - abs; // Delta
Magnitude and Ratio Operations¶
Beyond addition and subtraction, the following table summarises the multiplication, division, and magnitude operations involving absolute quantities and deltas:
| Operation | Result | Notes |
|---|---|---|
Absolute / Absolute |
Absolute | A physical ratio (e.g., efficiency, strain, density). |
Absolute / Delta |
Delta | Rate of an absolute with respect to a signed step. |
Delta / Absolute |
Delta | A change scaled by a fixed magnitude. |
Delta / Delta |
Delta | A rate of change (e.g., velocity = displacement / duration). |
abs(delta_scalar) |
Absolute | Magnitude of a scalar delta. Equivalent to delta.absolute(). |
norm(delta_vector) |
Absolute | Magnitude of a vector delta (e.g., speed = norm(velocity)). |
The last two rows highlight the two pathways from a delta to an absolute:
- For scalar deltas:
abs(d)or equivalentlyd.absolute()— both check the non-negativity precondition at runtime. - For vector deltas:
norm(v)— the Euclidean norm, which is always non-negative by definition.
Interpolation¶
Interpolation of absolute quantities (e.g., finding a value between \(a\) and \(b\)) is well-defined and results in another absolute quantity. This is commonly used in physics and engineering for averaging, blending, or estimating values between two known points.
Non-negativity¶
Non-negativity of absolute quantities is meant to be an opt-in feature in the quantity
specification. For example, we can pass a non_negative tag type in quantity definition:
With that, we will be able to mark any quantity as non-negative and this property will be inherited by the subquantities in the quantity hierarchy tree of the same kind.
We could also derive non-negativity for derived quantities based on their equations. A non-negative flag could be set only if all of the components in the equations are marked as non-negative. However, we will not be able to account for a negative scalar possibly used in an equation. This is why it is probably better to assume that ad-hoc derived quantities do not inherit this property. If this result is assigned to a typed/named quantity, then the non-negativity will be checked then (if set).
Interesting Quantity Types¶
Time¶
Let's look closer at the quantity of time. There is no way to measure its absolute value as we don't even know where (when?) the time axis starts... Only time points and time deltas (durations) make sense.
However it seems that the ISQ thought this through already. It does not define a quantity of time at all. It only provides duration defined as:
ISO 80000-3:2019
name: duration
symbol: \(t\)
Definition: measure of the time difference between two events
Remarks: Duration is often just called time. Time is one of the seven base quantities in the International System of Quantities, ISQ (see ISO 80000-1). Duration is a measure of a time interval.
As long as absolute quantity<isq::time[s]> may seem off, quantity<isq::duration[s]>
is OK as an absolute quantity. Durations typically should be non-negative as well.
If we need a negative duration then quantity<delta<isq::duration[s]>> makes sense.
isq::time should be reserved for affine space point only
(e.g., quantity<point<isq::time>[s]>). You can find more about this in my
Bringing Quantity-Safety To The Next Level
blog article.
The above does not mean that we should always use typed quantities for time. We can still use simple quantities and the library will reason about them correctly:
quantity<s>or42 * s-> absolute durationquantity<delta<s>>ordelta<s>(-3)-> delta durationquantity<point<s>>orpoint<s>(94573457438583)-> time point
Length¶
A somewhat similar case might be the length quantity, as there is no one well-established
zero origin that serves as a reference for all length measurements. However, asking the
users always to provide a delta specifier for length would probably be overkill.
Everyone can imagine what no length/radius/height means. Also, most of us are okay
with each object having its own length measurement origin.
Note
In the context of absolute quantities, quantity<m> always represents a size (such
as the length, width, or height of an object), not a position in space.
Positions are modeled using the point<m> abstraction, which always refers to
a location relative to a defined origin.
This distinction ensures that code using absolute quantities for size cannot be
confused with code representing positions. For example, quantity<m> is used
for the length of a rod, not its position in space.
Electric Current¶
Electric current is the only ISQ base quantity that has a well defined zero point (no current flow in the wire), but its values can be both positive and negative depending on the direction the current flows.
We could be tempted to model electric current as the 1-dimensional vector quantity, but probably it is not the best idea. It is officially defined as a signed scalar quantity.
Absolute electric current values should be available, but should not perform a non-negative precondition check on construction and assignment.
Temperature¶
As we pointed out before, it will be possible to form absolute quantities of temperature, but only when the unit is (potentially prefixed) Kelvin. For offset units like degree Celsius, it will not be possible.
The reason runs deeper than a mere unit-system rule. Kelvin temperature encodes thermodynamic energy content: the mean molecular kinetic energy of an ideal gas is proportional to \(k_B T\), where \(T\) must be an absolute temperature. This makes Kelvin a ratio scale quantity — ratios and products involving \(T\) are physically meaningful (\(T_h / T_c\) in the Carnot efficiency, \(nRT\) in the ideal gas law). Degree Celsius, by contrast, is an interval scale: only differences of Celsius temperatures are physically meaningful, not their ratios or products. A Celsius value of \(20\ ^\circ\mathrm{C}\) is not "twice as hot" as \(10\ ^\circ\mathrm{C}\).
This distinction is exactly what the Point/Absolute split captures:
quantity<K>— Absolute: ratio-scale magnitude, safe in multiplicative expressions.quantity<point<deg_C>>— Point: interval-scale location, blocked in multiplicative expressions by the type system.
If the user has a temperature point in Celsius and wants to treat it as an absolute quantity and pass it to some quantity equation, then such point first needs to be converted to use Kelvin as its unit and then we need to create an absolute quantity from it:
quantity temp = point<deg_C>(21);
quantity kinetic_energy = 3 / 2 * si::boltzmann_constant * temp.in(K).absolute();
The explicit .in(K).absolute() chain is a type-safety checkpoint: it ensures the
programmer consciously acknowledges the shift from an interval-scale location to a
ratio-scale magnitude, preventing the silent 20 vs. 293.15 error described in the
Current Pain Points section above.
Which Quantity Abstraction Should I Use?¶
The decision tree below provides guidance on choosing the appropriate quantity abstraction for a specific use case. Start with the first question and follow the path based on your answers:
flowchart TD
ask_point{{Adding two quantities of this type makes sense?}}
ask_point -- No --> Point[Point]
ask_point -- Yes --> ask_delta{{Possibly negative difference/distance between two quantities?}}
ask_delta -- Yes --> Delta[Delta]
ask_delta -- No --> Absolute[Absolute]
%% Styling
classDef pointStyle fill:#c0392b,stroke:#e74c3c,stroke-width:2px,color:#fff
classDef deltaStyle fill:#27ae60,stroke:#2ecc71,stroke-width:2px,color:#fff
classDef absoluteStyle fill:#2980b9,stroke:#3498db,stroke-width:2px,color:#fff
classDef questionStyle fill:#d4a017,stroke:#f0b429,stroke-width:2px,color:#fff
class Point pointStyle
class Delta deltaStyle
class Absolute absoluteStyle
class ask_point,ask_delta questionStyle
Bug Prevention and Safety Benefits¶
The new model eliminates a class of subtle bugs that arise from conflating positions, sizes, and differences. For example:
- No accidental addition of positions
- the type system prevents adding two
point<m>objects, which is physically meaningless
- the type system prevents adding two
- No silent sign errors
- subtracting two absolute quantities always yields a delta, so negative results are explicit and must be handled intentionally
- No misuse of offset units
- absolute quantities cannot be constructed with offset units (like Celsius), preventing incorrect temperature calculations
- Compile‑time enforcement
- most mistakes caught before runtime
Revised Example¶
Let's revisit our initial example. Here is what it can look like with the absolute quantities usage:
Mass Balance in Drying Process
A food technologist is drying several samples of a specific product to estimate its change in moisture content (on a wet basis) during the drying process.
quantity<delta<percent>> // (1)!
moisture_content_change(quantity<delta<kg>> water_lost, // (2)!
quantity<kg> total) // (3)!
{
// gsl_Expects(is_gt_zero(total)); (4)
return water_lost / total;
}
quantity<kg> initial[] = { 2.34 * kg, 1.93 * kg, 2.43 * kg }; // (5)!
quantity<kg> dried[] = { 1.89 * kg, 1.52 * kg, 1.92 * kg };
quantity total_initial = std::reduce(std::cbegin(initial), std::cend(initial)); // (6)!
quantity total_dried = std::reduce(std::cbegin(dried), std::cend(dried));
std::cout << "Initial product mass: " << total_initial << "\n"; // (7)!
std::cout << "Dried product mass: " << total_dried << "\n";
std::cout << "Moisture content change: " << moisture_content_change(total_initial - total_dried, total_initial) << "\n"; // (8)!
- Theoretically a negative result is possible if the product gained water in the process of drying.
- Explicit delta.
- Absolute quantity.
- No longer needed as absolute quantities of mass will have a precondition of being non-negative.
- Simple initialization of absolute quantities.
- Arithmetic works.
- Test output works.
- Type safe!
This version is concise, physically sound, and type‑safe. Function arguments can't be reordered and non‑negativity guarantees remove the need for manual runtime checks.
Migration and Backward Compatibility¶
Although the theory in the chapters above may seem intimidating, users will not be significantly affected by the changes in mp-units V3.
As we can see, the new code above does not differ much from our previously unsafe but
favored version. The only difference is that we spell delta explicitly now, and that we
can safely assume that absolute values of mass are non-negative. The rest looks and feels
the same, but the new solution is safer and more expressive.
The biggest migration challenge is related to the results of subtraction on two
quantities, points, or deltas. From now on, they will result in a different type
(i.e., quantity<delta<...>>) which is not implicitly convertible to an absolute
quantity.
quantity<km / h> avg_speed(quantity<km> distance, quantity<h> duration)
{
return distance / duration;
}
quantity_point<km> odo1 = ...;
quantity_point<s> ts1 = ...;
quantity_point<km> odo2 = ...;
quantity_point<s> ts2 = ...;
quantity res = avg_speed((odo2 - odo1).absolute(), (ts2 - ts1).absolute()); // OK
In case of an invalid order of subtraction arguments, the precondition check will fail at runtime only for the conversion to absolutes case. This increases the safety of our code.
Another migration challenge may be related to the usage of negative values. All of the
ISQ base quantities besides electric current could be defined to be non-negative
for absolute quantities. This means that if a user is dealing with a negative
delta today, such code will fail the precondition check in the absolute quantity
constructor. To fix that, the user will need to use explicit delta specifiers or
construction helpers in such cases:
quantity<m> d1 = -2 * m; // Precondition check failure at runtime
quantity<delta<m>> d2(-2 * m); // Precondition check failure at runtime
quantity d3 = delta<m>(-2); // OK
Regarding temperature support, in the Semantics chapter, we said that a new abstraction will not work for offset units. However, it was also the case in mp-units V2. If we check The Affine Space chapter we will find a note that:
Quote
The multiply syntax support is disabled for units that provide a point origin in
their definition (i.e., units of temperature like K, deg_C, and deg_F).
We always found those confusing and required our users to be explicit about the intent
with point and delta construction helpers. So nothing changes here from the coding
point of view. From the design point of view, we replace some strange corner case design
constraint with a properly named abstraction that models that behavior.
Actually, V3 will improve temperature support significantly. Thanks to the new abstraction, we will be able to multiply and divide absolute temperatures with other quantities, but only if the temperature is measured in Kelvin. Also, the multiply syntax will work to construct such absolute quantities (e.g., \(300 \times \mathrm{K}\)).
Last, but not least, the quantity_point<...> class template will be replaced with
quantity<point<...>> syntax.
Key Migration Rules¶
| V2 Pattern | V3 Equivalent | Notes |
|---|---|---|
quantity<kg> |
quantity<kg> |
Before delta and now absolute |
quantity<kg> m = m1 - m2; |
quantity<kg> m = (m1 - m2).absolute(); |
Explicit if you want abs |
quantity<kg> dm = m1 - m2; |
quantity<delta<kg>> dm = m1 - m2; |
Explicit deltas when negative value possible |
quantity_point<kg> p = ...; |
quantity<point<kg>> p = ... |
Use point<kg> wrapper |
quantity<kg> d = p - point<kg>(42); |
quantity<delta<kg>> d = p - point<kg>(42); |
Deltas must be explicit |
Most user code will continue to compile after adding explicit delta or .absolute()
conversions where needed.
Rationale¶
Here we discuss the rationale behind the proposed changes, including the design philosophy, alignment with scientific practice, and the benefits for standardization and future extensibility.
Non-negativity in Physical Equations vs API Design¶
In most physical equations, the quantities we work with are expected to be non-negative amounts. For example, mass, energy, distance, and duration are all inherently non-negative in physical reality. While the mathematical abstraction of a delta (difference) allows for negative values, in practice, negative deltas are rare or even unphysical in many domains.
Consider the case of speed:
Quote
Speed is defined as the ratio of a change in length (distance traveled) to a change in time (duration):
\(speed = \frac{\Delta length}{\Delta time}\)
While the formula uses deltas, in almost all practical scenarios, both \(\Delta length\) and \(\Delta time\) are non-negative. A negative duration, for example, is not physically meaningful and would indicate a logic error in most applications.
Prefer absolute quantities for non-negative deltas¶
This has important implications for API design. If a function like avg_speed accepts
deltas as arguments, users might inadvertently pass negative values, leading to
nonsensical results or subtle bugs:
// Problematic: allows negative duration
quantity<m/s> avg_speed(quantity<delta<m>> distance, quantity<delta<s>> duration)
{
return distance / duration;
}
Instead, it is safer and more physically correct to require absolute quantities for
such parameters, leveraging the fact that ISQ provides distance and duration as
non-negative quantities:
This approach ensures that only non-negative values can be passed, preventing negative durations or distances from entering your calculations. This reduces the risk of bugs, makes your code more robust, and better reflects the intent of most physical equations.
New opportunities¶
The new syntax allows us to model quantities that were impossible to express before without some workarounds.
For example, we can now correctly calculate Carnot engine efficiency with any of the following:
quantity temp_cold = 300. * K;
quantity temp_hot = 500. * K;
quantity carnot_eff_1 = 1. - temp_cold / temp_hot;
quantity carnot_eff_2 = (temp_hot - temp_cold) / temp_hot;
In the above code, we can easily create absolute or delta values of temperatures and perform arithmetic on them. Previously, we had to create deltas from both points artificially with:
quantity temp_cold = point<K>(300.);
quantity temp_hot = point<K>(500.);
quantity carnot_eff_1 = 1. - temp_cold.quantity_from_unit_zero() / temp_hot.quantity_from_unit_zero();
quantity carnot_eff_2 = (temp_hot - temp_cold) / temp_hot.quantity_from_unit_zero();
It worked, but was far from being physically pure and pretty.
Why Obvious Workarounds Fall Short?¶
Two workaround approaches exist, each with its own caveat.
Approach 1: quantity_from_unit_zero()¶
quantity_from_unit_zero() returns the displacement of a quantity point from its unit's
origin. For Kelvin this is si::absolute_zero, so the result is exactly the
thermodynamic temperature:
For Celsius, however, the unit's origin is si::ice_point — the ice point.
The function therefore returns the displacement from the ice point, not from absolute
zero:
This is a silent pitfall: the call compiles and returns a plausible-looking value for any temperature unit, but is only thermodynamically meaningful when the point is already in Kelvin. Explicit conversion before the call fixes it:
Approach 2: Subtract si::absolute_zero¶
Subtracting the absolute-zero origin always gives the correct thermodynamic temperature regardless of the unit the point was stored in:
The only thing to be aware of is the resulting unit. Because si::ice_point (the origin
of the Celsius scale) is defined internally in milli<kelvin>, the subtraction yields
mK rather than K:
The value is correct and rescales cleanly to any unit. In a physical equation like the
ideal gas law, p will come out in mPa instead of Pa, but it will convert
automatically on the first assignment to a typed quantity such as quantity<Pa>.
The bottom line¶
Both approaches work correctly when used with care. quantity_from_unit_zero() is concise but
requires the point to already be in Kelvin; subtraction from si::absolute_zero is
always safe but carries an mK unit until rescaled. In either case, the right idiom —
.in(K).quantity_from_unit_zero() — must be remembered and applied at every call site, and
there is no way to enforce it through the type system.
V3 Absolute Quantities address this: 300 * K is already an Absolute Quantity, directly
usable in any multiplicative expression. When an offset-unit point must enter a
thermodynamic equation, the explicit .in(K).absolute() chain makes the conversion
visible and type-safe — exactly once, at the boundary.
Design Philosophy and Standardization¶
Absolute quantities make physical semantics explicit while simplifying common use cases. They expose existing conceptual complexity rather than adding new layers. The design is:
- Consistent with ISQ – mirrors the distinction between displacement, position, and difference.
- Predictable – clear subtraction and conversion rules.
- Scalable – a single
quantityclass handles all variants via wrappers. - Safe – non‑negativity and offset‑unit rules prevent misuse.
- Extensible – additional quantity abstractions may be added in the future by simply introducing a new wrapper.
For standardization, this model brings three tangible benefits:
- Closer alignment with physical reasoning used by scientists and engineers.
- Improved readability and verification in generic C++ code.
- Zero runtime overhead — all checks are compile‑time or lightweight preconditions.
Mathematical Grounding: Ratio Scale and Convex Sets¶
The term Ratio Scale used throughout this article comes from measurement theory (Stevens, 1946) and is standard in metrology (VIM) and physics. A ratio scale has a true physical zero so that ratios of values are meaningful: \(2\ \mathrm{kg}\) is genuinely twice as much as \(1\ \mathrm{kg}\), whereas \(40\ ^\circ\mathrm{C}\) is not twice as hot as \(20\ ^\circ\mathrm{C}\).
Convex Cone is the corresponding mathematical structure: the set of all admissible values of a ratio-scale quantity is the non-negative half-line \([0, +\infty)\), which is a convex cone — it is closed under addition and under multiplication by a non-negative scalar. Requests for a "validator" or "convex space" abstraction in quantities libraries are describing exactly this property.
The two terms therefore describe the same thing from two complementary angles: Ratio Scale is the measurement-theory characterisation (what operations are physically meaningful), while Convex Cone is the algebraic-structure characterisation (what set the values live in). Both appear in the literature and both are correct; the Alignment with scientific practice table in the Semantics section shows all three columns side-by-side.
The .absolute() conversion method is the explicit, type-safe crossing from an
Interval-Scale Affine Space (Points) or an unrestricted Vector Space (Deltas) into the
Ratio-Scale Convex Cone (Absolutes). Rather than relying on a separate validation layer or
runtime validators, the type system itself encodes the constraint — providing the
mathematical rigor that metrology and physics demand.
Frequently Asked Questions: The V3 Physical Model¶
As mp-units moves toward a more rigorous modeling of physical spaces, several questions arise regarding the distinction between Points, Deltas, and Absolute Quantities. This Q&A addresses the most common technical and philosophical inquiries.
1. Why do we need "Absolute Quantities" if we already have "Delta Quantities"?¶
While both can share the same underlying representation (e.g., double), they represent
different mathematical structures.
- Delta Quantities belong to a Vector Space. They represent a displacement, can be negative, and lack a natural origin.
- Absolute Quantities belong to a Ratio Scale. They represent a magnitude measured from a unique, non-arbitrary physical zero (like \(0\text{ K}\) or \(0\text{ kg}\)).
Without the Absolute abstraction, the library cannot distinguish between a Change in Temperature (\(\Delta 20\text{ K}\)) and a State of Temperature (\(20\text{ K}\) absolute). This leads to the "Offset Unit Trap," where a user might accidentally plug \(20^\circ\text{C}\) (a delta) into an ideal gas law equation, yielding a result off by a factor of 15.
2. Is Velocity a Delta or an Absolute Quantity?¶
Velocity is a Delta Quantity (Vector). It represents a displacement over time and carries direction (or a sign in 1-D).
Speed is an Absolute Quantity (Scalar). It is the magnitude (norm) of that velocity.
In V3, we use the type hierarchy to model this: velocity is a child of speed. When
you call norm(velocity), the type "decays" from the Vector-Delta space to the
Absolute-Scalar space.
3. Why does Absolute - Delta result in a Delta?¶
One might assume that as \(A - A = D\) then \(A - D = A\). However, this violates the Non-Negativity Guarantee of the Absolute type.
Consider a fuel tank and an engine that burns fuel on the way to destination:
fuel_present= \(10\text{ kg}\) (Absolute)fuel_required= \(15\text{ kg}\) (Delta)result= \(-5\text{ kg}\)
A "Negative Absolute Mass" is a physical impossibility. However, a Negative Delta
is mathematically valid—it represents a deficit. To ensure compile-time safety, any
operation that can result in a negative value must return a Delta. Subtraction
is a "Type Demoter" that moves you from the restricted absolute Ratio scale back into
the unrestricted Vector space.
4. If I can't burn "negative fuel," why is BurnRate * Time a Delta?¶
This is a distinction between a Stock (State) and a Flow (Transaction):
- Stocks (Absolute): Represent the "Inventory" (e.g., Fuel in the tank). These are strictly non-negative.
- Flows (Delta): Represent the "Transfer." Even if a specific process (like an engine) only flows in one direction, the mathematical category of a flow must support directionality.
Even if an engine only burns fuel (negative flow), the category of "Flow" must support directionality to handle both consumption and refueling. By modeling the flow as a Delta from the start, we maintain a consistent algebraic chain:
This allows the type system to handle both Fuel Consumption (Negative Delta) and Refueling (Positive Delta) using the same logic, without forcing the user to treat "Refilling" and "Burning" as two different mathematical universes.
5. Why not just use Runtime Contracts for negative Absolute results?¶
We could, but it weakens the type system. If \(Absolute - Delta\) returns an \(Absolute\), the compiler assumes the result is a valid non-negative magnitude. If it isn't, the program crashes or triggers a contract violation at runtime. By returning a Delta Quantity, we handle the "Negative Possibility" at compile-time. The user is forced to acknowledge that the result might be a deficit before treating it as a magnitude again.
6. Should Time Points be treated the same as Position Points?¶
Mathematically, both are Affine Spaces. However, they differ in dimensionality:
- Position exists in a 3-D space; its "Deltas" are Vectors (\(\vec{r}\)).
- Time exists in a 1-D space; its "Deltas" are Signed Scalars.
V3 respects this by allowing 1-D deltas to carry a sign bit (direction), whereas Absolute Quantities (like Age or Duration-amount) are strictly non-negative. Treating a 1-D time-delta as a pure scalar is dangerous because it allows directionality to propagate into equations where only magnitude is required.
For example, if you calculate the Total Energy (\(Q = P \times \Delta t\)) during a reverse-time simulation, a negative \(\Delta t\) will result in a negative \(Q\). If that \(Q\) is then used to calculate a required Mass or Volume, you would get a physically impossible negative result.
By distinguishing between a Delta (the step) and an Absolute (the duration magnitude), V3 forces you to be explicit about whether the direction of time matters to your equation.
7. Why is Quantity the default for Absolute in V3?¶
It aligns the library with how physics is taught. In V2, the most "natural" syntax
(quantity<K>) was often used to represent deltas, forcing users to use more complex
syntax for absolute states. In V3, we align the library with physics textbooks:
- Textbook: \(PV = nRT\) (Uses absolute temperature).
- V3 Code:
(P * V) / (n * T)(Usesquantity<K>, which is Absolute by default).
By making Absolute the default, the most common physics equations become the easiest to write and the safest to execute. If you need a point or a delta, you wrap it; if you have a magnitude, you just use it.
8. Isn't point.absolute() just more boilerplate?¶
It is a Type-Safety Checkpoint. When you convert a point<deg_C> to an absolute<K>,
you are performing a non-trivial physical transformation (shifting the origin to absolute
zero). Forcing the user to call .absolute() ensures that this conversion is intentional.
It prevents the silent bug of dividing two "points" and getting a meaningless ratio.
9. Is the 3-category model too complex for users?¶
It has a learning curve, but it maps more closely to how we think. We don't think of "The distance to the moon" as a "Delta of Position" in daily life; we think of it as a magnitude. By providing the Absolute category, we give users a name for the "buckets of stuff" they are actually measuring.
Conclusion¶
Adding absolute quantities elevates mp-units from a dimensional analysis tool to a true physical reasoning framework. The proposal clarifies semantics, improves safety, and aligns code directly with equations found in textbooks. This is not extra complexity—it's the formalization of the real structure of physical space in C++ types.
We plan to deliver this as part of mp-units V3 and welcome community and WG21 feedback.