Skip to content

Math functions

We provide many common mathematical functions out of the box. If you think we’re missing a particular math function, and you’d like to see it added, reach out to us and ask!

General usage advice

Prefer to make unqualified calls to these functions. So for example: if you’re using unit types and you want the “max”, just write plain max(...).

  • Don’t write std::max(...), because that would give the wrong function.
  • Don’t write au::max(...), because that’s neither necessary nor idiomatic.

Function categories

Here are the functions we provide, grouped roughly into related categories.

Sign-based functions:

Checking signs, comparing to 0

Quantity cannot be compared to 0 or 0.0, since these are raw numeric types. However, any Quantity can be compared to ZERO, which is a built-in constant of the library. See our Zero discussion for more background.

abs

Adapts std::abs to Quantity types. Covers both integral and floating point overloads of std::abs.

Signature:

template <typename U, typename R>
auto abs(Quantity<U, R> q);

Returns: The input quantity, but with std::abs applied to its underlying value.

copysign

Adapts std::copysign to Quantity types.

Signatures:

// 1: First argument Quantity, second argument raw numeric
template <typename U, typename R, typename T>
constexpr auto copysign(Quantity<U, R> mag, T sgn);

// 2: First argument raw numeric, second argument Quantity
template <typename T, typename U, typename R>
constexpr auto copysign(T mag, Quantity<U, R> sgn);

// 3: Both arguments Quantity
template <typename U1, typename R1, typename U2, typename R2>
constexpr auto copysign(Quantity<U1, R1> mag, Quantity<U2, R2> sgn);

Returns: The first argument, with the sign from the second argument applied to it.

Comparison-based functions

min, max

Select the smaller (min) or larger (max) of the two inputs. This operation is unit-aware, and supports mixing different input units, as long as they have the same dimension. These functions support both Quantity and QuantityPoint inputs.

Signatures:1

//
// min()
//

// 1. `Quantity` inputs
template <typename U1, typename U2, typename R1, typename R2>
auto min(Quantity<U1, R1> q1, Quantity<U2, R2> q2);

// 2. `QuantityPoint` inputs
template <typename U1, typename U2, typename R1, typename R2>
auto min(QuantityPoint<U1, R1> p1, QuantityPoint<U2, R2> p2);

//
// max()
//

// 1. `Quantity` inputs
template <typename U1, typename U2, typename R1, typename R2>
auto max(Quantity<U1, R1> q1, Quantity<U2, R2> q2);

// 2. `QuantityPoint` inputs
template <typename U1, typename U2, typename R1, typename R2>
auto max(QuantityPoint<U1, R1> p1, QuantityPoint<U2, R2> p2);

Returns: The value of the smallest (min) or largest (max) of the inputs, expressed in their common type.

Note

unlike std::min and std::max, we return by value, not by reference. This is because we support combining different units. This means the return type will generally be different from the types of the inputs.

clamp

“Clamp” the first parameter to the range defined by the second and third. This is a unit-aware analogue to std::clamp, which was introduced in C++17. However, this version differs in several respects from std::clamp, in order to handle quantities more effectively. We’ll explain these differences at the end of the clamp section.

Signatures:

// 1. `Quantity` inputs
template <typename UV , typename RV ,
          typename ULo, typename RLo,
          typename UHi, typename RHi>
constexpr auto clamp(
    Quantity<UV , RV > v,
    Quantity<ULo, RLo> lo,
    Quantity<UHi, RHi> hi);

// 2. `QuantityPoint` inputs
template <typename UV , typename RV ,
          typename ULo, typename RLo,
          typename UHi, typename RHi>
constexpr auto clamp(
    QuantityPoint<UV , RV > v,
    QuantityPoint<ULo, RLo> lo,
    QuantityPoint<UHi, RHi> hi);
A note on custom comparators

std::clamp includes a four-parameter version, where the fourth parameter is a custom comparator. std::clamp provides this because it must support an unknowably wide range of custom types. However, au::clamp only supports Quantity and QuantityPoint types, whose notions of comparison is unambiguous. Therefore, we opt to keep the library simple, and omit this four-parameter version.

Returns: The closest quantity (or quantity point) to v which is between lo and hi, inclusive — that is, greater than or equal to lo, and less than or equal to hi. If lo > hi, the behaviour is undefined. The return type will be expressed in the appropriate unit and rep; expand the note below for further details.

Details on the unit and rep for the return type

