xo-unit: + constexpr implementation (runtime+compiletime)

This commit is contained in:
Roland Conybeare 2024-04-22 14:53:57 -04:00
commit 5bde1bfb94
12 changed files with 820 additions and 0 deletions

View file

@ -0,0 +1,94 @@
/** @file Quantity2.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "quantity2_concept.hpp"
#include "scaled_unit2.hpp"
#include "unit2.hpp"
namespace xo {
namespace unit {
/** @class quantity
* @brief represent a scalar quantity with attached units. enforce dimensional consistency.
*
* Constexpr implementation, but units are explicitly represented:
* sizeof(Quantity2) > sizeof(Repr)
*
* Explicit unit representation allows introducing units at runtime,
* for example in python bindings
*
* Require:
* - Repr supports numeric operations (+, -, *, /)
* - Repr supports conversion from double.
**/
template <typename Repr = double,
typename Int = std::int64_t>
class Quantity2 {
public:
using repr_type = Repr;
using unit_type = unit2<Int>;
using ratio_int_type = Int;
public:
constexpr Quantity2(Repr scale, const unit2<Int> & unit)
: scale_{scale}, unit_{unit} {}
constexpr const repr_type & scale() const { return scale_; }
constexpr const unit_type & unit() const { return unit_; }
constexpr Quantity2 unit_qty() const { return Quantity2(1, unit_); }
constexpr Quantity2 reciprocal() const { return Quantity2(1.0 / scale_, unit_.reciprocal()); }
template <typename OtherQuantity>
static constexpr
auto multiply(const Quantity2 & x, const OtherQuantity & y) {
using r_repr_type = std::common_type_t<typename Quantity2::repr_type,
typename OtherQuantity::repr_type>;
using r_int_type = std::common_type_t<typename Quantity2::ratio_int_type,
typename OtherQuantity::ratio_int_type>;
auto rr = detail::nu_product(x.unit(), y.unit());
r_repr_type r_scale = (::sqrt(rr.outer_scale_sq_)
* rr.outer_scale_exact_.template to<r_repr_type>()
* static_cast<r_repr_type>(x.scale())
* static_cast<r_repr_type>(y.scale()));
return Quantity2<r_repr_type, r_int_type>(r_scale,
rr.natural_unit_);
}
private:
/** @brief quantity represents this multiple of a unit amount **/
Repr scale_ = Repr{};
/** @brief unit for this quantity **/
unit2<Int> unit_;
}; /*Quantity2*/
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Repr = double,
typename Int = std::int64_t>
inline constexpr Quantity2<Repr, Int>
unit_qty(const scaled_unit2<Int> & u) {
return Quantity2<Repr, Int>(u.outer_scale_exact_.template to<double>() * ::sqrt(u.outer_scale_sq_),
u.natural_unit_);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity, typename OtherQuantity>
constexpr auto
operator* (const Quantity & x, const OtherQuantity & y)
{
return Quantity::multiply(x, y);
}
} /*namespace unit*/
} /*namespace xo*/
/** end Quantity2.hpp **/

View file

@ -0,0 +1,29 @@
/** @file Quantity2_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "Quantity2.hpp"
#include <iostream>
namespace xo {
namespace unit {
template <typename Repr = double,
typename Int = std::int64_t>
inline std::ostream &
operator<< (std::ostream & os,
const Quantity2<Repr, Int> & x)
{
os << "<qty"
<< xtag("scale", x.scale())
<< xtag("unit", x.unit())
<< ">";
return os;
}
} /*namespace unit*/
} /*namespace xo*/
/** end Quantity2_iostream.hpp **/

View file

