15#ifndef CONFIG_THERMO_STD_FORMAT
16#if __has_include(<format>) && defined(__cpp_lib_format)
17#define CONFIG_THERMO_STD_FORMAT 1
19#define CONFIG_THERMO_STD_FORMAT 0
23#if CONFIG_THERMO_STD_FORMAT
28static_assert(__cplusplus >= 202302L,
"thermo requires C++23 or later");
40template<
typename Rep,
typename Precision = std::ratio<1>>
50template<
typename Rep,
typename Precision>
54template<
typename T,
template<
typename...>
class Template>
55struct _is_specialization_of : std::false_type {};
57template<
template<
typename...>
class Template,
typename... Args>
58struct _is_specialization_of<Template<Args...>, Template> : std::true_type {};
60template<
typename T,
template<
typename...>
class Template>
61inline constexpr bool _is_specialization_of_v = _is_specialization_of<T, Template>::value;
64concept not_delta = !_is_specialization_of_v<Rep, delta>;
69 static constexpr Rep zero() noexcept {
return Rep(0); }
72 static constexpr Rep max() noexcept {
return std::numeric_limits<Rep>::max(); }
75 static constexpr Rep min() noexcept {
return std::numeric_limits<Rep>::lowest(); }
79struct _is_ratio : std::false_type {};
81template<std::
intmax_t Num, std::
intmax_t Denom>
82struct _is_ratio<
std::ratio<Num, Denom>> : std::true_type {};
85struct treat_as_inexact : std::bool_constant<std::floating_point<T>> {};
88inline constexpr bool treat_as_inexact_v = treat_as_inexact<T>::value;
90consteval intmax_t _gcd(intmax_t m, intmax_t n)
noexcept {
99template<
typename R1,
typename R2>
100inline constexpr intmax_t _safe_ratio_divide_den = [] {
101 constexpr intmax_t g1 = _gcd(R1::num, R2::num);
102 constexpr intmax_t g2 = _gcd(R1::den, R2::den);
103 return (R1::den / g2) * (R2::num / g1);
106template<
typename From,
typename To>
107concept _harmonic_precision = _safe_ratio_divide_den<From, To> == 1;
120template<
typename Rep,
typename Precision>
123 static_assert(_is_ratio<Precision>::value,
"precision must be a specialization of std::ratio");
124 static_assert(Precision::num > 0,
"precision must be positive");
145 template<
typename Rep2>
146 requires std::convertible_to<const Rep2&, rep> && (treat_as_inexact_v<rep> || !treat_as_inexact_v<Rep2>)
147 constexpr explicit delta(
const Rep2& r)
148 : _r(
static_cast<rep>(r)) {}
160 template<
typename Rep2,
typename Precision2>
161 requires std::convertible_to<const Rep2&, rep> &&
162 (treat_as_inexact_v<rep> || (_harmonic_precision<Precision2, precision> && !treat_as_inexact_v<Rep2>))
215 requires(!treat_as_inexact_v<rep>)
222 requires(!treat_as_inexact_v<rep>)
229 static constexpr delta zero() noexcept {
return delta(delta_values<rep>::zero()); }
232 static constexpr delta min() noexcept {
return delta(delta_values<rep>::min()); }
235 static constexpr delta max() noexcept {
return delta(delta_values<rep>::max()); }
251template<
typename ToDelta,
typename Rep,
typename Precision>
253 if constexpr (std::is_same_v<ToDelta, delta<Rep, Precision>>) {
256 using to_rep =
typename ToDelta::rep;
257 using to_precision =
typename ToDelta::precision;
258 using cf = std::ratio_divide<Precision, to_precision>;
259 using cr = std::common_type_t<to_rep, Rep, intmax_t>;
261 if constexpr (cf::den == 1 && cf::num == 1) {
262 return ToDelta(
static_cast<to_rep
>(d.
count()));
263 }
else if constexpr (cf::den == 1) {
264 return ToDelta(
static_cast<to_rep
>(
static_cast<cr
>(d.
count()) *
static_cast<cr
>(cf::num)));
265 }
else if constexpr (cf::num == 1) {
266 return ToDelta(
static_cast<to_rep
>(
static_cast<cr
>(d.
count()) /
static_cast<cr
>(cf::den)));
269 static_cast<to_rep
>(
static_cast<cr
>(d.
count()) *
static_cast<cr
>(cf::num) /
static_cast<cr
>(cf::den))
287template<
typename ToDelta,
typename Rep,
typename Precision>
308template<
typename ToDelta,
typename Rep,
typename Precision>
329template<
typename ToDelta,
typename Rep,
typename Precision>
347template<
typename ToDelta,
typename Rep,
typename Precision>
370 if (
d.count() >= 0) {
381template<
typename Rep1,
typename Precision1,
typename Rep2,
typename Precision2>
389template<
typename Rep1,
typename Precision1,
typename Rep2,
typename Precision2>
397template<
typename Rep1,
typename Precision,
typename Rep2>
398 requires not_delta<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>>
402 return cd(
cd(
d).count() *
r);
406template<
typename Rep1,
typename Rep2,
typename Precision>
407 requires not_delta<Rep1> && std::convertible_to<const Rep1&, std::common_type_t<Rep1, Rep2>>
414template<
typename Rep1,
typename Precision,
typename Rep2>
415 requires not_delta<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>>
419 return cd(
cd(
d).count() /
s);
423template<
typename Rep1,
typename Precision1,
typename Rep2,
typename Precision2>
425 -> std::common_type_t<Rep1, Rep2> {
431template<
typename Rep1,
typename Precision,
typename Rep2>
432 requires not_delta<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>> &&
437 return cd(
cd(
d).count() %
s);
441template<
typename Rep1,
typename Precision1,
typename Rep2,
typename Precision2>
449template<
typename Rep1,
typename Precision1,
typename Rep2,
typename Precision2>
455template<
typename Rep1,
typename Precision1,
typename Rep2,
typename Precision2>
456 requires std::three_way_comparable<std::common_type_t<Rep1, Rep2>>
483template<
typename Ratio>
485 static constexpr const char*
value =
nullptr;
489 static constexpr const char*
value =
"";
493 static constexpr const char*
value =
"d";
497 static constexpr const char*
value =
"m";
499template<
typename Ratio>
500inline constexpr const char*
_si_prefix_v = _si_prefix<typename Ratio::type>::value;
505template<
typename Precision>
508 using degree = std::ratio<1>;
509 static constexpr const char*
suffix =
"°C";
512 using degree = std::ratio<5, 9>;
513 static constexpr const char*
suffix =
"°F";
529template<
typename OutputIt>
531 for (; *
s !=
'\0'; ++
s) {
542template<
typename Rep,
typename Precision>
546 using dpc = std::ratio_divide<typename delta<Rep, Precision>::precision,
typename unit::degree>;
547 static_assert(
_si_prefix_v<dpc> !=
nullptr,
"thermo: precision has no SI prefix");
562 using offset = std::ratio<27315, 100>;
563 using degree = std::ratio<1>;
564 static constexpr const char*
suffix =
"°C";
573 using offset = std::ratio<0>;
574 using degree = std::ratio<1>;
575 static constexpr const char*
suffix =
"K";
584 using offset = std::ratio<45967, 180>;
585 using degree = std::ratio<5, 9>;
586 static constexpr const char*
suffix =
"°F";
597template<
typename Scale>
598inline constexpr bool _is_relative_scale = std::ratio_not_equal_v<typename Scale::offset, std::ratio<0>>;
601template<
typename Scale,
typename Delta = delta<
int64_t>>
605template<
typename Scale1,
typename Delta1,
typename Scale2,
typename Delta2>
607 using from_prec =
typename Delta1::precision;
608 using to_prec =
typename Delta2::precision;
609 using from_off =
typename Scale1::offset;
610 using to_off =
typename Scale2::offset;
612 using prec_ratio = std::ratio_divide<from_prec, to_prec>;
613 using offset_diff = std::ratio_subtract<from_off, to_off>;
616 static constexpr bool value = prec_ratio::den == 1 && offset_in_target_units::den == 1;
619template<
typename Scale1,
typename Delta1,
typename Scale2,
typename Delta2>
629template<
typename Scale,
typename Delta>
646template<
typename ToTemp,
typename Scale,
typename Delta>
659template<
typename Scale,
typename Delta>
669 using rep =
typename Delta::rep;
680 template<
typename Rep2>
683 : _d(
static_cast<rep>(
r)) {}
693 template<
typename Delta2>
694 requires(!std::is_same_v<Delta, Delta2>) &&
702 template<
typename Delta2>
709 template<
typename Scale2,
typename Delta2>
710 requires(!std::is_same_v<temperature, temperature<Scale2, Delta2>>) &&
717 template<
typename Scale2,
typename Delta2>
718 requires(!std::is_same_v<temperature, temperature<Scale2, Delta2>>) &&
756 template<
typename Rep2,
typename Precision2>
762 template<
typename Rep2,
typename Precision2>
778template<
typename ToTemp,
typename Scale,
typename Delta>
780 using ToScale =
typename ToTemp::scale;
781 using ToDelta =
typename ToTemp::delta_type;
782 using to_rep =
typename ToDelta::rep;
784 if constexpr (std::is_same_v<Scale, ToScale> && std::is_same_v<Delta, ToDelta>) {
786 }
else if constexpr (std::is_same_v<Scale, ToScale>) {
789 using from_prec =
typename Delta::precision;
790 using from_off =
typename Scale::offset;
791 using to_prec =
typename ToDelta::precision;
792 using to_off =
typename ToScale::offset;
794 using offset_diff = std::ratio_subtract<from_off, to_off>;
795 using common_rep = std::common_type_t<typename Delta::rep, to_rep, intmax_t>;
799 using prec_ratio = std::ratio_divide<from_prec, to_prec>;
800 using offset_ratio = std::ratio_divide<offset_diff, to_prec>;
803 constexpr double pr =
static_cast<double>(prec_ratio::num) / prec_ratio::den;
804 constexpr double or_ =
static_cast<double>(offset_ratio::num) / offset_ratio::den;
809 static_cast<intmax_t>(prec_ratio::den) *
static_cast<intmax_t>(offset_ratio::den);
820template<
typename Scale,
typename Delta1,
typename Delta2>
823 using cd = std::common_type_t<Delta1, Delta2>;
828template<
typename Scale,
typename Delta1,
typename Delta2>
831 using cd = std::common_type_t<Delta1, Delta2>;
843template<
typename Scale,
typename Delta1,
typename Delta2>
845 -> std::common_type_t<Delta1, Delta2> {
846 using cd = std::common_type_t<Delta1, Delta2>;
847 return cd(
lhs.count() -
rhs.count());
851template<
typename Scale,
typename Delta1,
typename Rep2,
typename Precision2>
854 using result_delta = std::common_type_t<Delta1, delta<Rep2, Precision2>>;
859template<
typename Rep1,
typename Precision1,
typename Scale,
typename Delta2>
866template<
typename Scale,
typename Delta1,
typename Rep2,
typename Precision2>
869 using result_delta = std::common_type_t<Delta1, delta<Rep2, Precision2>>;
873template<
typename Scale,
typename Delta1,
typename Delta2>
875 return lhs.count() ==
rhs.count();
878template<
typename Scale,
typename Delta1,
typename Delta2>
879 requires std::three_way_comparable<std::common_type_t<typename Delta1::rep, typename Delta2::rep>>
881 return lhs.count() <=>
rhs.count();
907template<
typename Scale,
typename Rep,
typename Precision>
910 using dpc = std::ratio_divide<typename delta<Rep, Precision>::precision,
typename Scale::degree>;
911 static_assert(
_si_prefix_v<dpc> !=
nullptr,
"thermo: precision has no SI prefix");
919template<
typename Rep1,
typename Precision1,
typename Rep2,
typename Precision2>
922 using common_precision = std::ratio<
924 (Precision1::den /
thermo::_gcd(Precision1::den, Precision2::den)) * Precision2::den>;
930template<
typename Scale,
typename Delta1,
typename Delta2>
935#if CONFIG_THERMO_STD_FORMAT
940template<
typename Scale,
typename Rep,
typename Precision>
941struct formatter<
thermo::temperature<Scale, thermo::delta<Rep, Precision>>> {
944 using _dpc =
typename ratio_divide<typename _delta_t::precision, typename Scale::degree>::type;
946 std::formatter<double> _num;
947 bool _has_spec =
false;
950 constexpr auto parse(format_parse_context& ctx) {
951 auto it = ctx.begin();
952 if (it == ctx.end() || *it ==
'}') {
953 if (_prefix ==
nullptr) {
954 throw format_error(
"thermo: precision has no SI prefix; use an explicit format spec");
959 return _num.parse(ctx);
962 template<
typename FormatContext>
965 double degrees =
static_cast<double>(t.
count()) * _dpc::num / _dpc::den;
966 auto out = _num.format(degrees, ctx);
969 auto out = std::format_to(ctx.out(),
"{}", t.
count());
979template<
typename Rep,
typename Precision>
980struct formatter<
thermo::delta<Rep, Precision>> {
984 using _dpc =
typename ratio_divide<typename _delta_t::precision, typename _unit::degree>::type;
986 std::formatter<double> _num;
987 bool _has_spec =
false;
990 constexpr auto parse(format_parse_context& ctx) {
991 auto it = ctx.begin();
992 if (it == ctx.end() || *it ==
'}') {
993 if (_prefix ==
nullptr) {
994 throw format_error(
"thermo: precision has no SI prefix; use an explicit format spec");
999 return _num.parse(ctx);
1002 template<
typename FormatContext>
1003 auto format(
const _delta_t& d, FormatContext& ctx)
const {
1005 double degrees =
static_cast<double>(d.count()) * _dpc::num / _dpc::den;
1006 auto out = _num.format(degrees, ctx);
1010 auto out = std::format_to(ctx.out(),
"{}", d.count());
1027template<
unsigned long long Value,
unsigned long long Power>
1029 static constexpr unsigned long long value = 10 * pow10<Value, Power - 1>::value;
1032template<
unsigned long long Value>
1033struct pow10<Value, 0> {
1034 static constexpr unsigned long long value = Value;
1037template<
char... Digits>
1040template<
char D,
char... Rest>
1041struct parse_int<D, Rest...> {
1042 static_assert(D >=
'0' && D <=
'9',
"invalid digit");
1043 static constexpr unsigned long long value = pow10<D -
'0',
sizeof...(Rest)>::value + parse_int<Rest...>::value;
1047struct parse_int<D> {
1048 static_assert(D >=
'0' && D <=
'9',
"invalid digit");
1049 static constexpr unsigned long long value = D -
'0';
1052template<
typename Delta,
char... Digits>
1053constexpr Delta check_overflow() {
1054 using parsed = parse_int<Digits...>;
1055 constexpr typename Delta::rep repval = parsed::value;
1057 repval >= 0 &&
static_cast<unsigned long long>(repval) == parsed::value,
1058 "literal value cannot be represented by delta type"
1060 return Delta(repval);
1067template<
char... Digits>
1069 return thermo::celsius(detail::check_overflow<thermo::delta_celsius, Digits...>());
1073template<
char... Digits>
1075 return thermo::decicelsius(detail::check_overflow<thermo::delta_decicelsius, Digits...>());
1079template<
char... Digits>
1085template<
char... Digits>
1087 return thermo::kelvin(detail::check_overflow<thermo::delta_kelvin, Digits...>());
1091template<
char... Digits>
1093 return thermo::decikelvin(detail::check_overflow<thermo::delta_decikelvin, Digits...>());
1097template<
char... Digits>
1099 return thermo::millikelvin(detail::check_overflow<thermo::delta_millikelvin, Digits...>());
1103template<
char... Digits>
1105 return thermo::fahrenheit(detail::check_overflow<thermo::delta_fahrenheit, Digits...>());
1109template<
char... Digits>
1115template<
char... Digits>
1121template<
char... Digits>
1127template<
char... Digits>
1133template<
char... Digits>
1139template<
char... Digits>
1145template<
char... Digits>
1151template<
char... Digits>
A temperature difference with a representation and precision.
constexpr delta & operator*=(const rep &r)
static constexpr delta zero() noexcept
Returns a zero-length delta.
constexpr delta & operator++()
static constexpr delta min() noexcept
Returns the minimum (most negative) representable delta.
constexpr delta()=default
Constructs a zero delta.
constexpr delta & operator/=(const rep &r)
static constexpr delta max() noexcept
Returns the maximum representable delta.
Rep rep
The representation type.
constexpr delta & operator--()
constexpr rep count() const
Returns the tick count.
constexpr delta operator--(int)
delta & operator=(const delta &)=default
constexpr delta & operator-=(const delta &d)
constexpr delta & operator%=(const delta &d)
typename Precision::type precision
The precision as a std::ratio.
delta(const delta &)=default
constexpr delta< typename std::common_type< rep >::type, precision > operator+() const
constexpr delta operator++(int)
constexpr delta(const Rep2 &r)
Constructs from a tick count.
constexpr delta & operator+=(const delta &d)
constexpr delta< typename std::common_type< rep >::type, precision > operator-() const
constexpr delta & operator%=(const rep &r)
An absolute temperature on a given scale.
typename Delta::rep rep
The representation type.
Delta delta_type
The delta type.
constexpr temperature()=default
Constructs a temperature at the scale's zero point.
constexpr temperature operator-() const
Negates the temperature (e.g.
constexpr temperature & operator++()
constexpr temperature(const temperature< Scale, Delta2 > &t)
constexpr temperature(const temperature< Scale2, Delta2 > &t)
constexpr temperature(const Rep2 &r)
Constructs from a tick count.
static constexpr temperature max() noexcept
Returns the maximum representable temperature.
constexpr temperature operator--(int)
constexpr temperature(const temperature< Scale, Delta2 > &t)
temperature(const temperature &)=default
constexpr temperature(const Delta &d)
Constructs from a delta.
static constexpr temperature min() noexcept
Returns the minimum representable temperature.
constexpr temperature operator++(int)
constexpr temperature & operator+=(const delta< Rep2, Precision2 > &d)
Scale scale
The temperature scale.
constexpr temperature & operator--()
constexpr rep count() const
Returns the tick count.
constexpr temperature & operator-=(const delta< Rep2, Precision2 > &d)
temperature & operator=(const temperature &)=default
constexpr temperature(const temperature< Scale2, Delta2 > &t)
User-defined literals for temperature types.
Temperature types and utilities.
temperature< celsius_scale > celsius
Celsius with 1 degree precision.
constexpr auto operator-(const delta< Rep1, Precision1 > &lhs, const delta< Rep2, Precision2 > &rhs) -> std::common_type_t< delta< Rep1, Precision1 >, delta< Rep2, Precision2 > >
Returns the difference of two deltas.
std::string to_string(const delta< Rep, Precision > &d)
Renders a delta as its exact count with a precision-qualified unit, e.g.
temperature< fahrenheit_scale, delta< int64_t, std::ratio< 5, 9000 > > > millifahrenheit
Fahrenheit with 0.001 degree precision.
delta< int64_t, std::milli > delta_millicelsius
Delta with 0.001 degree precision (Celsius/Kelvin).
constexpr ToDelta round(const delta< Rep, Precision > &d)
Rounds a delta to the nearest representable value in the target precision.
constexpr auto operator+(const delta< Rep1, Precision1 > &lhs, const delta< Rep2, Precision2 > &rhs) -> std::common_type_t< delta< Rep1, Precision1 >, delta< Rep2, Precision2 > >
Returns the sum of two deltas.
constexpr ToDelta delta_cast(const delta< Rep, Precision > &d)
Converts a delta to a different precision or representation.
constexpr bool operator==(const delta< Rep1, Precision1 > &lhs, const delta< Rep2, Precision2 > &rhs)
delta< int64_t, std::milli > delta_millikelvin
Delta with 0.001 degree precision (Celsius/Kelvin).
constexpr auto operator*(const delta< Rep1, Precision > &d, const Rep2 &r) -> delta< std::common_type_t< Rep1, Rep2 >, Precision >
Multiplies a delta by a scalar.
delta< int64_t, std::ratio< 5, 9000 > > delta_millifahrenheit
Delta with 0.001°F precision.
temperature< fahrenheit_scale, delta< int64_t, std::ratio< 5, 9 > > > fahrenheit
Fahrenheit with 1 degree precision.
constexpr ToDelta ceil(const delta< Rep, Precision > &d)
Rounds a delta up to the nearest representable value in the target precision.
delta< int64_t, std::ratio< 5, 9 > > delta_fahrenheit
Delta with 1°F precision.
constexpr auto difference(const temperature< Scale, Delta1 > &lhs, const temperature< Scale, Delta2 > &rhs) -> std::common_type_t< Delta1, Delta2 >
Returns the difference between two temperatures as a delta.
constexpr ToDelta floor(const delta< Rep, Precision > &d)
Rounds a delta down to the nearest representable value in the target precision.
delta< int64_t > delta_kelvin
Delta with 1 degree precision (Celsius/Kelvin).
temperature< celsius_scale, delta< int64_t, std::deci > > decicelsius
Celsius with 0.1 degree precision.
temperature< kelvin_scale, delta< int64_t, std::milli > > millikelvin
Kelvin with 0.001 degree precision.
constexpr ToTemp temperature_cast(const temperature< Scale, Delta > &t)
Converts a temperature to a different scale or precision.
constexpr auto operator<=>(const delta< Rep1, Precision1 > &lhs, const delta< Rep2, Precision2 > &rhs)
temperature< fahrenheit_scale, delta< int64_t, std::ratio< 5, 90 > > > decifahrenheit
Fahrenheit with 0.1 degree precision.
temperature< kelvin_scale, delta< int64_t, std::deci > > decikelvin
Kelvin with 0.1 degree precision.
temperature< kelvin_scale > kelvin
Kelvin with 1 degree precision.
temperature< celsius_scale, delta< int64_t, std::milli > > millicelsius
Celsius with 0.001 degree precision.
constexpr auto operator/(const delta< Rep1, Precision > &d, const Rep2 &s) -> delta< std::common_type_t< Rep1, Rep2 >, Precision >
Divides a delta by a scalar.
delta< int64_t > delta_celsius
Delta with 1 degree precision (Celsius/Kelvin).
constexpr ToDelta trunc(const delta< Rep, Precision > &d)
Rounds a delta toward zero to the nearest representable value in the target precision.
Trait to detect delta specializations.