Comparison is a common-unit operation. We must convert all inputs to their common unit before we compare, and therefore the output must also be expressed in this same common unit.

The rep of the return type will be the common type of the input reps. Specifically, given the above signatures, it will be std::common_type_t<RV, RLo, RHi>.

The unit of the return type depends on whether we are working with Quantity inputs, or QuantityPoint.

  • For Quantity, the return type’s unit is CommonUnitT<UV, ULo, UHi>.
  • For QuantityPoint, the return type’s unit is CommonPointUnitT<UV, ULo, UHi>: this is the common point unit, which takes relative origin offsets into account.
Differences from std::clamp

Here are the main changes which stem from handling quantities instead of simple numbers.

  • unlike std::clamp, we return by value, not by reference. This is because we support combining different units. This means the return type will generally be different from the types of the inputs.

  • The return type can be different from the type of v, because we must express it in the common unit and rep of the input parameter types.

  • We do not currently plan to provide the four-parameter overload, unless we get a compelling use case.

Exponentiation

int_pow

Raise a Quantity to an integer power. Since this is an arbitrary-unit operation, the power applies independently to the unit and to the value.

If the input has an integral rep (storage type), then the exponent cannot be negative.

Signature:

template <int Exp, typename U, typename R>
constexpr auto int_pow(Quantity<U, R> q);

Returns: A Quantity whose unit is the input unit raised to the given power, and whose value is the input value raised to the given power.

sqrt

A unit-aware adaptation of std::sqrt. Both the input and output are Quantity types. Since sqrt is an arbitrary-unit operation, the square root applies independently to the unit and to the value.

We mirror std::sqrt in selecting our output rep. That is to say: the output rep will be the return type of std::sqrt when called with a value of our input rep.

Signature:

template <typename U, typename R>
auto sqrt(Quantity<U, R> q);

Returns: A Quantity whose unit is the square root of the input quantity’s unit, and whose value is the square root of the input quantity’s value.

Warning: not all unit conversions are currently supported

There is one edge case to be aware of with sqrt: we don’t yet support any conversion which picks up a radical factor. This is because all conversion factors get computed at compile time, and we don’t have a way to compute rational powers at compile time. To fix this, we would need a constexpr-compatible implementation of std::powl.

Let’s clarify what you can and can’t do in today’s library, with an example.

// Taking the square root of "weird" units: this works.
const auto geo_mean_length = sqrt(inches(1) * meters(1));

// Now let's look at retrieving the value in different units.

// Using a Quantity-equivalent Unit just retrieves the stored value.
// This _always_ works.  (In this case, it gives `1.0`.)
const auto retrieved_value = geo_mean_length.in(sqrt(inch * meters));

// This conversion is non-trivial, but it's also OK.
// The reason is that the conversion factor doesn't have any rational powers.
// (In this case, it gives `10.0`.)
const auto rationally_converted_value = geo_mean_length.in(sqrt(inch * centi(meters)));

// This test case doesn't currently work.
// Later, if we can compute radical conversion factors at compile time, it will.
// (It should give roughly 6.274558...)
// const auto radically_converted_value = geo_mean_length.in(inches);

Trigonometric functions

sin, cos, tan

The value of the named trigonometric function (\sin, \cos, or \tan), evaluated at an input Quantity representing an angle.

If called with any Quantity which is not an angle, we produce a hard compiler error.

Signatures:

//
// sin()
//
template <typename U, typename R>
auto sin(Quantity<U, R> q);

//
// cos()
//
template <typename U, typename R>
auto cos(Quantity<U, R> q);

//
// tan()
//
template <typename U, typename R>
auto tan(Quantity<U, R> q);

Returns: The result of converting the input to Radians, and then calling the corresponding STL function (that is, std::sin() for sin(), and so on).

In converting to radians, we mirror the corresponding STL functions in how we handle the rep. For floating point rep (float, double, and so on), the return type is the rep. For integral inputs (int, uint32_t, and so on), we cast to double and return double. See, for instance, the std::sin documentation.

Example: using angles of integer degrees

This example is taken from a test case in the library.

EXPECT_NEAR(sin(degrees(30)), 0.5, 1e-15);

arcsin, arccos, arctan

The standard inverse trigonometric functions, each returning a Quantity of Radians.

Each function corresponds to an STL function, except with arc replaced by a. For example, arcsin() corresponds to std::asin(), and so on. This library’s functions return Quantity<Radians, T> whenever the corresponding STL function would return a T.

