Add 'xo-unit/' from commit 'b531e382c2'

git-subtree-dir: xo-unit
git-subtree-mainline: e9ee6992ca
git-subtree-split: b531e382c2
This commit is contained in:
Roland Conybeare 2025-05-10 21:29:43 -05:00
commit d1fa15f248
105 changed files with 11790 additions and 0 deletions

View file

@ -0,0 +1,240 @@
/** @file basis_unit.hpp **/
#pragma once
#include "native_unit.hpp"
#include "dimension.hpp"
//#include "basis_unit_abbrev.hpp"
#include "xo/ratio/ratio.hpp"
namespace xo {
namespace qty {
using bu_abbrev_type = flatstring<16>;
using scalefactor_ratio_type = xo::ratio::ratio<std::int64_t>;
using scalefactor2x_ratio_type = xo::ratio::ratio<__int128>;
/** @class basis_unit
* @brief A dimensionless multiple of a single natively-specified basis dimension
*
* For example "3600 minutes" or "1e-6 grams".
*
* Members are public so that a @c basis_unit instance qualifies as a 'structural type',
* and therefore may be used as a non-type template parameter.
**/
struct basis_unit {
public:
/** @defgroup basis-unit-constructors basis_unit constructors **/
///@{
/** sentinel basis unit: invalid dimension and zero scalefactor **/
constexpr basis_unit() = default;
/** basis unit representing multiple @p scalefactor of native dimension @p **/
constexpr basis_unit(dimension native_dim,
const scalefactor_ratio_type & scalefactor)
: native_dim_{native_dim},
scalefactor_{scalefactor}
{}
///@}
/** @defgroup basis-unit-access-methods basis_unit access methods **/
///@{
/** get @c native_dim member **/
constexpr dimension native_dim() const { return native_dim_; }
/** get @c scalefactor member **/
constexpr const scalefactor_ratio_type & scalefactor() const { return scalefactor_; }
///@}
public: /* public so instance can be a non-type template parameter (a structural type) */
/** @defgroup basis-unit-instance-vars basis_unit instance variables **/
///@{
/** @brief identifies a native unit, e.g. time **/
dimension native_dim_ = dimension::invalid;
/** @brief this unit defined as multiple scalefactor times native unit **/
scalefactor_ratio_type scalefactor_ = {};
///@}
};
/** @defgroup basis-unit-comparison-support basis_unit comparisons **/
///@{
/** @c true iff basis units are equal;
* both native dimension and scalefactor must be equal
**/
inline constexpr bool
operator==(const basis_unit & x, const basis_unit & y)
{
return ((x.native_dim_ == y.native_dim_)
&& (x.scalefactor_ == y.scalefactor_));
}
/** @c true iff bass units are not equal **/
inline constexpr bool
operator!=(const basis_unit & x, const basis_unit & y)
{
return ((x.native_dim_ != y.native_dim_)
|| (x.scalefactor_ != y.scalefactor_));
}
///@}
namespace detail {
/** @brief namespace for basis-unit constants and helpers **/
namespace bu {
// ----- mass -----
constexpr basis_unit mass_unit(std::int64_t num, std::int64_t den) {
return basis_unit(dimension::mass, scalefactor_ratio_type(num, den));
}
/** @defgroup basis-unit-mass-units basis_unit mass units **/
///@{
/** basis unit of 10^-12 grams **/
constexpr basis_unit picogram = mass_unit( 1, 1000000000000);
/** basis unit of 10^-9 grams **/
constexpr basis_unit nanogram = mass_unit( 1, 1000000000);
/** basis unit of 10^-6 grams **/
constexpr basis_unit microgram = mass_unit( 1, 1000000);
/** basis unit of 10^-3 grams **/
constexpr basis_unit milligram = mass_unit( 1, 1000);
/** basis unit of 1 gram **/
constexpr basis_unit gram = mass_unit( 1, 1);
/** basis unit of 10^3 grams **/
constexpr basis_unit kilogram = mass_unit( 1000, 1);
/** basis unit of 10^6 grams = 10^3 kilograms **/
constexpr basis_unit tonne = mass_unit( 1000000, 1);
/** basis unit of 10^9 grams = 10^6 kilograms = 10^3 tonnes **/
constexpr basis_unit kilotonne = mass_unit( 1000000000, 1);
/** basis unit of 10^12 grams = 10^9 kilograms = 10^6 tonnes **/
constexpr basis_unit megatonne = mass_unit( 1000000000000, 1);
/** basis unit of 10^15 grams = 10^12 kilograms = 10^9 tonnes **/
constexpr basis_unit gigatonne = mass_unit(1000000000000000, 1);
///@}
// ----- distance -----
constexpr basis_unit distance_unit(std::int64_t num, std::int64_t den) {
return basis_unit(dimension::distance, scalefactor_ratio_type(num, den));
}
/** @defgroup basis-unit-distance-units basis_unit distance units **/
///@{
/* US spelling */
/** basis unit of 10^-12 meters **/
constexpr basis_unit picometer = distance_unit( 1, 1000000000000);
/** basis unit of 10^-9 meters **/
constexpr basis_unit nanometer = distance_unit( 1, 1000000000);
/** basis unit of 10^-6 meters **/
constexpr basis_unit micrometer = distance_unit( 1, 1000000);
/** basis unit of 10^-3 meters **/
constexpr basis_unit millimeter = distance_unit( 1, 1000);
/** basis unit of 1 meter **/
constexpr basis_unit meter = distance_unit( 1, 1);
/** basis unit of 10^3 meters **/
constexpr basis_unit kilometer = distance_unit( 1000, 1);
/** basis unit of 10^6 meters (for form's sake -- not commonly used) **/
constexpr basis_unit megameter = distance_unit( 1000000, 1);
/** basis unit of 10^9 meters (for form's sake -- not commonly used) **/
constexpr basis_unit gigameter = distance_unit( 1000000000, 1);
/** basis unit of 1 light-second = distance light travels in a vacuum in 1 second **/
constexpr basis_unit lightsecond = distance_unit( 299792458, 1);
/** basis unit of 1 astronomical unit, representing approximate radius of earth orbit. **/
constexpr basis_unit astronomicalunit = distance_unit( 149597870700, 1);
/* Int'l spelling */
/** international spelling for picometer **/
constexpr basis_unit picometre = picometer;
/** international spelling for nanometer **/
constexpr basis_unit nanometre = nanometer;
/** international spelling for micrometer **/
constexpr basis_unit micrometre = micrometer;
/** international spelling for millimeter **/
constexpr basis_unit millimetre = millimeter;
/** international spelling for meter **/
constexpr basis_unit metre = meter;
/** international spelling for kilometer **/
constexpr basis_unit kilometre = kilometer;
/** international spelling for megameter **/
constexpr basis_unit megametre = megameter;
/** international spelling for gigameter **/
constexpr basis_unit gigametre = gigameter;
/** @brief basis-unit representing 1 inch; defined as exactly 1/12 feet **/
constexpr basis_unit inch = distance_unit( 3048, 120000);
/** @brief basis-unit representing 1 foot; defined as exactly 0.3048 meters **/
constexpr basis_unit foot = distance_unit( 3048, 10000);
/** @brief basis-unit representing 1 yard; defined as exactly 3 feet **/
constexpr basis_unit yard = distance_unit( 3*3048, 10000);
/** @brief basis-unit representing 1 mile; defined as exactly 1760 yards = 5280 feet **/
constexpr basis_unit mile = distance_unit( 5280*3048, 10000);
///@}
// ----- time -----
constexpr basis_unit time_unit(std::int64_t num, std::int64_t den) {
return basis_unit(dimension::time, scalefactor_ratio_type(num, den));
}
/** @defgroup basis-unit-time-units basis_unit time units **/
///@{
/** basis unit of 10^-12 seconds **/
constexpr basis_unit picosecond = time_unit( 1, 1000000000000);
/** basis unit of 10^-9 seconds **/
constexpr basis_unit nanosecond = time_unit( 1, 1000000000);
/** basis unit of 10^-6 seconds **/
constexpr basis_unit microsecond = time_unit( 1, 1000000);
/** basis unit of 10^-3 seconds **/
constexpr basis_unit millisecond = time_unit( 1, 1000);
/** basis unit of 1 second **/
constexpr basis_unit second = time_unit( 1, 1);
/** basis unit of 1 minute = 60 seconds **/
constexpr basis_unit minute = time_unit( 60, 1);
/** basis unit of 1 hour = 3600 seconds **/
constexpr basis_unit hour = time_unit( 3600, 1);
/** basis unit of 1 day = exactly 24 hours **/
constexpr basis_unit day = time_unit( 24*3600, 1);
/** basis unit of 1 week = exactly 7 days **/
constexpr basis_unit week = time_unit( 7*24*3600, 1);
/** basis unit of 1 month = exactly 30 days **/
constexpr basis_unit month = time_unit( 30*24*3600, 1);
/** basis unit of 1 year, defined as 365.25 days **/
constexpr basis_unit year = time_unit( (365*24+6)*3600, 1);
/* alt conventions used in finance */
/** basis unit of 1 year365 = exactly 365 days **/
constexpr basis_unit year365 = time_unit( 365*24*3600, 1);
/** basis unit of 1 year360 = exactly 360 days **/
constexpr basis_unit year360 = time_unit( 360*24*3600, 1);
/** basis unit of 1 year250 = exactly 250 days.
* Approximate number of business days in one year
**/
constexpr basis_unit year250 = time_unit( 250*24*3600, 1);
//constexpr basis_unit century = time_unit( 100L*(365*24+6)*3600, 1);
//constexpr basis_unit millenium = time_unit(1000L*(365*24+6)*3600, 1);
///@}
// ----- currency -----
/** @defgroup basis-unit-misc-units basis_unit miscellaneous units**/
///@{
constexpr basis_unit currency_unit(std::int64_t num, std::int64_t den) {
return basis_unit(dimension::currency, scalefactor_ratio_type(num, den));
}
/** pseudounit -- placeholder for any actual currency amount **/
constexpr basis_unit currency = currency_unit(1, 1);
// ----- price -----
constexpr basis_unit price_unit(std::int64_t num, std::int64_t den) {
return basis_unit(dimension::price, scalefactor_ratio_type(num, den));
}
/** psuedounit -- context-dependent interpretation for a screen price **/
constexpr basis_unit price = price_unit(1, 1);
///@}
} /*namespace bu*/
} /*namespace detail*/
} /*namespace qty*/
} /*namespace xo*/
/** end basis_unit.hpp **/

View file