@ -0,0 +1,162 @@
/** @file bpu_store.hpp **/
#pragma once
#include "native_bpu2.hpp"
namespace xo {
namespace unit {
/** @class basis_unit2_store
* @brief Store known basis units for runtime
**/
template <typename Tag>
struct basis_unit2_store {
basis_unit2_store() : bu_abbrev_vv_(static_cast<std::size_t>(dim::n_dim)) {
this->bu_establish_abbrev_for<dim::mass, 1, 1000000000>();
this->bu_establish_abbrev_for<dim::mass, 1, 1000000>();
this->bu_establish_abbrev_for<dim::mass, 1, 1000>();
this->bu_establish_abbrev_for<dim::mass, 1, 1>();
this->bu_establish_abbrev_for<dim::mass, 1000, 1>();
this->bu_establish_abbrev_for<dim::mass, 1000000, 1>();
this->bu_establish_abbrev_for<dim::mass, 1000000000, 1>();
this->bu_establish_abbrev_for<dim::distance, 1, 1000000000>();
this->bu_establish_abbrev_for<dim::distance, 1, 1000000>();
this->bu_establish_abbrev_for<dim::distance, 1, 1000>();
this->bu_establish_abbrev_for<dim::distance, 1, 1>();
this->bu_establish_abbrev_for<dim::distance, 1000, 1>();
this->bu_establish_abbrev_for<dim::time, 1, 1000000000>();
this->bu_establish_abbrev_for<dim::time, 1, 1000000>();
this->bu_establish_abbrev_for<dim::time, 1, 1000>();
this->bu_establish_abbrev_for<dim::time, 1, 1>();
this->bu_establish_abbrev_for<dim::time, 60, 1>();
this->bu_establish_abbrev_for<dim::time, 3600, 1>();
this->bu_establish_abbrev_for<dim::time, 24*3600, 1>();
this->bu_establish_abbrev_for<dim::time, 250*24*3600, 1>();
this->bu_establish_abbrev_for<dim::time, 360*24*3600, 1>();
this->bu_establish_abbrev_for<dim::time, 365*24*3600, 1>();
this->bu_establish_abbrev_for<dim::currency, 1, 1>();
this->bu_establish_abbrev_for<dim::price, 1, 1>();
}
/* e.g.
* [(1/1000000000, "nm"), (1/1000000, "um"), (1/1000, "mm"), (1/1, "m"), (1000/1, "km")]
*/
using native_scale_v = std::vector<std::pair<scalefactor_ratio_type, basis_unit2_abbrev_type>>;
/** @brief get basis-unit abbreviation at runtime **/
basis_unit2_abbrev_type bu_abbrev(dim basis_dim,
const scalefactor_ratio_type & scalefactor) const
{
const auto & bu_abbrev_v = bu_abbrev_vv_[static_cast<std::size_t>(basis_dim)];
std::size_t i_abbrev = bu_abbrev_lub_ix(basis_dim, scalefactor, bu_abbrev_v);
if ((i_abbrev < bu_abbrev_v.size())
&& (bu_abbrev_v[i_abbrev].first == scalefactor))
{
return bu_abbrev_v[i_abbrev].second;
} else {
return units::bu_fallback_abbrev(basis_dim, scalefactor);
}
}
/** @brief get basis-power-unit abbreviation at runtime **/
bpu2_abbrev_type bpu_abbrev(dim basis_dim,
const scalefactor_ratio_type & scalefactor,
const power_ratio_type & power)
{
return abbrev::bpu2_abbrev(basis_dim,
scalefactor,
power);
}
template <dim BasisDim, std::int64_t InnerScaleNum, std::int64_t InnerScaleDen>
void bu_establish_abbrev_for() {
this->bu_establish_abbrev
(basis_unit2(BasisDim,
scalefactor_ratio_type(InnerScaleNum, InnerScaleDen)),
units::scaled_native_unit2_abbrev_v<BasisDim, InnerScaleNum, InnerScaleDen>);
}
/** @brief establish abbreviation @p abbrev for basis unit @p bu
**/
void bu_establish_abbrev(const basis_unit2 & bu,
const basis_unit2_abbrev_type & abbrev) {
auto & bu_abbrev_v = bu_abbrev_vv_[static_cast<std::size_t>(bu.native_dim())];
std::int32_t i_abbrev = 0;
if (!bu_abbrev_v.empty()) {
i_abbrev = bu_abbrev_lub_ix(bu.native_dim(),
bu.scalefactor(),
bu_abbrev_v);
}
auto entry = std::make_pair(bu.scalefactor(), abbrev);
if ((i_abbrev < bu_abbrev_v.size())
&& (bu_abbrev_v[i_abbrev].first == bu.scalefactor()))
{
bu_abbrev_v[i_abbrev] = entry;
} else {
bu_abbrev_v.insert(bu_abbrev_v.begin() + i_abbrev, entry);
}
}
private:
/** @brief get least-upper-bound index position in bu_abbrev_v[]
*
* return value in [0, n] where n = bu_abbrev_v.size()
**/
static std::size_t bu_abbrev_lub_ix(dim basis_dim,
const scalefactor_ratio_type & scalefactor,
const native_scale_v & bu_abbrev_v)
{
std::size_t n = bu_abbrev_v.size();
if (n == 0)
return 0;
std::size_t lo = 0;
std::size_t hi = n-1;
if (scalefactor <= bu_abbrev_v[lo].first)
return 0;
auto cmp = (scalefactor <=> bu_abbrev_v[hi].first);
if (cmp > 0)
return n;
if (cmp == 0)
return hi;
while (hi-lo > 1) {
/* inv:
* bu_abbrev_v[lo].first < scalefactor <= bu_abbrev_v[hi].first
*/
std::size_t mid = lo + (hi - lo)/2;
if (scalefactor > bu_abbrev_v[mid].first)
lo = mid;
else
hi = mid;
}
return hi;
}
private:
/* bu_abbrev_v[dim] holds known units for native unit dim */
std::vector<native_scale_v> bu_abbrev_vv_;
};
} /*namespace unit*/
} /*namespace xo*/
/** end bpu_store.hpp **/