Their names are slightly different than the corresponding STL functions, because in C++ it’s impermissible to have two functions whose signatures differ only in their return type.

Note

For more flexibility and robustness in dealing with arctangent use cases, see arctan2 below.

Signatures:

//
// arcsin()
//
template <typename T>
auto arcsin(T x);

//
// arccos()
//
template <typename T>
auto arccos(T x);

//
// arctan()
//
template <typename T>
auto arctan(T x);

Returns: radians(stl_func(x)), where stl_func is the corresponding STL function (that is, std::acos() for arccos(), and so on).

Example: getting the result in degrees

The fact that we return a Quantity, not a raw number, makes these functions far more flexible than their STL counterparts. For example, it’s easy to get the result in degrees using fluent, readable code:

// It's easy to express the answer in your preferred angular units.
const auto angle = arcsin(0.5).as(degrees);

// This test verifies that the result has the value we'd expect from basic trigonometry.
constexpr auto TOL = degrees(1e-12);
EXPECT_THAT(angle, IsNear(degrees(30.0), TOL));

arctan2

The two-argument arctangent function, which determines the in-plane angle based on the y and x coordinates of a point in the plane.

arctan2 corresponds to std::atan2, but returns a Quantity of Radians instead of a raw number.

This two-argument version is more robust than the single-argument version. arctan2(y, x) is equivalent to arctan(y / x), but it avoids the problems faced by the latter whenever x is zero.

Unlike the other inverse trigonometric functions, which only support raw numeric inputs, arctan2 also supports Quantity inputs. These inputs must have the same dimension, or else we will produce a hard compiler error. We convert them to their common unit, if necessary, before delegating to std::atan2.

Signatures:

// 1. Raw numeric inputs.
template <typename T, typename U>
auto arctan2(T y, U x);

// 2. Quantity inputs (must be same dimension).
template <typename U1, typename R1, typename U2, typename R2>
auto arctan2(Quantity<U1, R1> y, Quantity<U2, R2> x);

Returns: radians(std::atan2(y, x)). If the inputs are Quantity types, then instead of passing y and x, we first convert them to their common unit, and pass their values in that unit.

Rounding functions

round_as, round_in

Round a Quantity to the nearest integer value, using units that are specified explicitly at the callsite.

These functions are intended as unit-aware analogues to std::round. However, we firmly oppose the idea of providing the same (single-argument) API as std::round for Quantity, because a quantity has no single well-defined result: it depends on the units. (For example, std::round(height) is an intrinsically ill-formed concept: what is an “integer height”?)

As with everything else in the library, "as" is a word that means “return a Quantity”, and "in" is a word that means “return a raw number”.

Signatures:

//
// round_as(): return a Quantity
//

// 1. Unit-only version (including safety checks).  Typical callsites look like:
//    `round_as(units, quantity)`
template <typename RoundingUnits, typename U, typename R>
auto round_as(RoundingUnits rounding_units, Quantity<U, R> q);

// 2. Explicit-rep version (overriding; ignores safety checks).  Typical callsites look like:
//    `round_as<Type>(units, quantity)`
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto round_as(RoundingUnits rounding_units, Quantity<U, R> q);


//
// round_in(): return a raw number
//

// 1. Unit-only version (including safety checks).  Typical callsites look like:
//    `round_in(units, quantity)`
template <typename RoundingUnits, typename U, typename R>
auto round_in(RoundingUnits rounding_units, Quantity<U, R> q);

// 2. Explicit-rep version (overriding; ignores safety checks).  Typical callsites look like:
//    `round_in<Type>(units, quantity)`
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto round_in(RoundingUnits rounding_units, Quantity<U, R> q);

Returns: A Quantity, expressed in the requested units, which has an integer value in those units. We return the nearest such quantity to the original input quantity.

The policy for the rep is consistent with std::round. The output rep is the same as the return type of applying std::round to the input rep.

ceil_in, ceil_as

Round a Quantity up to the smallest integer value which is at least as big as that quantity, using units that are specified explicitly at the callsite.

These functions are intended as unit-aware analogues to std::ceil. However, we firmly oppose the idea of providing the same (single-argument) API as std::ceil for Quantity, because a quantity has no single well-defined result: it depends on the units. (For example, std::ceil(height) is an intrinsically ill-formed concept: what is an “integer height”?)

As with everything else in the library, "as" is a word that means “return a Quantity”, and "in" is a word that means “return a raw number”.

Signatures:

//
// ceil_as(): return a Quantity
//

// 1. Unit-only version (including safety checks).  Typical callsites look like:
//    `ceil_as(units, quantity)`
template <typename RoundingUnits, typename U, typename R>
auto ceil_as(RoundingUnits rounding_units, Quantity<U, R> q);

// 2. Explicit-rep version (overriding; ignores safety checks).  Typical callsites look like:
//    `ceil_as<Type>(units, quantity)`
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto ceil_as(RoundingUnits rounding_units, Quantity<U, R> q);


//
// ceil_in(): return a raw number
//

// 1. Unit-only version (including safety checks).  Typical callsites look like:
//    `ceil_in(units, quantity)`
template <typename RoundingUnits, typename U, typename R>
auto ceil_in(RoundingUnits rounding_units, Quantity<U, R> q);

// 2. Explicit-rep version (overriding; ignores safety checks).  Typical callsites look like:
//    `ceil_in<Type>(units, quantity)`
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto ceil_in(RoundingUnits rounding_units, Quantity<U, R> q);

Returns: A Quantity, expressed in the requested units, which has an integer value in those units. We return the smallest such quantity which is no smaller than the original input quantity.

The policy for the rep is consistent with std::ceil. The output rep is the same as the return type of applying std::ceil to the input rep.

floor_in, floor_as

Round a Quantity down to the largest integer value which is no bigger than that quantity, using the units that are specified explicitly at the callsite.

These functions are intended as unit-aware analogues to std::floor. However, we firmly oppose the idea of providing the same (single-argument) API as std::floor for Quantity, because a quantity has no single well-defined result: it depends on the units. (For example, std::floor(height) is an intrinsically ill-formed concept: what is an “integer height”?)

As with everything else in the library, "as" is a word that means “return a Quantity”, and "in" is a word that means “return a raw number”.

Signatures:

//
// floor_as(): return a Quantity
//

// 1. Unit-only version (including safety checks).  Typical callsites look like:
//    `floor_as(units, quantity)`
template <typename RoundingUnits, typename U, typename R>
auto floor_as(RoundingUnits rounding_units, Quantity<U, R> q);

// 2. Explicit-rep version (overriding; ignores safety checks).  Typical callsites look like:
//    `floor_as<Type>(units, quantity)`
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto floor_as(RoundingUnits rounding_units, Quantity<U, R> q);


//
// floor_in(): return a raw number
//

// 1. Unit-only version (including safety checks).  Typical callsites look like:
//    `floor_in(units, quantity)`
template <typename RoundingUnits, typename U, typename R>
auto floor_in(RoundingUnits rounding_units, Quantity<U, R> q);

// 2. Explicit-rep version (overriding; ignores safety checks).  Typical callsites look like:
//    `floor_in<Type>(units, quantity)`
template <typename OutputRep, typename RoundingUnits, typename U, typename R>
auto floor_in(RoundingUnits rounding_units, Quantity<U, R> q);

Returns: A Quantity, expressed in the requested units, which has an integer value in those units. We return the largest such quantity which is no larger than the original input quantity.

The policy for the rep is consistent with std::floor. The output rep is the same as the return type of applying std::floor to the input rep.

Inverse functions

inverse_as, inverse_in

A unit-aware computation of 1 / x.

“Unit-aware” means that you specify the desired target unit, and the library will figure out the appropriate units for representing the 1 in 1 / x. This intelligent choice enables it to automatically handle many conversions with integer types, without the computation ever needing to leave the integer domain.

Example: inverse of 250 \,\text{Hz}

The inverse of 250 \,\text{Hz} is 0.004 \,\text{s}. If we are using integer types, of course this would truncate down to 0. However, we could choose an alternate unit — say, \text{µs} — and we would get a “nicer” answer of 4000 \,\text{µs}.

Now for Au. If you request the inverse of hertz(250) in micro(seconds), the library will indeed return micro(seconds)(4000) — and it can perform this computation without ever leaving the integer domain! What happens under the hood is that the value of 250 is divided into a value of 1'000'000, not 1.

To see how we came up with this value, let’s re-express the fundamental equation. Let x be the original quantity, and y its inverse. We have:

\begin{align} y &= 1 / x \\ 1 &= xy \end{align}

This is a quantity equation. And since multiplication is an arbitrary-unit operation, we can reason independently about the unit and the value. The units on the right hand side are hertz * micro(seconds). This is a dimensionless unit with a magnitude of 10^{-6}. The units on the left hand side must match; therefore, we must express 1 in these units. When we do, we find its value in these units is 10^6 — or, in C++ code, 1'000'000.