@ -0,0 +1,188 @@
/** @file bpu.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "basis_unit.hpp"
#include "bu_store.hpp"
namespace xo {
namespace qty {
namespace abbrev {
/** fixed-size string representation for exponent of a basis-power-unit **/
using power_abbrev_type = flatstring<16>;
/** @defgroup bpu-abbrev-helpers bpu abbrev helpers **/
///@{
/** @brief construct prefix string for unit exponent
*
* Auxiliary function for @ref bpu_abbrev
**/
constexpr power_abbrev_type
flatstring_from_exponent(const power_ratio_type & power)
{
if (power.den() == 1) {
if (power.num() == 1) {
/* for no exponent annotation for power ^1 */
return power_abbrev_type::from_chars("");
} else {
/* e.g. "^-1", "^2" */
return (power_abbrev_type::from_flatstring
(flatstring_concat(flatstring("^"),
power_abbrev_type::from_int(power.num()))));
}
} else {
/* e.g. "^1/2", "^-1/2" */
return (power_abbrev_type::from_flatstring
(flatstring_concat(flatstring("^"),
power.to_str<power_abbrev_type::fixed_capacity>())));
}
}
/** construct suffix abbreviation for a basis-power-unit **/
static constexpr bpu_abbrev_type
bpu_abbrev(dim native_dim,
const scalefactor_ratio_type & scalefactor,
const power_ratio_type & power)
{
return (bpu_abbrev_type::from_flatstring
(flatstring_concat
(bu_abbrev(basis_unit(native_dim, scalefactor)),
flatstring_from_exponent(power))));
}
///@}
}
/** @class bpu
*
* @brief represent product of a compile-time scale-factor with a rational power of a native unit
**/
template<typename Int>
struct bpu {
public:
using ratio_int_type = Int;
public:
/** @defgroup bpu-ctors bpu constructors **/
///@{
/** default constructor. creates dimensionless bpu,
* representing zero'th power of sentinel basis unit
**/
constexpr bpu() = default;
/** construct @c bpu representing exponent @p power of basis unit @p bu **/
constexpr bpu(const basis_unit & bu,
const power_ratio_type & power)
: bu_{bu},
power_{power}
{}
/** construct @c bpu representing exponent @p power of @c basis_unit(native_dim,scalefactor) **/
constexpr bpu(dim native_dim,
const scalefactor_ratio_type & scalefactor,
const power_ratio_type & power)
: bu_(native_dim, scalefactor),
power_{power}
{}
/** construct bpu representing basis unit @p bu, i.e. with unit exponent **/
static constexpr bpu<Int> unit_power(const basis_unit & bu) {
return bpu<Int>(bu, power_ratio_type(1,1));
}
///@}
/** @defgroup bpu-access-methods bpu access methods **/
///@{
/** @brief report this bpu's @ref basis_unit, e.g. @c detail::bu::minute **/
constexpr const basis_unit & bu() const { return bu_; }
/** @brief report this bpu's @ref dimension, e.g. @c dimension::time **/
constexpr dimension native_dim() const { return bu_.native_dim(); }
/** @brief report this bpu's scale factor, e.g. @c 60/1 for @c detail::bu::minute **/
constexpr const scalefactor_ratio_type & scalefactor() const { return bu_.scalefactor(); }
/** @brief report this bpu's exponent, e.g. @c 3/1 for bpu representing cubic meters **/
constexpr const power_ratio_type & power() const { return power_; }
///@}
/** @defgroup bpu-methods **/
///@{
/** abbreviation for this dimension
*
* @code
* bpu<int64_t>(dim::time,
* scalefactor_ratio_type(60,1),
* power_ratio_type(-2,1)).abbrev() => "min^-2"
* @endcode
**/
constexpr bpu_abbrev_type abbrev() const
{
return abbrev::bpu_abbrev(bu_.native_dim_,
bu_.scalefactor_,
power_);
}
/** for bpu @c x, @c x.reciprocal() represents dimension of @c 1/x
*
* Example:
* @code
* constexpr auto x = bpu<int64_t>(dim::time,
* scalefactor_ratio_type(60,1),
* power_ratio_type(1));
* x.abbrev() => "min"
* x.reciprocal().abbrev() => "min^-1"
* @endcode
**/
constexpr bpu<Int> reciprocal() const {
return bpu<Int>(bu_.native_dim(), bu_.scalefactor(), power_.negate());
}
/** construct bpu representing the same unit, but using @c Int2 to represent exponenct **/
template <typename Int2>
constexpr bpu<Int2> to_repr() const {
return bpu<Int2>(this->native_dim(),
this->scalefactor(),
ratio::ratio<Int2>(power_.num(), power_.den()));
}
///@}
public: /* need public members so that a basis_unit instance can be a non-type template parameter (a structural type) */
/** @defgroup bpu-instance-vars **/
///@{
/** this @c bpu represent a power of basis unit @c bu.
*
* Public to avoid disqualifying @c bpu as a 'structural type'.
**/
struct basis_unit bu_;
/** this unit represents basis dimension (bu) taken to this power
*
* Public to avoid disqualifying @c bpu as a 'structural type'.
**/
power_ratio_type power_ = {};
///@}
};
/** @defgroup bpu-comparison **/
///@{
/** @brief compare bpus @p x and @p y for equality
*
* Equality requires that both basis unit and power are equal
**/
template <typename Int>
inline constexpr bool
operator==(const bpu<Int> & x, const bpu<Int> & y) {
return ((x.bu() == y.bu())
&& (x.power_ == y.power_));
}
/** @brief compare bpus @p x and @p y for inequality **/
template <typename Int>
inline constexpr bool
operator!=(const bpu<Int> & x, const bpu<Int> & y) {
return ((x.bu() != y.bu())
|| (x.power_ != y.power_));
}
///@}
} /*namespace qty*/
} /*namespace xo*/
/** end bpu.hpp **/

View file

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

View file