View file

@ -0,0 +1,21 @@
/** @file dim_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "dim_util.hpp"
#include <iostream>
namespace xo {
namespace unit {
inline std::ostream &
operator<<(std::ostream & os, dim x) {
os << dim2str(x);
return os;
}
} /*namespace unit*/
} /*namespace xo*/
/** end dim_iostream.hpp **/

View file

@ -0,0 +1,29 @@
/** @file native_bpu2_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "xo/ratio/ratio_iostream.hpp"
#include "native_bpu2.hpp"
#include <iostream>
namespace xo {
namespace unit {
template <typename Int>
inline std::ostream &
operator<<(std::ostream & os, const bpu2<Int> & x) {
os << "<bpu"
<< xtag("dim", x.native_dim())
<< xtag("mult", x.scalefactor())
<< xtag("pwr", x.power())
<< ">";
return os;
}
} /*namespace unit*/
} /*namespace xo*/
/** end native_bpu2_iostream.hpp **/

View file

@ -0,0 +1,229 @@
/** @file natural_unit.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "native_bpu2.hpp"
#include <cmath>
#include <cassert>
namespace xo {
namespace unit {
/** @class natural_unit
* @brief an array representing the cartesian product of distinct basis-power-units
*
* 1. Each bpu in the array represents a power of a basis dimension, e.g. "meter" or "second^2".
* 2. Each bpu in an array has a different dimension id.
* For example dim::time, if present, appears once.
* 3. Basis dimensions can appear in any order.
* Order used for constructing abbreviations: will get @c "kg.m" or @c "m.kg"
* depending on the orderin of @c dim::distance and @c dim::mass in @c bpu_v_
**/
template <typename Int>
class natural_unit {
public:
using ratio_int_type = Int;
public:
constexpr natural_unit() : n_bpu_{0} {}
constexpr std::size_t n_bpu() const { return n_bpu_; }
constexpr bpu2<Int> * bpu_v() const { return bpu_v_; }
constexpr void push_back(const bpu2<Int> & bpu) {
if (n_bpu_ < n_dim)
bpu_v_[n_bpu_++] = bpu;
}
constexpr bpu2<Int> & operator[](std::size_t i) { return bpu_v_[i]; }
constexpr const bpu2<Int> & operator[](std::size_t i) const { return bpu_v_[i]; }
private:
/** @brief the number of occupied slots in @c bpu_v_ **/
std::size_t n_bpu_;
/** @brief storage for basis power units **/
bpu2<Int> bpu_v_[n_dim];
};
namespace detail {
template <typename Int, typename... Ts>
constexpr void
push_bpu_array(natural_unit<Int> * p_target, Ts... args);
template <typename Int>
constexpr void
push_bpu_array(natural_unit<Int> * p_target) {}
template <typename Int, typename T0, typename... Ts>
constexpr void
push_bpu_array(natural_unit<Int> * p_target, T0 && bpu0, Ts... args) {
p_target->push_back(bpu0);
push_bpu_array(p_target, args...);
}
}
template <typename Int>
struct bpu_array_maker {
template <typename... Ts>
static constexpr natural_unit<Int>
make_bpu_array(Ts... args) {
natural_unit<Int> bpu_array;
detail::push_bpu_array(&bpu_array, args...);
return bpu_array;
}
};
namespace detail {
/**
* Given bpu ~ (b.u)^p:
* - b = bpu.scalefactor
* - u = bpu.native_dim
* - p = bpu.power
*
* want to rewrite in the form a'.(b'.u)^p
*
* Can compute a' exactly iff p is integral.
* In that case:
* (b.u)^p = ((b/b').b'.u)^p
* = (b/b')^p.(b'.u)^p
* = a'.(b'.u)^p with a' = (b/b')^p
*
* Can write p = p0 + q, with p0 = floor(p) integral, q = frac(p) in [0,1)
*
* Then
* (b/b')^p = (b/b')^p0 * (b/b')^q
*
* we'll compute:
* - (b/b')^p0 exactly (as a ratio)
* - (b/b')^q inexactly (as a double)
**/
template <typename Int>
struct outer_scalefactor_result {
constexpr outer_scalefactor_result(const ratio::ratio<Int> & outer_scale_exact,
double outer_scale_sq)
: outer_scale_exact_{outer_scale_exact},
outer_scale_sq_{outer_scale_sq} {}
/* (b/b')^p0 */
ratio::ratio<Int> outer_scale_exact_;
/* (b/b')^q -- until c++26 only allow q=0 or q=1/2 */
double outer_scale_sq_;
};
template <typename Int>
struct bpu2_rescale_result {
constexpr bpu2_rescale_result(const bpu2<Int> & bpu_rescaled,
const ratio::ratio<Int> & outer_scale_exact,
double outer_scale_sq)
: bpu_rescaled_{bpu_rescaled},
outer_scale_exact_{outer_scale_exact},
outer_scale_sq_{outer_scale_sq}
{}
/* (b'.u)^p */
bpu2<Int> bpu_rescaled_;
/* (b/b')^p0 */
ratio::ratio<Int> outer_scale_exact_;
/* [(b/b')^q]^2 -- until c++26 only allow q=0 or q=1/2 */
double outer_scale_sq_;
};
template <typename Int>
constexpr
bpu2_rescale_result<Int>
bpu2_rescale(const bpu2<Int> & orig,
const scalefactor_ratio_type & new_scalefactor)
{
ratio::ratio<Int> mult = (orig.scalefactor() / new_scalefactor);
/* inv: p_frac in [0, 1) */
auto p_frac = orig.power().frac();
/* asof c++26: replace mult_sq with ::pow(mult, p_frac) */
double mult_sq = std::numeric_limits<double>::quiet_NaN();
if (p_frac.den() == 1) {
mult_sq = 1.0;
} else if(p_frac.den() == 2) {
mult_sq = mult.template to<double>();
} else {
// remaining possibilities not supported until c++26
}
return bpu2_rescale_result<Int>(bpu2<Int>(orig.native_dim(),
new_scalefactor,
orig.power()),
mult.power(orig.power().floor()),
mult_sq);
}
template <typename Int>
constexpr
outer_scalefactor_result<Int>
bpu_product_inplace(bpu2<Int> * p_target_bpu,
const bpu2<Int> & rhs_bpu_orig)
{
assert(rhs_bpu_orig.native_dim() == p_target_bpu->native_dim());
bpu2_rescale_result<Int> rhs_bpu_rr = bpu2_rescale(rhs_bpu_orig,
p_target_bpu->scalefactor());
*p_target_bpu = bpu2<Int>(p_target_bpu->native_dim(),
p_target_bpu->scalefactor(),
p_target_bpu->power() + rhs_bpu_orig.power());
return outer_scalefactor_result<Int>(rhs_bpu_rr.outer_scale_exact_,
rhs_bpu_rr.outer_scale_sq_);
}
template <typename Int>
constexpr
outer_scalefactor_result<Int>
bpu_array_product_inplace(natural_unit<Int> * p_target,
const bpu2<Int> & bpu)
{
std::size_t i = 0;
for (; i < p_target->n_bpu(); ++i) {
if ((*p_target)[i].native_dim() == bpu.native_dim()) {
outer_scalefactor_result<Int> retval = bpu_product_inplace(&((*p_target)[i]), bpu);
/* TODO: strip 0 power */
return retval;
}
}
/* control here: i=p_target->n_bpu() */
p_target->push_back(bpu);
return outer_scalefactor_result<Int>
(ratio::ratio<Int>(1, 1) /*outer_scale_exact*/,
1.0 /*outer_scale_sq*/);
}
template <typename Int>
constexpr natural_unit<Int>
nu_reciprocal(const natural_unit<Int> & nu)
{
natural_unit<Int> retval;
for (std::size_t i = 0; i < nu.n_bpu(); ++i)
retval.push_back(nu[i].reciprocal());
return retval;
} /*nunit_reciprocal*/
} /*namespace detail*/
namespace nu2 {
constexpr auto nanogram = bpu_array_maker<std::int64_t>::make_bpu_array(make_unit_power<std::int64_t>(bu2::nanogram));
constexpr auto microgram = bpu_array_maker<std::int64_t>::make_bpu_array(make_unit_power<std::int64_t>(bu2::microgram));
}
} /*namespace unit*/
} /*namespace xo*/
/** end natural_unit.hpp **/