That is how the library knows to divide 250 into 1'000'000 to get an answer of 4'000 — all without ever leaving the integer domain.

These functions include safety checks.

  • Quantity inputs with floating point rep are always allowed.
  • Quantity inputs with integral rep are allowed only when the product of the input and target units — which is necessarily dimensionless — has a magnitude not greater than 10^{-6}. We chose this threshold because it means that the round-trip double inversion will be lossless for any Quantity whose underlying value is not greater than 1'000.

As with all other library functions, you can circumvent the safety checks by using one of the “explicit-rep” versions, which are forcing in the same way as static_cast.

Signatures:

//
// inverse_as(): return a Quantity
//

// 1. Unit-only version (including safety checks).  Typical callsites look like:
//    `inverse_as(units, quantity)`
template <typename TargetUnits, typename U, typename R>
constexpr auto inverse_as(TargetUnits target_units, Quantity<U, R> q);

// 2. Explicit-rep version (overriding; ignores safety checks).  Typical callsites look like:
//    `inverse_as<Type>(units, quantity)`
template <typename TargetRep, typename TargetUnits, typename U, typename R>
constexpr auto inverse_as(TargetUnits target_units, Quantity<U, R> q);


//
// inverse_in(): return a raw number
//

// 1. Unit-only version (including safety checks).  Typical callsites look like:
//    `inverse_in(units, quantity)`
template <typename TargetUnits, typename U, typename R>
constexpr auto inverse_in(TargetUnits target_units, Quantity<U, R> q);

// 2. Explicit-rep version (overriding; ignores safety checks).  Typical callsites look like:
//    `inverse_in<Type>(units, quantity)`
template <typename TargetRep, typename TargetUnits, typename U, typename R>
constexpr auto inverse_in(TargetUnits target_units, Quantity<U, R> q);

Returns: The inverse of the input Quantity, expressed in the requested units.

Special values and language features

isnan

Indicates whether the underlying value of a Quantity is a NaN (“not-a-number”) value.

Signature:

template <typename U, typename R>
constexpr bool isnan(Quantity<U, R> q);

Returns: true if q is NaN; false otherwise.

std::numeric_limits specializations

Specializations for std::numeric_limits<Quantity<...>>.

For any Quantity<UnitT, Rep>, we simply delegate to std::numeric_limits<Rep> in the appropriate way, being careful to follow the rules for specializing, and adapt the result we get. For example, std::numeric_limits<Quantity<Hours, int>>::max() is exactly equal to hours(std::numeric_limits<int>::max()).

Warning

Be careful about using these limits in the presence of different Units of the same Dimension. Comparison operations will compile, but may not do what you expect. Consider this example:

seconds(0) < std::numeric_limits<Quantity<Hours, int>>::max()

Clearly, we’d want this to be true… but, in converting both sides to their common type, we’d end up multiplying the max-int on the right by 3600. What answer would we get for the comparison? It’s far from clear.

If you use these for a single Quantity type (i.e., same Unit and Rep), they should be just fine. (Then again—perhaps this is a good opportunity to ask yourself what you’re really trying to accomplish, and whether using the largest finite value of a particular type is the best way to achieve it!)

Miscellaneous

fmod

A unit-aware adaptation of std::fmod, giving the positive remainder of the division of the two inputs.

As with the integer modulus, we first express the inputs in their common unit.

Signature:

template <typename U1, typename R1, typename U2, typename R2>
auto fmod(Quantity<U1, R1> q1, Quantity<U2, R2> q2);

Returns: The remainder of q1 / q2, in the type Quantity<U, R>, where U is the common unit of U1 and U2, and R is the common type of R1 and R2.

remainder

A unit-aware adaptation of std::remainder, giving the zero-centered remainder of the division of the two inputs.

As with the integer modulus, we first express the inputs in their common unit.

Signature:

template <typename U1, typename R1, typename U2, typename R2>
auto remainder(Quantity<U1, R1> q1, Quantity<U2, R2> q2);

Returns: The remainder of q1 / q2, in the type Quantity<U, R>, where U is the common unit of U1 and U2, and R is the common type of R1 and R2.


  1. These signatures are for purposes of illustration, not completeness. In the real code, there are additional signatures covering the case of identical inputs. We need these in order to disambiguate our min or max implementations with respect to std::min and std::max