@ -0,0 +1,279 @@
/** @file bu_store.hpp **/
#pragma once
//#include "bpu.hpp"
#include "basis_unit.hpp"
#include "xo/ratio/ratio.hpp"
#include <array>
#include <cstdint>
namespace xo {
namespace qty {
using bpu_abbrev_type = flatstring<24>;
using power_ratio_type = xo::ratio::ratio<std::int64_t>;
namespace detail {
/** @class bu_dim_store
* @brief store basis-unit abbreviations for a particular dimension
**/
struct bu_dim_store {
/** max number of basis-units per dimension **/
static constexpr std::size_t max_bu_per_dim = 25;
/** @defgroup bu-dim-store-type-traits bu-dim-store type traits **/
///@{
using entry_type = std::pair<scalefactor2x_ratio_type, bu_abbrev_type>;
/* e.g.
* [(1/1000000000, "nm"), (1/1000000, "um"), (1/1000, "mm"), (1/1, "m"), (1000/1, "km")]
*/
using native_scale_v = std::array<entry_type, max_bu_per_dim>;
///@}
public:
constexpr bu_dim_store() = default;
constexpr bool empty() const { return n_bu_ == 0; }
constexpr std::size_t size() const { return n_bu_; }
constexpr const entry_type & operator[](std::size_t i) const { return bu_abbrev_v_[i]; }
/** @brief get least-upper-bound index position in bu_abbrev_v[]
*
* return value in [0, n] where n = .size()
**/
constexpr std::size_t abbrev_lub_ix(const scalefactor_ratio_type & scalefactor) const
{
if (n_bu_ == 0)
return 0;
std::size_t lo = 0;
std::size_t hi = n_bu_-1;
if (scalefactor <= bu_abbrev_v_[lo].first)
return 0;
auto cmp = (scalefactor <=> bu_abbrev_v_[hi].first);
if (cmp > 0)
return n_bu_;
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;
}
constexpr void insert_aux(std::size_t ix,
const entry_type & entry)
{
if (n_bu_ >= max_bu_per_dim)
return;
++n_bu_;
for (std::size_t dest_ix = n_bu_; dest_ix > ix; --dest_ix)
bu_abbrev_v_[dest_ix] = bu_abbrev_v_[dest_ix - 1];
bu_abbrev_v_[ix] = entry;
}
/** @brief establish abbreviation @p abbrev for basis unit @p bu
**/
constexpr void bu_establish_abbrev(const scalefactor_ratio_type & scalefactor,
const bu_abbrev_type & abbrev)
{
std::int32_t i_abbrev = this->abbrev_lub_ix(scalefactor);
auto entry = std::make_pair(scalefactor, abbrev);
if ((i_abbrev < bu_abbrev_v_.size())
&& (bu_abbrev_v_[i_abbrev].first == scalefactor))
{
bu_abbrev_v_[i_abbrev] = entry;
} else {
this->insert_aux(i_abbrev, entry);
}
}
public:
/** @defgroup bu-dim-store-instance-vars bu-dim-store instance vars **/
///@{
std::size_t n_bu_ = 0;
std::array<entry_type, max_bu_per_dim> bu_abbrev_v_;
///@}
}; /*bu_dim_store*/
/** @class bu_store
* @brief associate basis units with abbreviations
**/
struct bu_store {
/** @defgroup bu-store-constructors bu-store constructors **/
///@{
/** construct canonical instance containing all known basis units **/
constexpr bu_store() {
// ----- mass -----
this->bu_establish_abbrev(detail::bu::picogram, bu_abbrev_type::from_chars("pg"));
this->bu_establish_abbrev(detail::bu::nanogram, bu_abbrev_type::from_chars("ng"));
this->bu_establish_abbrev(detail::bu::microgram, bu_abbrev_type::from_chars("ug"));
this->bu_establish_abbrev(detail::bu::milligram, bu_abbrev_type::from_chars("mg"));
this->bu_establish_abbrev(detail::bu::gram, bu_abbrev_type::from_chars("g"));
this->bu_establish_abbrev(detail::bu::kilogram, bu_abbrev_type::from_chars("kg"));
this->bu_establish_abbrev(detail::bu::tonne, bu_abbrev_type::from_chars("t"));
this->bu_establish_abbrev(detail::bu::kilotonne, bu_abbrev_type::from_chars("kt"));
this->bu_establish_abbrev(detail::bu::megatonne, bu_abbrev_type::from_chars("Mt"));
this->bu_establish_abbrev(detail::bu::gigatonne, bu_abbrev_type::from_chars("Gt"));
// ----- distance -----
this->bu_establish_abbrev(detail::bu::picometer, bu_abbrev_type::from_chars("pm"));
this->bu_establish_abbrev(detail::bu::nanometer, bu_abbrev_type::from_chars("nm"));
this->bu_establish_abbrev(detail::bu::micrometer, bu_abbrev_type::from_chars("um"));
this->bu_establish_abbrev(detail::bu::millimeter, bu_abbrev_type::from_chars("mm"));
this->bu_establish_abbrev(detail::bu::meter, bu_abbrev_type::from_chars("m"));
this->bu_establish_abbrev(detail::bu::kilometer, bu_abbrev_type::from_chars("km"));
this->bu_establish_abbrev(detail::bu::megameter, bu_abbrev_type::from_chars("Mm"));
this->bu_establish_abbrev(detail::bu::gigameter, bu_abbrev_type::from_chars("Gm"));
this->bu_establish_abbrev(detail::bu::lightsecond, bu_abbrev_type::from_chars("lsec"));
this->bu_establish_abbrev(detail::bu::astronomicalunit, bu_abbrev_type::from_chars("AU"));
this->bu_establish_abbrev(detail::bu::inch, bu_abbrev_type::from_chars("in"));
this->bu_establish_abbrev(detail::bu::foot, bu_abbrev_type::from_chars("ft"));
this->bu_establish_abbrev(detail::bu::yard, bu_abbrev_type::from_chars("yd"));
this->bu_establish_abbrev(detail::bu::mile, bu_abbrev_type::from_chars("mi"));
// ----- time -----
this->bu_establish_abbrev(detail::bu::picosecond, bu_abbrev_type::from_chars("ps"));
this->bu_establish_abbrev(detail::bu::nanosecond, bu_abbrev_type::from_chars("ns"));
this->bu_establish_abbrev(detail::bu::microsecond, bu_abbrev_type::from_chars("us"));
this->bu_establish_abbrev(detail::bu::millisecond, bu_abbrev_type::from_chars("ms"));
this->bu_establish_abbrev(detail::bu::second, bu_abbrev_type::from_chars("s"));
this->bu_establish_abbrev(detail::bu::minute, bu_abbrev_type::from_chars("min"));
this->bu_establish_abbrev(detail::bu::hour, bu_abbrev_type::from_chars("hr"));
this->bu_establish_abbrev(detail::bu::day, bu_abbrev_type::from_chars("dy"));
this->bu_establish_abbrev(detail::bu::week, bu_abbrev_type::from_chars("wk"));
this->bu_establish_abbrev(detail::bu::month, bu_abbrev_type::from_chars("mo"));
this->bu_establish_abbrev(detail::bu::year250, bu_abbrev_type::from_chars("yr250"));
this->bu_establish_abbrev(detail::bu::year, bu_abbrev_type::from_chars("yr"));
this->bu_establish_abbrev(detail::bu::year360, bu_abbrev_type::from_chars("yr360"));
this->bu_establish_abbrev(detail::bu::year365, bu_abbrev_type::from_chars("yr365"));
// ----- misc (currency, price) -----
this->bu_establish_abbrev(detail::bu::currency, bu_abbrev_type::from_chars("ccy"));
this->bu_establish_abbrev(detail::bu::price, bu_abbrev_type::from_chars("px"));
}
///@}
/** @defgroup bu-store-implementation-methods **/
///@{
/** report fallback abbreviation for a basis unit.
*
* Typically unused. Will be invoked only if a basis unit exists for which
* @ref bu_store::bu_establish_abbrev was not called by bu_store's constructor.
*
* Tries to produce something unambiguous,
* while still supplying enough information
* to indicate what's needed to resolve the problem.
*
* Example
* @code
* bu_store.bu_fallback_abbrev(dim::mass, scalefactor_ratio_type(1234,1))
* => "1234g"
* @endcode
**/
static constexpr bu_abbrev_type
bu_fallback_abbrev(dim basis_dim,
const scalefactor_ratio_type & scalefactor)
{
return (bu_abbrev_type::from_flatstring
(flatstring_concat
(scalefactor.to_str<bu_abbrev_type::fixed_capacity>(),
native_unit2_v[static_cast<std::uint32_t>(basis_dim)].abbrev_str())));
}
///@}
/** @defgroup bu-store-access-methods **/
///@{
/** @brief get basis-unit abbreviation at runtime **/
constexpr bu_abbrev_type bu_abbrev(const basis_unit & bu) const
{
const auto & bu_abbrev_v = bu_abbrev_vv_[static_cast<std::size_t>(bu.native_dim())];
std::size_t i_abbrev = bu_abbrev_v.abbrev_lub_ix(bu.scalefactor());
if ((i_abbrev < bu_abbrev_v.size())
&& (bu_abbrev_v[i_abbrev].first == bu.scalefactor()))
{
return bu_abbrev_v[i_abbrev].second;
} else {
return bu_fallback_abbrev(bu.native_dim(), bu.scalefactor());
}
}
///@}
/** @addtogroup bu-store-implementation-methods **/
///@{
/** associate abbreviation @p abbrev with basis unit @p bu
*
* Example:
* @code
* // femtograms
* bu_store.bu_establish_abbrev(detail::bu::mass_unit(1, 1000000000000000), "fg")
* @endcode
**/
constexpr void bu_establish_abbrev(const basis_unit & bu,
const bu_abbrev_type & abbrev) {
auto & dim_store = bu_abbrev_vv_[static_cast<std::size_t>(bu.native_dim_)];
dim_store.bu_establish_abbrev(bu.scalefactor_, abbrev);
}
///@}
public: /* ntoe: public members required so bu_dim_store can be a structural type */
/** @defgroup bu-store-instance-vars **/
///@{
/** bu-store contents, indexed by native dimension **/
std::array<bu_dim_store, n_dim> bu_abbrev_vv_;
///@}
};
} /*namespace detail*/
/** @brief global abbreviation store.
*
* @note
* Extending the contents of this store at runtime is not supported,
* in favor of preserving constexpr abbreviations.
**/
static constexpr detail::bu_store bu_abbrev_store;
/** @brief get abbreviation for basis-unit @p bu **/
constexpr bu_abbrev_type
bu_abbrev(const basis_unit & bu)
{
return bu_abbrev_store.bu_abbrev(bu);
}
} /*namespace qty*/
} /*namespace xo*/
/** end bu_store.hpp **/

View file

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

View file

@ -0,0 +1,65 @@
/* @file dimension.hpp */
#pragma once
#include <cstdint>
namespace xo {
namespace qty {
/** @enum dimension
* @brief represent an abstract dimension.
*
* *xo-unit* units are expressed as a cartesian product
* of powers of these dimensions.
**/
enum class dimension {
/** sentinel value. not a dimension **/
invalid = -1,
/** weight. native unit = 1 gram **/
mass,
/** distance. native unit = 1 meter **/
distance,
/** time. native unit = 1 second **/
time,
/** a currency amount. native unit depends on actual currency.
* For USD: one US dollar.
*
* NOTE: multicurrency work not supported by *xo-unit*.
* - (1usd + 1eur) is well-defined.
* - (1sec + 1m) is not.
**/
currency,
/** A screen price.
* The interpretation of prices is highly context dependent;
* expect useful to bucket separately from currenty amounts.
**/
price,
/** not a dimension. comes last, counts entries **/
n_dim
};
using dim = dimension;
/** @brief string value for a dimension enum **/
inline const char *
dim2str(dimension x)
{
switch(x) {
case dimension::mass: return "mass";
case dimension::distance: return "distance";
case dimension::time: return "time";
case dimension::currency: return "currency";
case dimension::price: return "price";
default: break;
}
return "?dim";
}
/** @brief number of built-in dimensions, convenient for array sizing **/
static constexpr std::size_t n_dim = static_cast<std::size_t>(dimension::n_dim);
} /*namespace qty*/
} /*namespace xo*/
/* end dimension.hpp */

View file

@ -0,0 +1,49 @@
/** @file native_unit.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "dimension.hpp"
#include "xo/flatstring/flatstring.hpp"
namespace xo {
namespace qty {
using native_unit2_abbrev_type = flatstring<8>;
/** @class native_unit
*
* @brief Represent a native (built-in) unit.
*
* A basis_unit is expressed as a multiple of a native_unit
*
**/
struct native_unit {
public:
constexpr native_unit(dimension native_dim,
const native_unit2_abbrev_type & abbrev_str)
: native_dim_{native_dim},
abbrev_str_{abbrev_str}
{}
constexpr dimension native_dim() const { return native_dim_; }
constexpr const native_unit2_abbrev_type & abbrev_str() const { return abbrev_str_; }
private:
dimension native_dim_;
native_unit2_abbrev_type abbrev_str_;
};
static constexpr native_unit native_unit2_v[n_dim] = {
native_unit(dimension::mass, native_unit2_abbrev_type::from_chars("g")),
native_unit(dimension::distance, native_unit2_abbrev_type::from_chars("m")),
native_unit(dimension::time, native_unit2_abbrev_type::from_chars("s")),
native_unit(dimension::currency, native_unit2_abbrev_type::from_chars("ccy")),
native_unit(dimension::price, native_unit2_abbrev_type::from_chars("px")),
};
} /*namespace qty*/
} /*namespace xo*/
/** end native_unit.hpp **/

View file