View file

@ -0,0 +1,28 @@
/** @file natural_unit_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "natural_unit.hpp"
#include <iostream>
namespace xo {
namespace unit {
template <typename Int>
inline std::ostream &
operator<<(std::ostream & os, const natural_unit<Int> & x) {
os << "<natural-unit [";
for (std::size_t i=0; i<x.n_bpu(); ++i) {
if (i > 0)
os << ", ";
os << x[i];
}
os << "]>";
return os;
}
} /*namespace unit*/
} /*namespace xo*/
/** end natural_unit_iostream.hpp **/

View file

@ -0,0 +1,28 @@
/** @file quantity2.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "bpu_array.hpp"
namespace xo {
namespace unit {
/** @class quantity
* @brief represent a scalar quantity with attached units. enforce dimensional consistency.
*
* Constexpr implementation, can compute units at compile time
**/
template <typename Repr = double, typename Int = std::int64_t>
class quantity2 {
public:
using repr_type = Repr;
private:
};
} /*namespace unit*/
} /*namespace xo*/
/** end quantity2.hpp **/

View file

@ -0,0 +1,23 @@
/** @file quantity2_concept.hpp **/
#pragma once
#include "unit_concept.hpp"
#include "numeric_concept.hpp"
namespace xo {
namespace unit {
template <typename Quantity>
concept quantity2_concept = requires(Quantity qty, typename Quantity::repr_type repr)
{
typename Quantity::unit_type;
typename Quantity::repr_type;
{ qty.scale() } -> std::same_as<typename Quantity::repr_type>;
//{ Quantity::unit_cstr() } -> std::same_as<char const *>;
//{ Quantity::unit_quantity() } -> std::same_as<Quantity>;
//{ Quantity::promote(repr) } -> std::same_as<Quantity>;
} && (true //unit_concept<typename Quantity::unit_type>
&& numeric_concept<typename Quantity::repr_type>);
} /*namespace unit*/
} /*namespace xo*/