@ -0,0 +1,527 @@
/** @file natural_unit.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "bpu.hpp"
#include <cmath>
#include <cassert>
namespace xo {
namespace qty {
using nu_abbrev_type = flatstring<32>;
template <typename Int>
class natural_unit;
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 nu_maker {
template <typename... Ts>
static constexpr natural_unit<Int>
make_nu(Ts... args) {
natural_unit<Int> bpu_array;
detail::push_bpu_array(&bpu_array, args...);
return bpu_array;
}
};
}
/** @class natural_unit
* @brief an array representing the cartesian product of distinct basis-power-units
*
* 1. Quantities are represented as a multiple of a natural unit
* 2. Each bpu in the array represents a power of a basis dimension, e.g. "meter" or "second^2".
* 3. Each bpu in an array has a different dimension id.
* For example @c dim::time, if present, appears once.
* 4. Basis dimensions can appear in any order.
* Order used for constructing abbreviations: will get @c "kg.m" or @c "m.kg"
* depending on the ordering of @c dim::distance and @c dim::mass in @c bpu_v_
*
* @c Int supplies representation for numerator and denominator in basis-unit scale factors.
**/
template <typename Int>
class natural_unit {
public:
/** @defgroup natural-unit-type-traits natural unit type traits **/
///@{
/** @brief representation for numerator and denominator of scalefactor ratios **/
using ratio_int_type = Int;
///@}
public:
/** @addtogroup natural-unit-ctors **/
///@{
/** construct dimensionless unit **/
constexpr natural_unit() : n_bpu_{0} {}
/** construct unit representing basis unit @p bu with exponent @p power **/
static constexpr natural_unit from_bu(basis_unit bu,
power_ratio_type power = power_ratio_type(1)) {
return detail::nu_maker<Int>::make_nu(bpu<Int>(bu, power));
}
///@}
/** @addtogroup natural-unit-access-methods **/
///@{
/** always true. Provided for symmetry with @c xo::qty::scaled_unit::is_natural **/
constexpr bool is_natural() const { return true; }
/** get member @c n_bpu **/
constexpr std::size_t n_bpu() const { return n_bpu_; }
/** true if this unit has no dimension **/
constexpr bool is_dimensionless() const { return n_bpu_ == 0; }
/** get address of member @c bpu_v **/
constexpr bpu<Int> * bpu_v() const { return bpu_v_; }
///@}
/** @defgroup natural-unit-methods **/
///@{
/** construct reciprocal of this unit.
*
* For example reciprocal of a newton (abbreviation @c "kg.m.s^-2") is
* a unit with abbreviation @c "kg^-1.m^-1.s^2"
**/
constexpr natural_unit reciprocal() const {
natural_unit retval;
for (std::size_t i = 0; i < this->n_bpu(); ++i)
retval.push_back((*this)[i].reciprocal());
return retval;
}
/** abbreviation for this unit.
*
* Apply as suffix when printing quantities involving this unit.
*
* For example @c "mm" for millimeters, or @c "ns" for nanoseconds
**/
constexpr nu_abbrev_type abbrev() const {
nu_abbrev_type retval;
for (std::size_t i = 0; i < n_bpu_; ++i) {
if (i > 0)
retval.append(".");
retval.append(bpu_v_[i].abbrev(), 0, -1);
}
return retval;
}
/** remove bpu at position @p p **/
constexpr void remove_bpu(size_t p) {
for (std::size_t i = p; i+1 < n_bpu_; ++i)
bpu_v_[i] = bpu_v_[i+1];
--n_bpu_;
}
/** append @p bpu to this unit in-place
*
* Require @c bpu.native_dim does not match any existing member of @ref bpu_v_
**/
constexpr void push_back(const bpu<Int> & bpu) {
if (n_bpu_ < n_dim)
bpu_v_[n_bpu_++] = bpu;
}
///@}
/** @addtogroup natural-unit-access-methods **/
///@{
/** get bpu for dimension @p d. if d isn't present, construct bpu with 0 power **/
constexpr bpu<Int> lookup_dim(dimension d) const {
for (std::size_t i = 0, n = n_bpu(); i<n; ++i) {
if (d == bpu_v_[i].native_dim())
return bpu_v_[i];
}
/** not found, return sentinel **/
return bpu<Int>(d, scalefactor_ratio_type(0), power_ratio_type(0));
}
/** get element @p i of @ref bpu_v_ **/
constexpr bpu<Int> & operator[](std::size_t i) { return bpu_v_[i]; }
/** get element @p i of @ref bpu_v_ (const version) **/
constexpr const bpu<Int> & operator[](std::size_t i) const { return bpu_v_[i]; }
///@}
/** @defgroup natural-unit-conversion-methods **/
///@{
/** convert to equivalent unit using scalefactor representation @p Int2 instead of
* @ref ratio_int_type
**/
template <typename Int2>
constexpr natural_unit<Int2> to_repr() const {
natural_unit<Int2> retval;
std::size_t i = 0;
for (; i < n_bpu_; ++i)
retval.push_back(bpu_v_[i].template to_repr<Int2>());
return retval;
}
///@}
public: /* public members so instance can be non-type template parameter (is a structural type) */
/** @defgroup natural-unit-instance-vars **/
///@{
/** the number of occupied slots in @c bpu_v_ **/
std::size_t n_bpu_;
/** storage for basis power units **/
bpu<Int> bpu_v_[n_dim];
///@}
};
/** @defgroup natural-unit-comparison-functions natural-unit comparison functions **/
///@{
/** compare natural units @p x, @p y for equality. **/
template <typename Int>
constexpr bool
operator==(const natural_unit<Int> & x,
const natural_unit<Int> & y)
{
if (x.n_bpu() != y.n_bpu())
return false;
/* does x contain any dimension that isn't present in y? */
for (std::size_t i = 0, n = x.n_bpu(); i<n; ++i) {
const bpu<Int> & xi = x[i];
if (xi != y.lookup_dim(xi.native_dim()))
return false;
}
/* if all bpu's x[i] match something from y, then x,y must be equal
* since they each have the same number of bpu's
*/
return true;
}
/** compare natural units @p x, @p y for inequality **/
template <typename Int>
constexpr bool
operator!=(const natural_unit<Int> & x,
const natural_unit<Int> & y) {
return !(x == y);
}
///@}
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,
typename OuterScale = ratio::ratio<Int> >
struct outer_scalefactor_result {
constexpr outer_scalefactor_result(const OuterScale & outer_scale_factor,
double outer_scale_sq)
: outer_scale_factor_{outer_scale_factor},
outer_scale_sq_{outer_scale_sq} {}
/* (b/b')^p0 */
OuterScale outer_scale_factor_;
/* (b/b')^q -- until c++26 only allow q=0 or q=1/2 */
double outer_scale_sq_;
};
template <typename Int,
typename OuterScale = ratio::ratio<Int> >
struct bpu2_rescale_result {
constexpr bpu2_rescale_result(const bpu<Int> & bpu_rescaled,
const OuterScale & outer_scale_factor,
double outer_scale_sq)
: bpu_rescaled_{bpu_rescaled},
outer_scale_factor_{outer_scale_factor},
outer_scale_sq_{outer_scale_sq}
{}
/* (b'.u)^p */
bpu<Int> bpu_rescaled_;
/* (b/b')^p0 */
OuterScale outer_scale_factor_;
/* [(b/b')^q]^2 -- until c++26 only allow q=0 or q=1/2 */
double outer_scale_sq_;
};
template < typename Int,
typename OuterScale = ratio::ratio<Int> >
constexpr
bpu2_rescale_result<Int, OuterScale>
bpu2_rescale(const bpu<Int> & orig,
const scalefactor_ratio_type & new_scalefactor)
{
/* we have orig, representing qty (b.u)^p,
* with b=orig.scalefactor, u=native dimension, p=orig.power
*/
ratio::ratio<Int> mult = (orig.scalefactor() / new_scalefactor);
/* inv: p_frac in (-1, 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();
/* pre-c++26 workaround */
{
if (p_frac.den() == 1) {
mult_sq = 1.0;
} else if(p_frac.num() == 1 && p_frac.den() == 2) {
mult_sq = mult.template convert_to<double>();
} else if(p_frac.num() == -1 && p_frac.den() == 2) {
mult_sq = 1.0 / mult.template convert_to<double>();
} else {
// remaining possibilities not supported until c++26
}
}
ratio::ratio<Int> mult_p = mult.power(orig.power().floor());
return bpu2_rescale_result<Int, OuterScale>(bpu<Int>(orig.native_dim(),
new_scalefactor,
orig.power()),
mult_p.template convert_to<OuterScale>(),
mult_sq);
}
template < typename Int,
typename OuterScale >
constexpr
outer_scalefactor_result<Int, OuterScale>
bpu_product_inplace(bpu<Int> * p_target_bpu,
const bpu<Int> & rhs_bpu_orig)
{
assert(rhs_bpu_orig.native_dim() == p_target_bpu->native_dim());
bpu2_rescale_result<Int, OuterScale> rhs_bpu_rr
= bpu2_rescale<Int, OuterScale>(rhs_bpu_orig,
p_target_bpu->scalefactor().template convert_to<OuterScale>());
*p_target_bpu = bpu<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_factor_,
rhs_bpu_rr.outer_scale_sq_);
}
template < typename Int,
typename OuterScale >
constexpr
outer_scalefactor_result<Int, OuterScale>
bpu_ratio_inplace(bpu<Int> * p_target_bpu,
const bpu<Int> & rhs_bpu_orig)
{
assert(rhs_bpu_orig.native_dim() == p_target_bpu->native_dim());
bpu2_rescale_result<Int, OuterScale> rhs_bpu_rr
= bpu2_rescale<Int, OuterScale>(rhs_bpu_orig,
p_target_bpu->scalefactor());
*p_target_bpu = bpu<Int>(p_target_bpu->native_dim(),
p_target_bpu->scalefactor(),
p_target_bpu->power() - rhs_bpu_orig.power());
return outer_scalefactor_result<Int, OuterScale>
(OuterScale(1) / rhs_bpu_rr.outer_scale_factor_,
1.0 / rhs_bpu_rr.outer_scale_sq_);
}
template < typename Int,
typename OuterScale >
constexpr
outer_scalefactor_result<Int, OuterScale>
nu_product_inplace(natural_unit<Int> * p_target,
const bpu<Int> & bpu)
{
std::size_t i = 0;
for (; i < p_target->n_bpu(); ++i) {
auto * p_target_bpu = &((*p_target)[i]);
if (p_target_bpu->native_dim() == bpu.native_dim()) {
outer_scalefactor_result<Int, OuterScale> retval
= bpu_product_inplace<Int, OuterScale>(p_target_bpu, bpu);
if (p_target_bpu->power().is_zero()) {
/* dimension assoc'd with *p_target_bpu has been cancelled */
p_target->remove_bpu(i);
}
return retval;
}
}
/* control here: i=p_target->n_bpu()
* Dimension represented by bpu does not already appear in *p_target.
* Adopt bpu's scalefactor
*/
p_target->push_back(bpu);
return outer_scalefactor_result<Int, OuterScale>
(OuterScale(1) /*outer_scale_factor*/,
1.0 /*outer_scale_sq*/);
}
template < typename Int,
typename OuterScale = ratio::ratio<Int> >
constexpr
outer_scalefactor_result<Int, OuterScale>
nu_ratio_inplace(natural_unit<Int> * p_target,
const bpu<Int> & bpu)
{
std::size_t i = 0;
for (; i < p_target->n_bpu(); ++i) {
auto * p_target_bpu = &((*p_target)[i]);
if (p_target_bpu->native_dim() == bpu.native_dim()) {
outer_scalefactor_result<Int, OuterScale> retval
= bpu_ratio_inplace<Int, OuterScale>(p_target_bpu, bpu);
if (p_target_bpu->power().is_zero()) {
/* dimension assoc'd with *p_target_bpu has been cancelled */
p_target->remove_bpu(i);
}
return retval;
}
}
/* here: i=p_target->n_bpu()
* Dimension represented by bpu does not already appear in *p_target.
* Adopt bpu's scalefactor
*/
p_target->push_back(bpu.reciprocal());
return outer_scalefactor_result<Int, OuterScale>
(OuterScale(1) /*outer_scale_factor*/,
1.0 /*outer_scale_sq*/);
}
} /*namespace detail*/
/** @brief namespace for constants representing basis natural units
*
* Application code will typically use instead parallel scaled-unit constants
* (see the 'u' namespace in 'scaled_unit.hpp')
**/
namespace nu {
constexpr auto dimensionless = natural_unit<std::int64_t>();
// ----- mass -----
constexpr auto picogram = natural_unit<std::int64_t>::from_bu(detail::bu::picogram);
constexpr auto nanogram = natural_unit<std::int64_t>::from_bu(detail::bu::nanogram);
constexpr auto microgram = natural_unit<std::int64_t>::from_bu(detail::bu::microgram);
constexpr auto milligram = natural_unit<std::int64_t>::from_bu(detail::bu::milligram);
constexpr auto gram = natural_unit<std::int64_t>::from_bu(detail::bu::gram);
constexpr auto kilogram = natural_unit<std::int64_t>::from_bu(detail::bu::kilogram);
constexpr auto tonne = natural_unit<std::int64_t>::from_bu(detail::bu::tonne);
constexpr auto kilotonne = natural_unit<std::int64_t>::from_bu(detail::bu::kilotonne);
constexpr auto megatonne = natural_unit<std::int64_t>::from_bu(detail::bu::megatonne);
constexpr auto gigatonne = natural_unit<std::int64_t>::from_bu(detail::bu::gigatonne);
// ----- distance -----
constexpr auto picometer = natural_unit<std::int64_t>::from_bu(detail::bu::picometer);
constexpr auto nanometer = natural_unit<std::int64_t>::from_bu(detail::bu::nanometer);
constexpr auto micrometer = natural_unit<std::int64_t>::from_bu(detail::bu::micrometer);
constexpr auto millimeter = natural_unit<std::int64_t>::from_bu(detail::bu::millimeter);
constexpr auto meter = natural_unit<std::int64_t>::from_bu(detail::bu::meter);
constexpr auto kilometer = natural_unit<std::int64_t>::from_bu(detail::bu::kilometer);
constexpr auto megameter = natural_unit<std::int64_t>::from_bu(detail::bu::megameter);
constexpr auto gigameter = natural_unit<std::int64_t>::from_bu(detail::bu::gigameter);
constexpr auto lightsecond = natural_unit<std::int64_t>::from_bu(detail::bu::lightsecond);
constexpr auto astronomicalunit = natural_unit<std::int64_t>::from_bu(detail::bu::astronomicalunit);
constexpr auto inch = natural_unit<std::int64_t>::from_bu(detail::bu::inch);
constexpr auto foot = natural_unit<std::int64_t>::from_bu(detail::bu::foot);
constexpr auto yard = natural_unit<std::int64_t>::from_bu(detail::bu::yard);
constexpr auto mile = natural_unit<std::int64_t>::from_bu(detail::bu::mile);
// ----- time -----
constexpr auto picosecond = natural_unit<std::int64_t>::from_bu(detail::bu::picosecond);
constexpr auto nanosecond = natural_unit<std::int64_t>::from_bu(detail::bu::nanosecond);
constexpr auto microsecond = natural_unit<std::int64_t>::from_bu(detail::bu::microsecond);
constexpr auto millisecond = natural_unit<std::int64_t>::from_bu(detail::bu::millisecond);
constexpr auto second = natural_unit<std::int64_t>::from_bu(detail::bu::second);
constexpr auto minute = natural_unit<std::int64_t>::from_bu(detail::bu::minute);
constexpr auto hour = natural_unit<std::int64_t>::from_bu(detail::bu::hour);
constexpr auto day = natural_unit<std::int64_t>::from_bu(detail::bu::day);
constexpr auto week = natural_unit<std::int64_t>::from_bu(detail::bu::week);
constexpr auto month = natural_unit<std::int64_t>::from_bu(detail::bu::month);
constexpr auto year = natural_unit<std::int64_t>::from_bu(detail::bu::year);
constexpr auto year250 = natural_unit<std::int64_t>::from_bu(detail::bu::year250);
constexpr auto year360 = natural_unit<std::int64_t>::from_bu(detail::bu::year360);
constexpr auto year365 = natural_unit<std::int64_t>::from_bu(detail::bu::year365);
constexpr auto currency = natural_unit<std::int64_t>::from_bu(detail::bu::currency);
constexpr auto price = natural_unit<std::int64_t>::from_bu(detail::bu::price);
constexpr auto volatility_30d = natural_unit<std::int64_t>::from_bu(detail::bu::month, power_ratio_type(-1,2));
constexpr auto volatility_250d = natural_unit<std::int64_t>::from_bu(detail::bu::year250, power_ratio_type(-1,2));
constexpr auto volatility_360d = natural_unit<std::int64_t>::from_bu(detail::bu::year360, power_ratio_type(-1,2));
constexpr auto volatility_365d = natural_unit<std::int64_t>::from_bu(detail::bu::year365, power_ratio_type(-1,2));
} /*namespace nu*/
} /*namespace qty*/
} /*namespace xo*/
/** end natural_unit.hpp **/

View file

@ -0,0 +1,29 @@
/** @file natural_unit_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "natural_unit.hpp"
#include "bpu_iostream.hpp"
#include <iostream>
namespace xo {
namespace qty {
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 qty*/
} /*namespace xo*/
/** end natural_unit_iostream.hpp **/

View file

@ -0,0 +1,34 @@
/* @file numeric_concept.hpp */
#pragma once
#include <concepts>
namespace xo {
namespace qty {
/** @concept numeric_concept
* @brief Concept for values that participate in arithmetic operations (+,-,*,/) and comparisons
*
* Intended to include at least:
* - built-in integral and floating-point types
* - xo::raio<U>
* - xo::unit::quantity<U,R>
*
* Intend numeric_concept to apply to types suitable for
* xo::unit::quantity::repr_type.
**/
template <typename T, typename U = T>
concept numeric_concept = requires(T x, U y)
{
{ -x };
{ x - y };
{ x + y };
{ x * y };
{ x / y };
{ x == y };
{ x != y };
};
} /*namespace qty*/
} /*namespace xo*/
/* end numeric_concept.hpp */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,28 @@
/** @file quantity_concept.hpp **/
#pragma once
//#include "unit_concept.hpp"
#include "numeric_concept.hpp"
namespace xo {
namespace qty {
template <typename Quantity>
concept quantity_concept = requires(Quantity qty, typename Quantity::repr_type repr)
{
typename Quantity::unit_type;
typename Quantity::repr_type;
//{ Quantity::multiply(qty, qty) };
{ qty.scale() } -> std::same_as<const typename Quantity::repr_type &>;
{ qty.unit() } -> std::same_as<const typename Quantity::unit_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 qty*/
} /*namespace xo*/
/* end quantity_concept.hpp */

View file

@ -0,0 +1,26 @@
/** @file quantity_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "quantity.hpp"
#include "natural_unit_iostream.hpp"
namespace xo {
namespace qty {
template < auto NaturalUnit, typename Repr >
inline std::ostream &
operator<< (std::ostream & os,
const quantity<NaturalUnit, Repr> & x)
{
os << x.scale() << x.abbrev();
return os;
}
} /*namespace qty*/
} /*namespace xo*/
/** end quantity_iostream.hpp **/

View file

@ -0,0 +1,37 @@
/** @file quantity_ops.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "quantity_concept.hpp"
namespace xo {
namespace qty {
/** note: won't have constexpr result until c++26 (when @c sqrt(), @c pow() are constexpr)
**/
template <typename Quantity, typename Quantity2>
requires quantity_concept<Quantity> && quantity_concept<Quantity2>
constexpr auto
operator== (const Quantity & x, const Quantity2 & y)
{
return (Quantity::compare(x, y) == 0);
}
/** note: won't have constexpr result until c++26 (when @c sqrt(), @c pow() are constexpr)
**/
template <typename Quantity, typename Quantity2>
requires quantity_concept<Quantity> && quantity_concept<Quantity2>
constexpr auto
operator<=> (const Quantity & x, const Quantity2 & y)
{
return Quantity::compare(x, y);
}
} /*namespace qty*/
} /*namespace xo*/
/** end quantity_ops.hpp **/

View file