View file

@ -0,0 +1,126 @@
/** @file scaled_unit2.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "natural_unit.hpp"
//#include <cstdint>
namespace xo {
namespace unit {
/** @class bpu2_array_rescale_result
* @brief Represents the product sqrt(outer_scale_sq) * outer_scale_exact * nat_unit
**/
template <typename Int>
struct scaled_unit2 {
constexpr scaled_unit2(const natural_unit<Int> & nat_unit,
ratio::ratio<Int> outer_scale_exact,
double outer_scale_sq)
: natural_unit_{nat_unit},
outer_scale_exact_{outer_scale_exact},
outer_scale_sq_{outer_scale_sq}
{}
constexpr scaled_unit2 reciprocal() const {
return scaled_unit2(nu_reciprocal(natural_unit_,
outer_scale_exact_.reciprocal(),
1.0 / outer_scale_sq_));
}
natural_unit<Int> natural_unit_;
ratio::ratio<Int> outer_scale_exact_;
double outer_scale_sq_;
};
namespace detail {
template <typename Int>
constexpr auto make_unit_rescale_result(const natural_unit<Int> & bpuv) {
return scaled_unit2<Int>(bpuv,
ratio::ratio<Int>(1, 1),
1.0);
}
}
namespace su2 {
constexpr auto nanogram = detail::make_unit_rescale_result<std::int64_t>(nu2::nanogram);
constexpr auto microgram = detail::make_unit_rescale_result<std::int64_t>(nu2::microgram);
}
namespace detail {
template <typename Int>
constexpr
detail::bpu2_rescale_result<Int>
bpu2_product(const bpu2<Int> & lhs_bpu,
const bpu2<Int> & rhs_bpu)
{
assert(lhs_bpu.native_dim() == rhs_bpu.native_dim());
bpu2<Int> prod_bpu = lhs_bpu;
auto rr = bpu_product_inplace(&prod_bpu, rhs_bpu);
return bpu2_rescale_result<Int>(prod_bpu, rr.outer_scale_exact_, rr.outer_scale_sq_);
}
template <typename Int>
constexpr
scaled_unit2<Int>
nu_product(const natural_unit<Int> & lhs_bpu_array,
const bpu2<Int> & rhs_bpu)
{
natural_unit<Int> prod = lhs_bpu_array;
auto rr = bpu_array_product_inplace(&prod, rhs_bpu);
return scaled_unit2<Int>(prod,
rr.outer_scale_exact_,
rr.outer_scale_sq_);
};
template <typename Int>
constexpr
scaled_unit2<Int>
nu_product(const natural_unit<Int> & lhs_bpu_array,
const natural_unit<Int> & rhs_bpu_array)
{
natural_unit<Int> prod = lhs_bpu_array;
/* accumulate product of scalefactors spun off by rescaling
* any basis-units in rhs_bpu_array that conflict with the same dimension
* in lh_bpu_array
*/
auto sfr = (detail::outer_scalefactor_result<Int>
(scalefactor_ratio_type(1, 1) /*outer_scale_exact*/,
1.0 /*outer_scale_sq*/));
for (std::size_t i = 0; i < rhs_bpu_array.n_bpu(); ++i) {
auto sfr2 = bpu_array_product_inplace(&prod, rhs_bpu_array[i]);
sfr.outer_scale_exact_ = sfr.outer_scale_exact_ * sfr2.outer_scale_exact_;
sfr.outer_scale_sq_ *= sfr2.outer_scale_sq_;
}
return scaled_unit2<Int>(prod,
sfr.outer_scale_exact_,
sfr.outer_scale_sq_);
}
}
template <typename Int>
inline constexpr scaled_unit2<Int>
operator* (const scaled_unit2<Int> & x_unit,
const scaled_unit2<Int> & y_unit)
{
auto rr = detail::nu_product(x_unit.natural_unit_,
y_unit.natural_unit_);
return (scaled_unit2<Int>
(rr.natural_unit_,
rr.outer_scale_exact_ * x_unit.outer_scale_exact_ * y_unit.outer_scale_exact_,
rr.outer_scale_sq_ * x_unit.outer_scale_sq_ * y_unit.outer_scale_sq_));
}
} /*namespace unit*/
} /*namespace xo*/
/** end scaled_unit2.hpp **/

View file

@ -0,0 +1,27 @@
/** @file scaled_unit_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "scaled_unit2.hpp"
#include <iostream>
namespace xo {
namespace unit {
template <typename Int>
inline std::ostream &
operator<<(std::ostream & os, const scaled_unit2<Int> & x) {
os << "<scaled-unit"
<< xtag("bpuv", x.natural_unit_)
<< xtag("outer_scale_exact", x.outer_scale_exact_)
<< xtag("outer_scale_sq", x.outer_scale_sq_)
<< ">";
return os;
};
} /*namespace unit*/
} /*namespace xo*/
/** end scaled_unit_iostream.hpp **/

24
include/xo/unit/unit2.hpp Normal file
View file

@ -0,0 +1,24 @@
/** @file unit2.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "natural_unit.hpp"
namespace xo {
namespace unit {
/** @class unit2
* @brief represent an arbitrary unit along with dimension details
*
* For example,
* kg.m.s^-2 or
* (kilogram * meter) / (second * second)
**/
template <typename Int>
using unit2 = natural_unit<Int>;
} /*namespace unit*/
} /*namespace xo*/
/** end unit2.hpp **/