@ -0,0 +1,401 @@
/** @file scaled_unit.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "width2x.hpp"
namespace xo {
namespace qty {
/** @class scaled_unit
* @brief Represents the product sqrt(outer_scale_sq) * outer_scale_exact * nat_unit
**/
template < typename Int,
typename OuterScale = ratio::ratio<Int> >
struct scaled_unit {
/** @defgroup scaled-unit-type-traits scaled-unit type traits **/
///@{
/** type for representing individual basis-unit scalefactors **/
using ratio_int_type = typename natural_unit<Int>::ratio_int_type;
///@}
public:
/** @defgroup scaled-unit-ctors scaled-unit constructors **/
///@{
/** create scaled unit representing a multiple
* @p outer_scale_factor * @p sqrt(outer_scale_sq)
* of natural unit @p nat_unit
**/
constexpr scaled_unit(const natural_unit<Int> & nat_unit,
OuterScale outer_scale_factor,
double outer_scale_sq)
: natural_unit_{nat_unit},
outer_scale_factor_{outer_scale_factor},
outer_scale_sq_{outer_scale_sq}
{}
///@}
/** @defgroup scaled-unit-access-methods scaled-unit access methods **/
///@{
/** always true for scaled_unit **/
static constexpr bool is_scaled_unit_type_v = true;
/** always true for scaled_unit **/
constexpr bool is_scaled_unit_type() const { return true; }
/** true iff scaled unit can be faithfully represented by a @ref natural_unit **/
constexpr bool is_natural() const {
return (outer_scale_factor_ == OuterScale(1) && (outer_scale_sq_ == 1.0));
}
/** true if this scaled unit has no dimension **/
constexpr bool is_dimensionless() const { return natural_unit_.is_dimensionless(); }
/** get number of distinct native dimensions present.
* e.g. for unit Newton = 1 kg.m.s^-2, n_bpu would be 3,
* with {mass, distance, time} present.
* Note that this value does not count exponents
**/
constexpr std::size_t n_bpu() const { return natural_unit_.n_bpu(); }
///@}
/** @defgroup scaled-unit-general-methods scaled-unit access methods **/
///@{
/** return reciprocal of this unit. **/
constexpr scaled_unit reciprocal() const {
return scaled_unit(natural_unit_.reciprocal(),
1 / outer_scale_factor_,
1.0 / outer_scale_sq_);
}
/** get bpu for dimension @p d. if d isn't present, construct bpu with 0 power **/
constexpr bpu<Int> lookup_dim(dimension d) const {
return natural_unit_.lookup_dim(d);
}
/** return @p i'th bpu associated with this unit **/
constexpr bpu<Int> & operator[](std::size_t i) { return natural_unit_[i]; }
/** return @p i'th bpu associated with this unit (const version) **/
constexpr const bpu<Int> & operator[](std::size_t i) const { return natural_unit_[i]; }
///@}
public: /* public members so scaled_unit instance can be a non-type template parameter (a structural type) */
/** @defgroup scaled-unit-instance-vars **/
///@{
/** scale factor multiplying @ref natural_unit_ **/
OuterScale outer_scale_factor_;
/** squared scale factor multiplying @ref natural_unit_ **/
double outer_scale_sq_;
/** natural unit term in this scaled unit **/
natural_unit<Int> natural_unit_;
///@}
};
// TODO: comparison operators
namespace detail {
/** promote natural unit to scaled unit (with unit outer scalefactors) **/
template <typename Int>
constexpr auto su_promote(const natural_unit<Int> & bpuv) {
return scaled_unit<Int>(bpuv,
ratio::ratio<Int>(1, 1),
1.0);
}
}
namespace u {
/* values here can be used as template arguments to quantity:
* e.g.
* quantity<u:picogram> qty1;
* quantity<u:meter/u:second> velocity;
*/
constexpr auto
su_from_bu(const basis_unit & bu,
const power_ratio_type & power = power_ratio_type(1))
{
return detail::su_promote(natural_unit<std::int64_t>::from_bu(bu, power));
}
/** @defgroup scaled-unit-dimensionless scaled-unit dimensionless constant **/
///@{
/** dimensionless unit; equivalent to 1 **/
constexpr auto dimensionless = detail::su_promote(natural_unit<std::int64_t>());
///@}
// ----- mass units -----
/** @defgroup scaled-unit-mass scaled-unit mass units **/
///@{
/** unit of 10^-12 grams **/
constexpr auto picogram = su_from_bu(detail::bu::picogram);
/** unit of 10^-9 grams **/
constexpr auto nanogram = su_from_bu(detail::bu::nanogram);
/** unit of 10^-6 grams **/
constexpr auto microgram = su_from_bu(detail::bu::microgram);
/** unit of 10^-3 grams **/
constexpr auto milligram = su_from_bu(detail::bu::milligram);
/** unit of 1 gram **/
constexpr auto gram = su_from_bu(detail::bu::gram);
/** unit of 10^3 grams **/
constexpr auto kilogram = su_from_bu(detail::bu::kilogram);
/** unit of 1 metric tonne = 10^3 kg **/
constexpr auto tonne = su_from_bu(detail::bu::tonne);
/** unit of 10^3 tonnes = 10^6 kg **/
constexpr auto kilotonne = su_from_bu(detail::bu::kilotonne);
/** unit of 10^6 tonnes = 10^9 kg **/
constexpr auto megatonne = su_from_bu(detail::bu::megatonne);
/** unit of 10^9 tonnes = 10^12 kg **/
constexpr auto gigatonne = su_from_bu(detail::bu::gigatonne);
///@}
// ----- distance units -----
/** @defgroup scaled-unit-distance scaled-unit distance units **/
///@{
/** unit of 10^-12 meters **/
constexpr auto picometer = su_from_bu(detail::bu::picometer);
/** unit of 10^-9 meters **/
constexpr auto nanometer = su_from_bu(detail::bu::nanometer);
/** unit of 10^-6 meters **/
constexpr auto micrometer = su_from_bu(detail::bu::micrometer);
/** unit of 10^-3 meters **/
constexpr auto millimeter = su_from_bu(detail::bu::millimeter);
/** unit of 1 meter **/
constexpr auto meter = su_from_bu(detail::bu::meter);
/** unit of 10^3 meters **/
constexpr auto kilometer = su_from_bu(detail::bu::kilometer);
/** unit of 10^6 meters (not commonly used) **/
constexpr auto megameter = su_from_bu(detail::bu::megameter);
/** unit of 10^9 meters (not commonly used) **/
constexpr auto gigameter = su_from_bu(detail::bu::gigameter);
/** unit of 1 light-second = distance light travels in a vacuum in 1 second **/
constexpr auto lightsecond = su_from_bu(detail::bu::lightsecond);
/** unit of 1 astronomical unit, for approximate radius of earth orbit **/
constexpr auto astronomicalunit = su_from_bu(detail::bu::astronomicalunit);
/** unit of 1 inch = 1/12 feet **/
constexpr auto inch = su_from_bu(detail::bu::inch);
/** unit of 1 foot = 0.3048 meters **/
constexpr auto foot = su_from_bu(detail::bu::foot);
/** unit of 1 yard = 3 feet **/
constexpr auto yard = su_from_bu(detail::bu::yard);
/** unit of 1 mile = 1760 yards **/
constexpr auto mile = su_from_bu(detail::bu::mile);
///@}
// ----- time units -----
/** @defgroup scaled-unit-time scaled-unit time units **/
///@{
/** unit of 1 picosecond = 10^-12 seconds **/
constexpr auto picosecond = su_from_bu(detail::bu::picosecond);
/** unit of 1 nanosecond = 10^-9 seconds **/
constexpr auto nanosecond = su_from_bu(detail::bu::nanosecond);
/** unit of 1 microseccond = 10^-6 seconds **/
constexpr auto microsecond = su_from_bu(detail::bu::microsecond);
/** unit of 1 millisecond = 10^-3 seconds **/
constexpr auto millisecond = su_from_bu(detail::bu::millisecond);
/** unit of 1 second **/
constexpr auto second = su_from_bu(detail::bu::second);
/** unit of 1 minute **/
constexpr auto minute = su_from_bu(detail::bu::minute);
/** unit of 1 hour **/
constexpr auto hour = su_from_bu(detail::bu::hour);
/** unit for a 24-hour day **/
constexpr auto day = su_from_bu(detail::bu::day);
/** unit for a week comprising exactly 7 24-hour days **/
constexpr auto week = su_from_bu(detail::bu::week);
/** unit for a 30-day month **/
constexpr auto month = su_from_bu(detail::bu::month);
/** unit for a year containing exactly 365.25 24-hour days **/
constexpr auto year = su_from_bu(detail::bu::year);
/** unit for a 'year' containing exactly 250 24-hour days.
* (approximates the number of business days in a year)
**/
constexpr auto year250 = su_from_bu(detail::bu::year250);
/** unit for a 'year' containing exactly 360 24-hour days **/
constexpr auto year360 = su_from_bu(detail::bu::year360);
/** unit for a 'year' containing exactly 365 24-hour days **/
constexpr auto year365 = su_from_bu(detail::bu::year365);
///@}
/** @defgroup scaled-unit-misc scaled-unit miscellaneous units **/
///@{
// ----- currency -----
/** generic currency unit **/
constexpr auto currency = su_from_bu(detail::bu::currency);
// ----- price - ---
/** generic price unit **/
constexpr auto price = su_from_bu(detail::bu::price);
///@}
// ----- volatility units -----
/** @defgroup scaled-unit-volatility scaled-unit volatility units **/
///@{
/** volatility, in 30-day units **/
constexpr auto volatility_30d = su_from_bu(detail::bu::month,
power_ratio_type(-1,2));
/** volatility, in 250-day 'annual' units **/
constexpr auto volatility_250d = su_from_bu(detail::bu::year250,
power_ratio_type(-1,2));
/** volatility, in 360-day 'annual' units **/
constexpr auto volatility_360d = su_from_bu(detail::bu::year360,
power_ratio_type(-1,2));
/** volatility, in 365-day 'annual' units **/
constexpr auto volatility_365d = su_from_bu(detail::bu::year365,
power_ratio_type(-1,2));
///@}
}
namespace detail {
template <typename Int,
typename Int2x = width2x<Int>,
typename OuterScale = ratio::ratio<Int2x>>
constexpr
scaled_unit<Int, OuterScale>
su_product(const natural_unit<Int> & lhs_bpu_array,
const natural_unit<Int> & rhs_bpu_array)
{
natural_unit<Int2x> prod = lhs_bpu_array.template to_repr<Int2x>();
/* 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<Int2x>
(OuterScale(1) /*outer_scale_factor*/,
1.0 /*outer_scale_sq*/));
for (std::size_t i = 0; i < rhs_bpu_array.n_bpu(); ++i) {
auto sfr2 = nu_product_inplace<Int2x, OuterScale>(&prod,
rhs_bpu_array[i].template to_repr<Int2x>());
sfr.outer_scale_factor_ = sfr.outer_scale_factor_ * sfr2.outer_scale_factor_;
sfr.outer_scale_sq_ *= sfr2.outer_scale_sq_;
}
return scaled_unit<Int, OuterScale>(prod.template to_repr<Int>(),
sfr.outer_scale_factor_,
sfr.outer_scale_sq_);
}
/* use Int2x to accumulate scalefactor
*/
template < typename Int,
typename Int2x = width2x<Int>,
typename OuterScale = ratio::ratio<Int2x> >
constexpr
scaled_unit<Int, OuterScale>
su_ratio(const natural_unit<Int> & nu_lhs,
const natural_unit<Int> & nu_rhs)
{
natural_unit<Int2x> ratio = nu_lhs.template to_repr<Int2x>();
/* 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<Int2x, OuterScale>
(OuterScale(1) /*outer_scale_factor*/,
1.0 /*outer_scale_sq*/));
for (std::size_t i = 0; i < nu_rhs.n_bpu(); ++i) {
auto sfr2 = nu_ratio_inplace<Int2x, OuterScale>(&ratio,
nu_rhs[i].template to_repr<Int2x>());
/* note: nu_ratio_inplace() reports multiplicative outer scaling factors,
* so multiply is correct here
*/
sfr.outer_scale_factor_ = (sfr.outer_scale_factor_
* sfr2.outer_scale_factor_);
sfr.outer_scale_sq_ *= sfr2.outer_scale_sq_;
}
return scaled_unit<Int, OuterScale>(ratio.template to_repr<Int>(),
sfr.outer_scale_factor_,
sfr.outer_scale_sq_);
}
}
/** @defgroup scaled-unit-operators **/
///@{
/** Multiply scaled_unit instances @p x_unit and @p y_unit.
* Result is a scaled_unit for the product dimension.
* For each basis dimension, result will prioritize scale from @p x_unit ahead of @p y_unit.
**/
template <typename Int,
typename Int2x = detail::width2x_t<Int>>
inline constexpr scaled_unit<Int>
operator* (const scaled_unit<Int> & x_unit,
const scaled_unit<Int> & y_unit)
{
auto rr = detail::su_product<Int, Int2x>(x_unit.natural_unit_,
y_unit.natural_unit_);
return (scaled_unit<Int>
(rr.natural_unit_,
(ratio::ratio<Int2x>(rr.outer_scale_factor_)
* ratio::ratio<Int2x>(x_unit.outer_scale_factor_)
* ratio::ratio<Int2x>(y_unit.outer_scale_factor_)),
rr.outer_scale_sq_ * x_unit.outer_scale_sq_ * y_unit.outer_scale_sq_));
}
/** Divide scaled_unit instances @p x_unit by @p y_unit.
* Result is a scaled_unit for the quotient dimension.
* For each basis dimension, result will prioritize scale from @p x_unit ahead of @p y_unit.
**/
template <typename Int,
typename Int2x = detail::width2x_t<Int>>
inline constexpr scaled_unit<Int>
operator/ (const scaled_unit<Int> & x_unit,
const scaled_unit<Int> & y_unit)
{
auto rr = detail::su_ratio<Int, Int2x>(x_unit.natural_unit_,
y_unit.natural_unit_);
return (scaled_unit<Int>
(rr.natural_unit_,
(ratio::ratio<Int2x>(rr.outer_scale_factor_)
* ratio::ratio<Int2x>(x_unit.outer_scale_factor_)
* ratio::ratio<Int2x>(y_unit.outer_scale_factor_)),
rr.outer_scale_sq_ * x_unit.outer_scale_sq_ * y_unit.outer_scale_sq_));
}
///@}
} /*namespace qty*/
} /*namespace xo*/
/** end scaled_unit.hpp **/

View file

@ -0,0 +1,23 @@
/** @file scaled_unit_concept.hpp **/
#pragma once
#include <concepts>
namespace xo {
namespace qty {
template <typename ScaledUnit>
concept scaled_unit_concept = requires(ScaledUnit su)
{
typename ScaledUnit::ratio_int_type;
{ su.is_scaled_unit_type() } -> std::same_as<bool>;
{ su.is_natural() } -> std::same_as<bool>;
{ su.is_dimensionless() } -> std::same_as<bool>;
} && ScaledUnit::is_scaled_unit_type_v;
} /*namespace qty*/
} /*namespace xo*/
/** end scaled_unit_concept.hpp **/

View file

@ -0,0 +1,31 @@
/** @file scaled_unit_iostream.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "scaled_unit.hpp"
#include "natural_unit_iostream.hpp"
#include "xo/flatstring/int128_iostream.hpp"
#include <iostream>
namespace xo {
namespace qty {
template <typename Int, typename OuterScale>
inline std::ostream &
operator<<(std::ostream & os,
const scaled_unit<Int, OuterScale> & x)
{
os << "<scaled-unit"
<< xtag("outer_scale_factor", x.outer_scale_factor_)
<< xtag("outer_scale_sq", x.outer_scale_sq_)
<< xtag("bpuv", x.natural_unit_)
<< ">";
return os;
};
} /*namespace qty*/
} /*namespace xo*/
/** end scaled_unit_iostream.hpp **/

View file

@ -0,0 +1,24 @@
/** @file unit2.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "natural_unit.hpp"
namespace xo {
namespace qty {
/** @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 qty*/
} /*namespace xo*/
/** end unit2.hpp **/

View file

@ -0,0 +1,39 @@
/** @file width2x.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "natural_unit.hpp"
#include <cstdint>
namespace xo {
namespace qty {
namespace detail {
template <typename Int>
struct width2x;
template <>
struct width2x<std::int16_t> {
using type = std::int32_t;
};
template <>
struct width2x<std::int32_t> {
using type = std::int64_t;
};
template <>
struct width2x<std::int64_t> {
using type = __int128_t;
};
template <typename Int>
using width2x_t = width2x<Int>::type;
}
} /*namespace qty*/
} /*namespace xo*/
/** end width2x.hpp **/

View file

@ -0,0 +1,562 @@
/** @file xquantity.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "quantity_ops.hpp"
#include "scaled_unit.hpp"
#include "natural_unit.hpp"
namespace xo {
namespace qty {
/** @class xquantity
* @brief represent a scalar quantity with polymorphic units.
*
* - @p Repr type used represent a dimensionless multiple of a natural unit.
*
* Constexpr implementation, but units are explicitly represented:
* @code
* sizeof(xquantity) > sizeof(xquantity::repr_type)
* @endcode
*
* Explicit unit representation allows introducing units at runtime,
* for example in python bindings.
* See for example <a href="https://github.com/rconybea/xo-pyutil">xo-pyutil</a>
*
* See @ref quantity for implementation with units established at compile time
*
* Require:
* - Repr supports numeric operations (+, -, *, /)
* - Repr supports conversion from double.
**/
template <typename Repr = double,
typename Int = std::int64_t>
class xquantity {
public:
/** @defgroup xquantity-type-traits xquantity type traits **/
///@{
/** @brief runtime representation for this value's scale **/
using repr_type = Repr;
/** @brief runtime representation for this value's unit **/
using unit_type = natural_unit<Int>;
/** @brief type used for numerator and denominator in basis-unit scalefactor ratios **/
using ratio_int_type = Int;
/** @brief double-width type for numerator and denominator of intermediate
* scalefactor ratios. Use to mitigate loss of precision during computation
* of conversion factors between units with widely-differing magnitude
**/
using ratio_int2x_type = detail::width2x_t<typename unit_type::ratio_int_type>;
///@}
public:
/** @defgroup xquantity-ctors xquantity constructors **/
///@{
/** create dimensionless, zero quantity **/
constexpr xquantity()
: scale_{0}, unit_{natural_unit<Int>()} {}
/** create quantity representing multiple of @p scale times @p unit **/
constexpr xquantity(Repr scale,
const natural_unit<Int> & unit)
: scale_{scale}, unit_{unit} {}
/** create quantity representing multiple of @p scale times @p unit.
*
* Collects outer scalefactors (if any) from @p unit,
* so for example:
*
* @code
* using namespace xo::qty;
* xquantity q(123, u::meter * u::millimeter);
*
* q.scale() --> 0.123
* @endcode
**/
constexpr xquantity(Repr scale,
const scaled_unit<Int> & unit)
:
scale_(scale
* unit.outer_scale_factor_.template convert_to<double>()
* ((unit.outer_scale_sq_ == 1.0)
? 1.0
: ::sqrt(unit.outer_scale_sq_))
),
unit_{unit.natural_unit_} {}
///@}
/** @defgroup xquantity-constants static xquantity constants **/
///@{
/** false since unit information may be unknown at compile time.
* Coordinates with @c quantity::always_constexpr_unit
**/
static constexpr bool always_constexpr_unit = false;
///@}
/** @defgroup xquantity-access-methods xquantity access methods **/
///@{
/** get member @ref scale_ **/
constexpr const repr_type & scale() const { return scale_; }
/** get member @ref unit_ **/
constexpr const unit_type & unit() const { return unit_; }
/** true iff this quantity has no dimension **/
constexpr bool is_dimensionless() const { return unit_.is_dimensionless(); }
/** return abbreviation for quantities with this unit **/
constexpr nu_abbrev_type abbrev() const { return unit_.abbrev(); }
///@}
/** @defgroup xquantity-arithmetic-support**/
///@{
/** create unit quantity with same unit as @c this **/
constexpr xquantity unit_qty() const { return xquantity(1, unit_); }
/** create zero quantity with same unit as @c this **/
constexpr xquantity zero_qty() const { return xquantity(0, unit_); }
/** create quantity representing reciprocal of @c this **/
constexpr xquantity reciprocal() const { return xquantity(1.0 / scale_, unit_.reciprocal()); }
/** create quantity representing this value scaled by dimensionless mutliplier @p x **/
template <typename Dimensionless>
requires std::is_arithmetic_v<Dimensionless>
constexpr auto scale_by(Dimensionless x) const {
return xquantity(x * this->scale_, this->unit_);
}
/** create quantity representing this value scaled by dimensionless multiplier @p 1/x **/
template <typename Dimensionless>
requires std::is_arithmetic_v<Dimensionless>
constexpr auto divide_by(Dimensionless x) const {
return xquantity(this->scale_ / x, this->unit_);
}
/** create quantity representing dimensionless numerator @p x divided by this value **/
template <typename Dimensionless>
requires std::is_arithmetic_v<Dimensionless>
constexpr auto divide_into(Dimensionless x) const {
return xquantity(x / this->scale_, this->unit_.reciprocal());
}
/** multiply two @c xquantity values, or a mixed (@c xquantity, @c quantity) pair **/
template <typename Quantity2>
static constexpr
auto multiply(const xquantity & x, const Quantity2 & y) {
using r_repr_type = std::common_type_t<typename xquantity::repr_type,
typename Quantity2::repr_type>;
using r_int_type = std::common_type_t<typename xquantity::ratio_int_type,
typename Quantity2::ratio_int_type>;
using r_int2x_type = std::common_type_t<typename xquantity::ratio_int2x_type,
typename Quantity2::ratio_int2x_type>;
auto rr = detail::su_product<r_int_type, r_int2x_type>(x.unit(), y.unit());
r_repr_type r_scale = (::sqrt(rr.outer_scale_sq_)
* rr.outer_scale_factor_.template convert_to<r_repr_type>()
* static_cast<r_repr_type>(x.scale())
* static_cast<r_repr_type>(y.scale()));
return xquantity<r_repr_type, r_int_type>(r_scale,
rr.natural_unit_);
}
/** compute quotient @p x / @p y, where @p x and @p y are xquantities **/
template <typename Quantity2>
static constexpr
auto divide(const xquantity & x, const Quantity2 & y) {
using r_repr_type = std::common_type_t<typename xquantity::repr_type,
typename Quantity2::repr_type>;
using r_int_type = std::common_type_t<typename xquantity::ratio_int_type,
typename Quantity2::ratio_int_type>;
using r_int2x_type = std::common_type_t<typename xquantity::ratio_int2x_type,
typename Quantity2::ratio_int2x_type>;
auto rr = detail::su_ratio<r_int_type, r_int2x_type>(x.unit(), y.unit());
/* note: su_ratio() reports multiplicative outer scaling factors,
* so multiply is correct here
*/
r_repr_type r_scale = (::sqrt(rr.outer_scale_sq_)
* rr.outer_scale_factor_.template convert_to<r_repr_type>()
* static_cast<r_repr_type>(x.scale())
/ static_cast<r_repr_type>(y.scale()));
return xquantity<r_repr_type, r_int_type>(r_scale,
rr.natural_unit_);
}
/** compute sum @p x + @p y, where @p x and @p y are xquantities **/
template <typename Quantity2>
static constexpr
auto add(const xquantity & x, const Quantity2 & y) {
using r_repr_type = std::common_type_t<typename xquantity::repr_type,
typename Quantity2::repr_type>;
using r_int_type = std::common_type_t<typename xquantity::ratio_int_type,
typename Quantity2::ratio_int_type>;
using r_int2x_type = std::common_type_t<typename xquantity::ratio_int2x_type,
typename Quantity2::ratio_int2x_type>;
/* conversion to get y in same units as x: multiply by y/x */
auto rr = detail::su_ratio<r_int_type, r_int2x_type>(y.unit(), x.unit());
if (rr.natural_unit_.is_dimensionless()) {
r_repr_type r_scale = (static_cast<r_repr_type>(x.scale())
+ (::sqrt(rr.outer_scale_sq_)
* rr.outer_scale_factor_.template convert_to<r_repr_type>()
* static_cast<r_repr_type>(y.scale())));
return xquantity<r_repr_type, r_int_type>(r_scale, x.unit_.template to_repr<r_int_type>());
} else {
/* units don't match! */
return xquantity<r_repr_type, r_int_type>(std::numeric_limits<r_repr_type>::quiet_NaN(),
x.unit_.template to_repr<r_int_type>());
}
}
/** compute difference @p x - @p y, where @p x and @p y are xquantities **/
template <typename Quantity2>
static constexpr
auto subtract(const xquantity & x, const Quantity2 & y) {
using r_repr_type = std::common_type_t<typename xquantity::repr_type,
typename Quantity2::repr_type>;
using r_int_type = std::common_type_t<typename xquantity::ratio_int_type,
typename Quantity2::ratio_int_type>;
using r_int2x_type = std::common_type_t<typename xquantity::ratio_int2x_type,
typename Quantity2::ratio_int2x_type>;
/* conversion to get y in same units as x: multiply by y/x */
auto rr = detail::su_ratio<r_int_type, r_int2x_type>(y.unit(), x.unit());
if (rr.natural_unit_.is_dimensionless()) {
r_repr_type r_scale = (static_cast<r_repr_type>(x.scale())
- (::sqrt(rr.outer_scale_sq_)
* rr.outer_scale_factor_.template convert_to<r_repr_type>()
* static_cast<r_repr_type>(y.scale())));
return xquantity<r_repr_type, r_int_type>(r_scale, x.unit_.template to_repr<r_int_type>());
} else {
/* units don't match! */
return xquantity<r_repr_type, r_int_type>(std::numeric_limits<r_repr_type>::quiet_NaN(),
x.unit_.template to_repr<r_int_type>());
}
}
///@}
/** @defgroup xquantity-unit-conversion **/
///@{
/** create quantity representing the same value, but in units of @p unit2 **/
constexpr
auto rescale(const natural_unit<Int> & unit2) const {
/* conversion factor from .unit -> unit2*/
auto rr = detail::su_ratio<ratio_int_type,
ratio_int2x_type>(this->unit_, unit2);
if (rr.natural_unit_.is_dimensionless()) {
repr_type r_scale = (::sqrt(rr.outer_scale_sq_)
* rr.outer_scale_factor_.template convert_to<repr_type>()
* this->scale_);
return xquantity(r_scale, unit2);
} else {
return xquantity(std::numeric_limits<repr_type>::quiet_NaN(), unit2);
}
}
constexpr
auto rescale_ext(const scaled_unit<Int> & unit2) const {
/* conversion factor from .unit -> unit2*/
auto rr = detail::su_ratio<ratio_int_type,
ratio_int2x_type>(unit_,
unit2.natural_unit_);
if (rr.natural_unit_.is_dimensionless()) {
/* NOTE: test for unit .outer_scale_sq values to get constexpr result with c++23
* and integer dimension powers.
*
* NOTE: we don't intend to support mixed-unit quantities.
* If we change intention, will need to take into account
* (s_scaled_unit.outer_scale_factor_, s_scaled_unit.outer_scale_sq_)
*/
repr_type r_scale
= ((((rr.outer_scale_sq_ == 1.0)
&& (unit2.outer_scale_sq_ == 1.0))
? 1.0
: ::sqrt(rr.outer_scale_sq_ / unit2.outer_scale_sq_))
* rr.outer_scale_factor_.template convert_to<repr_type>()
* this->scale_
/ unit2.outer_scale_factor_.template convert_to<repr_type>());
return xquantity(r_scale, unit2);
} else {
return xquantity(std::numeric_limits<repr_type>::quiet_NaN(), unit2);
}
}
///@}
/** @defgroup xquantity-comparison-support xquantity comparison support methods **/
///@{
/** perform 3-way comparison between @c xquantity values @p x and @p y **/
template <typename Quantity2>
static constexpr
auto compare(const xquantity & x, const Quantity2 & y) {
xquantity y2 = y.rescale(x.unit_);
return x.scale() <=> y2.scale();
}
///@}
/** @defgroup xquantity-operators xquantity operators **/
///@{
/** add @p x in-place, converting units if necessary **/
template <typename Quantity2>
xquantity & operator+= (const Quantity2 & x) {
*this = *this + x;
return *this;
}
/** unary negation; preserves unit information **/
xquantity operator-() const {
return xquantity(-scale_, unit_);
}
/** subtract @p x in-place, converting units if necessary **/
template <typename Quantity2>
xquantity & operator-= (const Quantity2 & x) {
*this = *this - x;
return *this;
}
/** multiply @p x in-place, converting units if necessary
*
* @note: unlike @c quantity::operator*=, may change dimension of lhs
**/
template <typename Quantity2>
xquantity & operator*= (const Quantity2 & x) {
*this = *this * x;
return *this;
}
/** divide @p x in-place, converting units if necessary
*
* @note: unlike @c quantity::operator/=, may change dimension of lhs
**/
template <typename Quantity2>
xquantity & operator/= (const Quantity2 & x) {
*this = *this / x;
return *this;
}
///@}
private:
/** @defgroup xquantity-instance-vars **/
///@{
/** quantity represents this multiple of a unit amount **/
Repr scale_ = Repr{};
/** unit for this quantity **/
natural_unit<Int> unit_;
///@}
}; /*xquantity*/
/** note: won't have constexpr result until c++26 (when @c sqrt(), @c pow() are constexpr)
**/
template <typename Repr = double,
typename Int = std::int64_t>
inline constexpr xquantity<Repr, Int>
unit_qty(const scaled_unit<Int> & u)
{
return xquantity<Repr, Int>
(u.outer_scale_factor_.template convert_to<double>() * ::sqrt(u.outer_scale_sq_),
u.natural_unit_);
}
/** note: won't have constexpr result until c++26 (when @c sqrt(), @c pow() are constexpr)
**/
template <typename Repr = double,
typename Int = std::int64_t>
inline constexpr xquantity<Repr, Int>
natural_unit_qty(const natural_unit<Int> & nu) {
return xquantity<Repr, Int>(1.0, nu);
}
/** note: won't have constexpr result until c++26 (when @c sqrt(), @c pow() are constexpr)
**/
template <typename Q1, typename Q2>
requires (quantity_concept<Q1>
&& quantity_concept<Q2>
&& (!Q1::always_constexpr_unit || !Q2::always_constexpr_unit))
constexpr auto
operator* (const Q1 & x, const Q2 & y)
{
return Q1::multiply(x, y);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator* (double x, const Quantity & y)
{
return y.scale_by(x);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator* (const Quantity & x, double y)
{
return x.scale_by(y);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity, typename Quantity2>
requires quantity_concept<Quantity> && quantity_concept<Quantity2>
constexpr auto
operator/ (const Quantity & x, const Quantity2 & y)
{
return Quantity::divide(x, y);
}
/** note: doesn not require unit scaling, so constexpr with c++23 **/
template <typename Quantity, typename Dimensionless>
requires quantity_concept<Quantity> && std::is_arithmetic_v<Dimensionless>
constexpr auto
operator/ (const Quantity & x, Dimensionless y)
{
return x.divide_by(y);
}
/** note: doesn not require unit scaling, so constexpr with c++23 **/
template <typename Dimensionless, typename Quantity>
requires std::is_arithmetic_v<Dimensionless> && quantity_concept<Quantity>
constexpr auto
operator/ (Dimensionless x, const Quantity & y)
{
return y.divide_into(x);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity, typename Quantity2>
requires quantity_concept<Quantity> && quantity_concept<Quantity2>
constexpr auto
operator+ (const Quantity & x, const Quantity2 & y)
{
return Quantity::add(x, y);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator+ (const Quantity & x, double y)
{
return x + Quantity(y, u::dimensionless);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator+ (double x, const Quantity & y)
{
return Quantity(x, u::dimensionless) + y;
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity, typename Quantity2>
requires (quantity_concept<Quantity>
&& quantity_concept<Quantity2>)
constexpr auto
operator- (const Quantity & x, const Quantity2 & y)
{
return Quantity::subtract(x, y);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator- (const Quantity & x, double y)
{
return x - Quantity(y, u::dimensionless);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator- (double x, const Quantity & y)
{
return Quantity(x, u::dimensionless) - y;
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator== (const Quantity & x, double y)
{
return (x == Quantity(y, u::dimensionless));
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity>
requires quantity_concept<Quantity>
constexpr auto
operator== (double x, const Quantity & y)
{
return (Quantity(x, u::dimensionless) == y);
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity, double>
requires quantity_concept<Quantity>
constexpr auto
operator<=> (const Quantity & x, double y)
{
return Quantity::compare(x, Quantity(y, u::dimensionless));
}
/** note: won't have constexpr result until c++26 (when ::sqrt(), ::pow() are constexpr)
**/
template <typename Quantity, double>
requires quantity_concept<Quantity>
constexpr auto
operator<=> (double x, const Quantity & y)
{
return Quantity::compare(Quantity(x, u::dimensionless), y);
}
namespace xu {
constexpr auto nanogram = xquantity(1.0, u::nanogram);
}
} /*namespace qty*/
} /*namespace xo*/
/** end xquantity.hpp **/

View file

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