quanta 0.1.0
Type-safe units for temperature, frequency, electrical and spatial quantities, modeled after std::chrono
Loading...
Searching...
No Matches
thermo.hpp
Go to the documentation of this file.
1#pragma once
2
8#include <assert.h>
9#include <charconv>
10#include <compare>
11#include <concepts>
12#include <cstdint>
13#include <cstdlib>
14#include <limits>
15#include <quanta/core.hpp>
16#include <ratio>
17#include <string>
18#include <system_error>
19
50namespace quanta::thermo {
51
52// The shared trait, re-exported for convenience; specialize quanta::treat_as_inexact.
55
56template<typename Rep, typename Precision = std::ratio<1>>
57class delta;
58
63template<typename _T>
64struct is_delta : std::false_type {};
65
66template<typename Rep, typename Precision>
67struct is_delta<delta<Rep, Precision>> : std::true_type {};
68
70template<typename Rep>
71concept not_delta = !_is_specialization_of_v<Rep, delta>;
72
73template<typename Rep>
74struct delta_values {
76 static constexpr Rep zero() noexcept { return Rep(0); }
77
79 static constexpr Rep max() noexcept { return std::numeric_limits<Rep>::max(); }
80
82 static constexpr Rep min() noexcept { return std::numeric_limits<Rep>::lowest(); }
83};
84
97template<typename Rep, typename Precision>
98class delta {
99 static_assert(!is_delta<Rep>::value, "rep cannot be a quanta::thermo::delta");
100 static_assert(_is_ratio<Precision>::value, "precision must be a specialization of std::ratio");
101 static_assert(Precision::num > 0, "precision must be positive");
102
103public:
105 using rep = Rep;
107 using precision = typename Precision::type;
108
110 constexpr delta() = default;
111 delta(const delta&) = default;
112
122 template<typename Rep2>
123 requires std::convertible_to<const Rep2&, rep> && (treat_as_inexact_v<rep> || !treat_as_inexact_v<Rep2>)
124 constexpr explicit delta(const Rep2& r)
125 : _r(static_cast<rep>(r)) {}
126
140 template<typename Rep2, typename Precision2>
141 requires std::convertible_to<const Rep2&, rep> &&
142 detail::_lossless_conversion<Rep2, Precision2, rep, precision> &&
143 detail::_quantity_castable<delta<Rep2, Precision2>, delta>
144 constexpr delta(const delta<Rep2, Precision2>& temp)
145 : _r(delta_cast<delta>(temp).count()) {}
146
163 template<typename Rep2, typename Precision2>
164 requires(!detail::_lossless_conversion<Rep2, Precision2, rep, precision> ||
165 !std::convertible_to<const Rep2&, rep>) &&
166 detail::_quantity_castable<delta<Rep2, Precision2>, delta>
167 constexpr explicit delta(const delta<Rep2, Precision2>& d)
168 : _r(delta_cast<delta>(d).count()) {}
169
170 ~delta() = default;
171 delta& operator=(const delta&) = default;
172
174 constexpr rep count() const { return _r; }
175
179
183
184 constexpr delta& operator++() {
185 ++_r;
186 return *this;
187 }
188
189 constexpr delta operator++(int) { return delta(_r++); }
190
191 constexpr delta& operator--() {
192 --_r;
193 return *this;
194 }
195
196 constexpr delta operator--(int) { return delta(_r--); }
197
198 constexpr delta& operator+=(const delta& d) {
199 _r += d.count();
200 return *this;
201 }
202
203 constexpr delta& operator-=(const delta& d) {
204 _r -= d.count();
205 return *this;
206 }
207
208 constexpr delta& operator*=(const rep& r) {
209 _r *= r;
210 return *this;
211 }
212
213 constexpr delta& operator/=(const rep& r) {
214 _r /= r;
215 return *this;
216 }
217
218 constexpr delta& operator%=(const rep& r)
219 requires(!treat_as_inexact_v<rep>)
220 {
221 _r %= r;
222 return *this;
223 }
224
225 constexpr delta& operator%=(const delta& d)
226 requires(!treat_as_inexact_v<rep>)
227 {
228 _r %= d.count();
229 return *this;
230 }
231
233 static constexpr delta zero() noexcept { return delta(delta_values<rep>::zero()); }
234
236 static constexpr delta min() noexcept { return delta(delta_values<rep>::min()); }
237
239 static constexpr delta max() noexcept { return delta(delta_values<rep>::max()); }
240
241private:
242 rep _r{};
243};
244
259template<typename ToDelta, typename Rep, typename Precision>
260 requires detail::_quantity_cast_requirements<delta<Rep, Precision>, ToDelta>
261constexpr ToDelta delta_cast(const delta<Rep, Precision>& d) {
262 return detail::_quantity_cast<ToDelta>(d);
263}
264
281template<typename ToDelta, typename Rep, typename Precision>
282constexpr ToDelta ceil(const delta<Rep, Precision>& d) {
283 return detail::_ceil_cast(delta_cast<ToDelta>(d), d);
284}
285
302template<typename ToDelta, typename Rep, typename Precision>
303constexpr ToDelta floor(const delta<Rep, Precision>& d) {
304 return detail::_floor_cast(delta_cast<ToDelta>(d), d);
305}
306
319template<typename ToDelta, typename Rep, typename Precision>
320constexpr ToDelta trunc(const delta<Rep, Precision>& d) {
321 return delta_cast<ToDelta>(d);
322}
323
339template<typename ToDelta, typename Rep, typename Precision>
340constexpr ToDelta round(const delta<Rep, Precision>& d) {
341 return detail::_round_cast<ToDelta>(d, [&d] { return floor<ToDelta>(d); });
342}
343
358template<typename Rep, typename Precision>
360 return detail::_abs(d);
361}
362
364template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
365constexpr auto operator+(const delta<Rep1, Precision1>& lhs, const delta<Rep2, Precision2>& rhs)
366 -> std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>> {
367 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
368 return cd(cd(lhs).count() + cd(rhs).count());
369}
370
372template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
373constexpr auto operator-(const delta<Rep1, Precision1>& lhs, const delta<Rep2, Precision2>& rhs)
374 -> std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>> {
375 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
376 return cd(cd(lhs).count() - cd(rhs).count());
377}
378
380template<typename Rep1, typename Precision, typename Rep2>
381 requires not_delta<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>>
382constexpr auto operator*(const delta<Rep1, Precision>& d, const Rep2& r)
384 using cd = delta<std::common_type_t<Rep1, Rep2>, Precision>;
385 return cd(cd(d).count() * r);
386}
387
389template<typename Rep1, typename Rep2, typename Precision>
390 requires not_delta<Rep1> && std::convertible_to<const Rep1&, std::common_type_t<Rep1, Rep2>>
391constexpr auto operator*(const Rep1& r, const delta<Rep2, Precision>& d)
393 return d * r;
394}
395
397template<typename Rep1, typename Precision, typename Rep2>
398 requires not_delta<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>>
399constexpr auto operator/(const delta<Rep1, Precision>& d, const Rep2& s)
401 using cd = delta<std::common_type_t<Rep1, Rep2>, Precision>;
402 return cd(cd(d).count() / s);
403}
404
406template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
407constexpr auto operator/(const delta<Rep1, Precision1>& lhs, const delta<Rep2, Precision2>& rhs)
408 -> std::common_type_t<Rep1, Rep2> {
409 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
410 return cd(lhs).count() / cd(rhs).count();
411}
412
414template<typename Rep1, typename Precision, typename Rep2>
415 requires not_delta<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>> &&
416 (!treat_as_inexact_v<Rep1> && !treat_as_inexact_v<Rep2>)
417constexpr auto operator%(const delta<Rep1, Precision>& d, const Rep2& s)
419 using cd = delta<std::common_type_t<Rep1, Rep2>, Precision>;
420 return cd(cd(d).count() % s);
421}
422
424template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
425 requires(!treat_as_inexact_v<Rep1> && !treat_as_inexact_v<Rep2>)
426constexpr auto operator%(const delta<Rep1, Precision1>& lhs, const delta<Rep2, Precision2>& rhs)
427 -> std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>> {
428 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
429 return cd(cd(lhs).count() % cd(rhs).count());
430}
431
432template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
433constexpr bool operator==(const delta<Rep1, Precision1>& lhs, const delta<Rep2, Precision2>& rhs) {
434 using ct = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
435 return ct(lhs).count() == ct(rhs).count();
436}
437
438template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
439 requires std::three_way_comparable<std::common_type_t<Rep1, Rep2>>
440constexpr auto operator<=>(const delta<Rep1, Precision1>& lhs, const delta<Rep2, Precision2>& rhs) {
441 using ct = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
442 return ct(lhs).count() <=> ct(rhs).count();
443}
444
463
465// SI prefix for a degrees-per-count ratio; nullptr when unmapped.
466template<typename Ratio>
467struct _si_prefix {
468 static constexpr const char* value = nullptr;
469};
470template<>
471struct _si_prefix<std::ratio<1>> {
472 static constexpr const char* value = "";
473};
474template<>
475struct _si_prefix<std::ratio<1, 10>> {
476 static constexpr const char* value = "d";
477};
478template<>
479struct _si_prefix<std::ratio<1, 1000>> {
480 static constexpr const char* value = "m";
481};
482template<typename Ratio>
483inline constexpr const char* _si_prefix_v = _si_prefix<typename Ratio::type>::value;
484
485// Display family (degree size + unit suffix) for a delta precision, keyed on
486// NORMALIZED ratios. Undefined primary template: unmapped precisions have no
487// determinable unit and are not formattable.
488template<typename Precision>
489struct _delta_unit;
490struct _celsius_delta_unit {
491 using degree = std::ratio<1>;
492 static constexpr const char* suffix = "°C";
493};
494struct _fahrenheit_delta_unit {
495 using degree = std::ratio<5, 9>;
496 static constexpr const char* suffix = "°F";
497};
498template<>
499struct _delta_unit<std::ratio<1>> : _celsius_delta_unit {};
500template<>
501struct _delta_unit<std::ratio<1, 10>> : _celsius_delta_unit {};
502template<>
503struct _delta_unit<std::ratio<1, 1000>> : _celsius_delta_unit {};
504template<>
505struct _delta_unit<std::ratio<5, 9>> : _fahrenheit_delta_unit {};
506template<>
507struct _delta_unit<std::ratio<1, 18>> : _fahrenheit_delta_unit {}; // 5/90 normalized
508template<>
509struct _delta_unit<std::ratio<1, 1800>> : _fahrenheit_delta_unit {}; // 5/9000 normalized
510
511// How a delta or temperature type is displayed: the delta marker ("Δ" or
512// ""), the unit suffix of its scale, the precision in degrees of that unit
513// (dpc), and the SI prefix for that precision (nullptr when it has none).
514template<typename T>
515struct _display_traits;
516
517template<typename Rep, typename Precision>
518struct _display_traits<delta<Rep, Precision>> {
519private:
520 using _precision = typename delta<Rep, Precision>::precision;
521 using _unit = _delta_unit<_precision>; // hard error if unmapped
522
523public:
524 using dpc = typename std::ratio_divide<_precision, typename _unit::degree>::type;
525 static constexpr const char* mark = "Δ";
526 static constexpr const char* unit = _unit::suffix;
527 static constexpr const char* prefix = _si_prefix_v<dpc>;
528};
529
530// A value and SI prefix to display, followed by the type's mark and unit.
531struct _display {
532 double value;
533 const char* prefix;
534};
535
536// The base-unit form: the value in unprefixed degrees of the same scale.
537template<typename Traits, typename Rep>
538constexpr _display _base_unit_display(const Rep& count) {
539 return {detail::_ratio_value<typename Traits::dpc>(count), ""};
540}
541
542// The scaled form. Temperatures are not rescaled, so this is the type's own
543// unit: the count (as a double) with the precision's prefix. A precision with
544// no SI prefix has no own unit label and is shown in unprefixed degrees.
545template<typename Traits, typename Rep>
546constexpr _display _scaled_display(const Rep& count) {
547 if constexpr (Traits::prefix != nullptr) {
548 return {static_cast<double>(count), Traits::prefix};
549 } else {
550 return _base_unit_display<Traits>(count);
551 }
552}
553
554// Renders a display value with no format spec, followed by its unit.
555template<typename Traits>
556std::string _display_string(const _display& d) {
557 return _fixed_string(d.value) + Traits::mark + d.prefix + Traits::unit;
558}
559
560// Renders an exact count with the type's own precision-qualified unit.
561template<typename Traits, typename Rep>
562std::string _own_unit_string(const Rep& count) {
563 static_assert(Traits::prefix != nullptr, "thermo: precision has no SI prefix");
564 return _count_string(count) + Traits::mark + Traits::prefix + Traits::unit;
565}
566
567#if CONFIG_QUANTA_STD_FORMAT
568// How std::format renders a delta or temperature with display traits Traits;
569// see detail::_unit_formatter. A precision with no SI prefix has no own unit.
570template<typename Traits>
571struct _format_traits {
572 static constexpr const char* markers = "bs";
573 static constexpr bool has_own_unit = Traits::prefix != nullptr;
574
575 // Reports "{}" for a precision with no own unit. The parameter only gives
576 // the function a path that is a constant expression, as a constexpr
577 // function needs; the formatter always calls it with true.
578 static constexpr void no_own_unit_error(bool fail = true) {
579 if (fail) {
580 _format_error("thermo: precision has no SI prefix; use an explicit format spec");
581 }
582 }
583
584 // The own unit is written from constants; there is nothing to obtain.
585 static constexpr bool own_unit(bool power) { return power; }
586
587 template<typename OutputIt>
588 static OutputIt append_own_unit(OutputIt out, bool) {
589 out = _format_append(out, Traits::mark);
590 out = _format_append(out, Traits::prefix);
591 return _format_append(out, Traits::unit);
592 }
593
594 template<typename T>
595 static _display base_display(const T& t) {
596 return _base_unit_display<Traits>(t.count());
597 }
598
599 template<typename T>
600 static _display scaled_display(const T& t) {
601 return _scaled_display<Traits>(t.count());
602 }
603
604 template<typename OutputIt>
605 static OutputIt append_label(OutputIt out, const _display& d) {
606 out = _format_append(out, Traits::mark);
607 out = _format_append(out, d.prefix);
608 return _format_append(out, Traits::unit);
609 }
610};
611
612// The std::formatter implementation shared by deltas and temperatures; see
613// the formatter specializations for the spec grammar.
614template<typename Traits>
615using _formatter = detail::_unit_formatter<_format_traits<Traits>>;
616#endif
637
653using quanta::scaled;
654using quanta::scaled_t;
655
663template<typename Rep, typename Precision>
664std::string to_string(const delta<Rep, Precision>& d) {
665 return _own_unit_string<_display_traits<delta<Rep, Precision>>>(d.count());
666}
667
672template<typename Rep, typename Precision>
674 using traits = _display_traits<delta<Rep, Precision>>;
675 return _display_string<traits>(_base_unit_display<traits>(d.count()));
676}
677
682template<typename Rep, typename Precision>
684 using traits = _display_traits<delta<Rep, Precision>>;
685 return _display_string<traits>(_scaled_display<traits>(d.count()));
686}
687
688// ============================================================================
689// Temperature Scales and Absolute Temperatures
690// ============================================================================
691
698struct celsius_scale {
699 using offset = std::ratio<27315, 100>;
700 using degree = std::ratio<1>;
701 static constexpr const char* suffix = "°C";
702};
703
709struct kelvin_scale {
710 using offset = std::ratio<0>;
711 using degree = std::ratio<1>;
712 static constexpr const char* suffix = "K";
713};
714
720struct fahrenheit_scale {
721 using offset = std::ratio<45967, 180>;
722 using degree = std::ratio<5, 9>;
723 static constexpr const char* suffix = "°F";
724};
725
734template<typename Scale>
735inline constexpr bool _is_relative_scale = std::ratio_not_equal_v<typename Scale::offset, std::ratio<0>>;
738template<typename Scale, typename Delta = delta<int64_t>>
739class temperature;
740
742// The ratios temperature_cast uses to convert across scales, from (Scale1,
743// Delta1) to (Scale2, Delta2): the precision ratio and the offset in target
744// units, computed without std::ratio_divide so that an overflow makes fits
745// false rather than failing to compile, and the least common denominator of
746// the two (which the cast's integer path computes as an intmax_t).
747template<typename Scale1, typename Delta1, typename Scale2, typename Delta2>
748struct _temperature_ratios {
749 static constexpr _ratio_quotient offset_diff =
750 _safe_ratio_subtract<typename Scale1::offset, typename Scale2::offset>;
751
752 static constexpr _ratio_quotient prec = _safe_ratio_divide<typename Delta1::precision, typename Delta2::precision>;
753 static constexpr _ratio_quotient offset =
754 _safe_ratio_divide<std::ratio<offset_diff.num, offset_diff.den>, typename Delta2::precision>;
755 static constexpr bool fits = offset_diff.fits && prec.fits && offset.fits &&
756 _product_fits(prec.den / _gcd(prec.den, offset.den), offset.den);
757};
758
759template<typename Scale1, typename Delta1, typename Scale2, typename Delta2>
760struct _lossless_temperature_conversion_impl {
761 using ratios = _temperature_ratios<Scale1, Delta1, Scale2, Delta2>;
762
763 static constexpr bool value = ratios::offset_diff.fits && ratios::prec.fits && ratios::offset.fits &&
764 ratios::prec.den == 1 && ratios::offset.den == 1;
765};
766
767// The operations temperature_cast performs across scales to convert a
768// FromRep count to a ToRep count, with Common the common type of FromRep,
769// ToRep and intmax_t: in floating point through Common (an inexact Common),
770// or else with integer arithmetic in Wide on the count converted to Source.
771// Integral reps convert directly to a signed wide integer (128-bit where
772// available, else intmax_t), whatever their signedness, since the offset
773// between scales may be negative and an unsigned intermediate would wrap;
774// other reps compute in Common.
775template<typename Source, typename ToRep, typename Wide>
776concept _temperature_integer_steps = requires(Source& from_val, Wide& w, const intmax_t& k) {
777 {
778 static_cast<Wide>(from_val) * static_cast<Wide>(k) * static_cast<Wide>(k) +
779 static_cast<Wide>(k) * static_cast<Wide>(k)
780 } -> std::convertible_to<Wide>;
781 { w / static_cast<Wide>(k) } -> std::convertible_to<Wide>;
782 static_cast<ToRep>(w);
783};
784
785template<typename FromRep, typename ToRep>
786inline constexpr bool _integral_temperature_reps = std::is_integral_v<FromRep> && std::is_integral_v<ToRep>;
787
788template<typename FromRep, typename ToRep, typename Common>
789using _temperature_source = std::conditional_t<_integral_temperature_reps<FromRep, ToRep>, FromRep, Common>;
790
791template<typename FromRep, typename ToRep, typename Common>
792using _temperature_wide =
793 std::conditional_t<_integral_temperature_reps<FromRep, ToRep>, detail::_wide_integer<intmax_t>, Common>;
794
795template<typename FromRep, typename ToRep, typename Common>
796concept _temperature_cast_steps =
797 (treat_as_inexact_v<Common> && requires(FromRep (&count)()) { static_cast<Common>(count()); } &&
798 requires(Common& from_val, const double& k, Common& result) {
799 { from_val * k + k } -> std::convertible_to<Common>;
800 static_cast<ToRep>(result);
801 }) ||
802 (!treat_as_inexact_v<Common> &&
803 requires(FromRep (&count)()) { static_cast<_temperature_source<FromRep, ToRep, Common>>(count()); } &&
804 _temperature_integer_steps<
805 _temperature_source<FromRep, ToRep, Common>,
806 ToRep,
807 _temperature_wide<FromRep, ToRep, Common>>);
808
809// Whether a conversion from (Scale1, Delta1) to (Scale2, Delta2) is implicit,
810// as for the other domains' quantities: the source rep converts implicitly to
811// the target rep, and the conversion loses nothing. On one scale, that is the
812// quantities' rule (a floating-point target rep, or a precision that evenly
813// divides the source's from a rep that is not floating-point); across scales,
814// both the precision ratio and the offset must be whole numbers of target
815// ticks, and a floating-point source rep needs a floating-point target rep.
816template<typename Scale1, typename Delta1, typename Scale2, typename Delta2>
817concept _implicit_temperature_conversion = std::convertible_to<const typename Delta1::rep&, typename Delta2::rep> &&
818 ((std::is_same_v<Scale1, Scale2> &&
819 detail::_lossless_conversion<
820 typename Delta1::rep,
821 typename Delta1::precision,
822 typename Delta2::rep,
823 typename Delta2::precision>) ||
824 (!std::is_same_v<Scale1, Scale2> && _lossless_temperature_conversion_impl<Scale1, Delta1, Scale2, Delta2>::value &&
825 (treat_as_inexact_v<typename Delta2::rep> || !treat_as_inexact_v<typename Delta1::rep>)));
826
827// The requirements of _temperature_cast, read off its code and checked by its
828// requires-clause: a plain copy, a delta_cast on the same scale, or the
829// cross-scale arithmetic above with ratios that fit. Only _temperature_cast
830// and temperature_cast, which calls it, are constrained on this: whatever
831// else needs to know whether a conversion can be performed asks whether the
832// cast can be called (_temperature_castable), so that the answer is the
833// cast's own.
834template<typename Scale1, typename Delta1, typename Scale2, typename Delta2>
835concept _temperature_cast_requirements = (std::is_same_v<Scale1, Scale2> && std::is_same_v<Delta1, Delta2>) ||
836 (std::is_same_v<Scale1, Scale2> && detail::_quantity_cast_requirements<Delta1, Delta2>) ||
837 (!std::is_same_v<Scale1, Scale2> && _temperature_ratios<Scale1, Delta1, Scale2, Delta2>::fits &&
838 requires { typename std::common_type<typename Delta1::rep, typename Delta2::rep, intmax_t>::type; } &&
839 _temperature_cast_steps<
840 typename Delta1::rep,
841 typename Delta2::rep,
842 std::common_type_t<typename Delta1::rep, typename Delta2::rep, intmax_t>>);
843
844// The conversion behind temperature_cast, constrained on exactly what it
845// does (see _temperature_cast_requirements).
846template<typename ToTemp, typename Scale, typename Delta>
847 requires _temperature_cast_requirements<Scale, Delta, typename ToTemp::scale, typename ToTemp::delta_type>
848constexpr ToTemp _temperature_cast(const temperature<Scale, Delta>& t) {
849 using ToScale = typename ToTemp::scale;
850 using ToDelta = typename ToTemp::delta_type;
851 using to_rep = typename ToDelta::rep;
852
853 if constexpr (std::is_same_v<Scale, ToScale> && std::is_same_v<Delta, ToDelta>) {
854 return t;
855 } else if constexpr (std::is_same_v<Scale, ToScale>) {
856 return ToTemp(delta_cast<ToDelta>(Delta(t.count())));
857 } else {
858 using from_prec = typename Delta::precision;
859 using from_off = typename Scale::offset;
860 using to_prec = typename ToDelta::precision;
861 using to_off = typename ToScale::offset;
862
863 using offset_diff = std::ratio_subtract<from_off, to_off>;
864 using common_rep = std::common_type_t<typename Delta::rep, to_rep, intmax_t>;
865
866 using prec_ratio = std::ratio_divide<from_prec, to_prec>;
867 using offset_ratio = std::ratio_divide<offset_diff, to_prec>;
868
869 if constexpr (treat_as_inexact_v<common_rep>) {
870 common_rep from_val = static_cast<common_rep>(t.count());
871 constexpr double pr = static_cast<double>(prec_ratio::num) / prec_ratio::den;
872 constexpr double or_ = static_cast<double>(offset_ratio::num) / offset_ratio::den;
873 common_rep result = from_val * pr + or_;
874 return ToTemp(ToDelta(static_cast<to_rep>(result)));
875 } else {
876 // Integral reps compute in a signed wide integer (see
877 // _temperature_integer_steps), any others in their common type.
878 using source_t = _temperature_source<typename Delta::rep, to_rep, common_rep>;
879 using wider_t = _temperature_wide<typename Delta::rep, to_rep, common_rep>;
880 source_t from_val = static_cast<source_t>(t.count());
881 // Put both ratios over their least common denominator:
882 // (count * pn * (lcd / pd) + on * (lcd / od)) / lcd
883 constexpr intmax_t lcd = prec_ratio::den / _gcd(prec_ratio::den, offset_ratio::den) * offset_ratio::den;
884 wider_t numerator = static_cast<wider_t>(from_val) * static_cast<wider_t>(prec_ratio::num) *
885 static_cast<wider_t>(lcd / prec_ratio::den) +
886 static_cast<wider_t>(offset_ratio::num) * static_cast<wider_t>(lcd / offset_ratio::den);
887 wider_t result = numerator / static_cast<wider_t>(lcd);
888 return ToTemp(ToDelta(static_cast<to_rep>(result)));
889 }
890 }
891}
892
893// Whether _temperature_cast can convert a From to a To. temperature's
894// converting constructors are constrained on this, so that a conversion is
895// offered exactly when the cast can perform it.
896template<typename From, typename To>
897concept _temperature_castable = requires(const From& t) { _temperature_cast<To>(t); };
898
903template<typename T>
904struct is_temperature : std::false_type {};
905
906template<typename Scale, typename Delta>
907struct is_temperature<temperature<Scale, Delta>> : std::true_type {};
908
909template<typename T>
910inline constexpr bool is_temperature_v = is_temperature<T>::value;
932template<typename ToTemp, typename Scale, typename Delta>
933 requires _temperature_cast_requirements<Scale, Delta, typename ToTemp::scale, typename ToTemp::delta_type>
934constexpr ToTemp temperature_cast(const temperature<Scale, Delta>& t) {
935 return _temperature_cast<ToTemp>(t);
936}
937
958template<typename Scale, typename Delta>
960 static_assert(is_delta<Delta>::value, "Delta must be a quanta::thermo::delta type");
961
962public:
964 using scale = Scale;
966 using delta_type = Delta;
968 using rep = typename Delta::rep;
969
971 constexpr temperature() = default;
972 temperature(const temperature&) = default;
973
979 template<typename Rep2>
980 requires std::convertible_to<const Rep2&, rep> && (treat_as_inexact_v<rep> || !treat_as_inexact_v<Rep2>)
981 constexpr explicit temperature(const Rep2& r)
982 : _d(static_cast<rep>(r)) {}
983
988 constexpr explicit temperature(const Delta& d)
989 : _d(d) {}
990
991 // Every conversion temperature_cast can perform has exactly one of the
992 // constructors below: implicit when lossless and the reps convert
993 // implicitly (see _implicit_temperature_conversion), explicit otherwise.
994
995 // Same scale, different delta - implicit
996 template<typename Delta2>
997 requires(!std::is_same_v<Delta, Delta2>) && _temperature_castable<temperature<Scale, Delta2>, temperature> &&
998 _implicit_temperature_conversion<Scale, Delta2, Scale, Delta>
1000 : _d(delta_cast<Delta>(Delta2(t.count()))) {}
1001
1002 // Same scale, different delta - explicit
1003 template<typename Delta2>
1004 requires(!std::is_same_v<Delta, Delta2>) && _temperature_castable<temperature<Scale, Delta2>, temperature> &&
1005 (!_implicit_temperature_conversion<Scale, Delta2, Scale, Delta>)
1006 constexpr explicit temperature(const temperature<Scale, Delta2>& t)
1007 : _d(delta_cast<Delta>(Delta2(t.count()))) {}
1008
1009 // Across scales - implicit
1010 template<typename Scale2, typename Delta2>
1011 requires(!std::is_same_v<temperature, temperature<Scale2, Delta2>>) &&
1012 _temperature_castable<temperature<Scale2, Delta2>, temperature> &&
1013 _implicit_temperature_conversion<Scale2, Delta2, Scale, Delta>
1015 : temperature(temperature_cast<temperature>(t)) {}
1016
1017 // Across scales - explicit
1018 template<typename Scale2, typename Delta2>
1019 requires(!std::is_same_v<temperature, temperature<Scale2, Delta2>>) &&
1020 _temperature_castable<temperature<Scale2, Delta2>, temperature> &&
1021 (!_implicit_temperature_conversion<Scale2, Delta2, Scale, Delta>)
1024
1025 ~temperature() = default;
1027
1029 constexpr rep count() const { return _d.count(); }
1030
1032 ++_d;
1033 return *this;
1034 }
1035
1036 constexpr temperature operator++(int) { return temperature(_d++); }
1037
1039 --_d;
1040 return *this;
1041 }
1042
1043 constexpr temperature operator--(int) { return temperature(_d--); }
1044
1052 constexpr temperature operator-() const
1053 requires _is_relative_scale<Scale>
1054 {
1055 return temperature(-_d);
1056 }
1057
1058 template<typename Rep2, typename Precision2>
1060 _d += d;
1061 return *this;
1062 }
1063
1064 template<typename Rep2, typename Precision2>
1066 _d -= d;
1067 return *this;
1068 }
1069
1071 static constexpr temperature min() noexcept { return temperature(Delta::min()); }
1072
1074 static constexpr temperature max() noexcept { return temperature(Delta::max()); }
1075
1076private:
1077 Delta _d{};
1078};
1079
1090template<typename Scale, typename Delta1, typename Delta2>
1092 -> std::common_type_t<Delta1, Delta2> {
1093 return Delta1(lhs.count()) - Delta2(rhs.count());
1094}
1095
1104template<typename Scale, typename Delta1, typename Delta2>
1106 -> std::common_type_t<Delta1, Delta2> {
1107 return difference(lhs, rhs);
1108}
1109
1115template<typename Scale, typename Delta1, typename Delta2>
1117
1119template<typename Scale, typename Delta1, typename Rep2, typename Precision2>
1122 using result_delta = std::common_type_t<Delta1, delta<Rep2, Precision2>>;
1123 return temperature<Scale, result_delta>(result_delta(Delta1(t.count())) + result_delta(d));
1124}
1125
1127template<typename Rep1, typename Precision1, typename Scale, typename Delta2>
1130 return t + d;
1131}
1132
1134template<typename Scale, typename Delta1, typename Rep2, typename Precision2>
1137 using result_delta = std::common_type_t<Delta1, delta<Rep2, Precision2>>;
1138 return temperature<Scale, result_delta>(result_delta(Delta1(t.count())) - result_delta(d));
1139}
1140
1141template<typename Scale, typename Delta1, typename Delta2>
1143 return Delta1(lhs.count()) == Delta2(rhs.count());
1144}
1145
1146template<typename Scale, typename Delta1, typename Delta2>
1147 requires std::three_way_comparable<std::common_type_t<typename Delta1::rep, typename Delta2::rep>>
1149 return Delta1(lhs.count()) <=> Delta2(rhs.count());
1150}
1151
1170
1172template<typename Scale, typename Rep, typename Precision>
1173struct _display_traits<temperature<Scale, delta<Rep, Precision>>> {
1174 using dpc = typename std::ratio_divide<typename delta<Rep, Precision>::precision, typename Scale::degree>::type;
1175 static constexpr const char* mark = "";
1176 static constexpr const char* unit = Scale::suffix;
1177 static constexpr const char* prefix = _si_prefix_v<dpc>;
1178};
1188template<typename Scale, typename Rep, typename Precision>
1189std::string to_string(const temperature<Scale, delta<Rep, Precision>>& t) {
1190 return _own_unit_string<_display_traits<temperature<Scale, delta<Rep, Precision>>>>(t.count());
1191}
1192
1198template<typename Scale, typename Rep, typename Precision>
1200 using traits = _display_traits<temperature<Scale, delta<Rep, Precision>>>;
1201 return _display_string<traits>(_base_unit_display<traits>(t.count()));
1202}
1203
1208template<typename Scale, typename Rep, typename Precision>
1209std::string to_string(const temperature<Scale, delta<Rep, Precision>>& t, scaled_t) {
1210 using traits = _display_traits<temperature<Scale, delta<Rep, Precision>>>;
1211 return _display_string<traits>(_scaled_display<traits>(t.count()));
1212}
1213
1214} // namespace quanta::thermo
1215
1216namespace std {
1217
1218template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
1219struct common_type<quanta::thermo::delta<Rep1, Precision1>, quanta::thermo::delta<Rep2, Precision2>> {
1220private:
1221 using common_precision = quanta::detail::_common_precision<Precision1, Precision2>;
1222
1223public:
1224 using type = quanta::thermo::delta<std::common_type_t<Rep1, Rep2>, common_precision>;
1225};
1226
1227template<typename Scale, typename Delta1, typename Delta2>
1228struct common_type<quanta::thermo::temperature<Scale, Delta1>, quanta::thermo::temperature<Scale, Delta2>> {
1230};
1231
1232#if CONFIG_QUANTA_STD_FORMAT
1233// "{}" prints the exact stored count with a precision-qualified unit
1234// (millicelsius(22500) -> "22500m°C"; floating-point counts in the shortest
1235// fixed notation that round-trips, as to_string does: never scientific). A
1236// non-empty spec controls only how the number is rendered (as a double),
1237// never the unit, so both forms use the same unit:
1238// std::format("{:.1f}", millicelsius(22534)) == "22534.0m°C".
1239//
1240// The unit changes only when the spec opts in with a single leading marker
1241// letter; the rest of the spec is then the std::formatter<double> spec:
1242// - 'b' (base unit, see quanta::base_unit) shows the value in the unprefixed
1243// degree of the same scale (°C, K or °F; never another scale):
1244// std::format("{:b.1f}", millicelsius(22534)) == "22.5°C".
1245// - 's' (scaled, see quanta::scaled) keeps the type's own unit, since
1246// temperatures are not rescaled: std::format("{:s.1f}", millicelsius(22534))
1247// == "22534.0m°C".
1248// With 'b' or 's' and nothing after the marker, the value is rendered in the
1249// shortest fixed notation that round-trips (never scientific), exactly as by
1250// to_string(t, quanta::base_unit) or to_string(t, quanta::scaled):
1251// std::format("{:b}", millicelsius(22534)) == "22.534°C".
1252//
1253// A spec starting with 'b' or 's' is always a marker, so these letters cannot
1254// be the fill character of a spec that starts with a fill ("{:b>10.1f}" is the
1255// marker followed by ">10.1f").
1256//
1257// A precision with no SI prefix has no unit label of its own: "{}" is a
1258// format error, and any other spec renders in the base unit.
1259template<typename Scale, typename Rep, typename Precision>
1260struct formatter<quanta::thermo::temperature<Scale, quanta::thermo::delta<Rep, Precision>>>
1261 : private quanta::thermo::_formatter<
1262 quanta::thermo::_display_traits<quanta::thermo::temperature<Scale, quanta::thermo::delta<Rep, Precision>>>> {
1263 using quanta::thermo::_formatter<quanta::thermo::_display_traits<
1265
1266 template<typename FormatContext>
1267 auto format(
1269 FormatContext& ctx
1270 ) const {
1271 return this->format_quantity(t, ctx);
1272 }
1273};
1274
1275// As for temperatures, with the Δ marker before the unit:
1276// std::format("{}", delta_millicelsius(1500)) == "1500Δm°C",
1277// std::format("{:.1f}", delta_millicelsius(1500)) == "1500.0Δm°C" and
1278// std::format("{:b.1f}", delta_millicelsius(1500)) == "1.5Δ°C". Celsius and
1279// kelvin deltas share types and display as Δ°C.
1280template<typename Rep, typename Precision>
1281struct formatter<quanta::thermo::delta<Rep, Precision>>
1282 : private quanta::thermo::_formatter<quanta::thermo::_display_traits<quanta::thermo::delta<Rep, Precision>>> {
1283 using quanta::thermo::_formatter<quanta::thermo::_display_traits<quanta::thermo::delta<Rep, Precision>>>::parse;
1284
1285 template<typename FormatContext>
1286 auto format(const quanta::thermo::delta<Rep, Precision>& d, FormatContext& ctx) const {
1287 return this->format_quantity(d, ctx);
1288 }
1289};
1290#endif
1291
1292} // namespace std
1293
1297namespace quanta {
1298inline namespace literals {
1299inline namespace thermo_literals {
1301template<char... Digits>
1302constexpr quanta::thermo::celsius operator""_c() {
1303 return quanta::thermo::celsius(detail::check_overflow<quanta::thermo::delta_celsius, Digits...>());
1304}
1305
1307template<char... Digits>
1308constexpr quanta::thermo::decicelsius operator""_dc() {
1309 return quanta::thermo::decicelsius(detail::check_overflow<quanta::thermo::delta_decicelsius, Digits...>());
1310}
1311
1313template<char... Digits>
1314constexpr quanta::thermo::millicelsius operator""_mc() {
1315 return quanta::thermo::millicelsius(detail::check_overflow<quanta::thermo::delta_millicelsius, Digits...>());
1316}
1317
1319template<char... Digits>
1320constexpr quanta::thermo::kelvin operator""_k() {
1321 return quanta::thermo::kelvin(detail::check_overflow<quanta::thermo::delta_kelvin, Digits...>());
1322}
1323
1325template<char... Digits>
1326constexpr quanta::thermo::decikelvin operator""_dk() {
1327 return quanta::thermo::decikelvin(detail::check_overflow<quanta::thermo::delta_decikelvin, Digits...>());
1328}
1329
1331template<char... Digits>
1332constexpr quanta::thermo::millikelvin operator""_mk() {
1333 return quanta::thermo::millikelvin(detail::check_overflow<quanta::thermo::delta_millikelvin, Digits...>());
1334}
1335
1337template<char... Digits>
1338constexpr quanta::thermo::fahrenheit operator""_f() {
1339 return quanta::thermo::fahrenheit(detail::check_overflow<quanta::thermo::delta_fahrenheit, Digits...>());
1340}
1341
1343template<char... Digits>
1345 return quanta::thermo::decifahrenheit(detail::check_overflow<quanta::thermo::delta_decifahrenheit, Digits...>());
1346}
1347
1349template<char... Digits>
1351 return quanta::thermo::millifahrenheit(detail::check_overflow<quanta::thermo::delta_millifahrenheit, Digits...>());
1352}
1353
1355template<char... Digits>
1357 return detail::check_overflow<quanta::thermo::delta_celsius, Digits...>();
1358}
1359
1361template<char... Digits>
1363 return detail::check_overflow<quanta::thermo::delta_millicelsius, Digits...>();
1364}
1365
1367template<char... Digits>
1369 return detail::check_overflow<quanta::thermo::delta_kelvin, Digits...>();
1370}
1371
1373template<char... Digits>
1375 return detail::check_overflow<quanta::thermo::delta_millikelvin, Digits...>();
1376}
1377
1379template<char... Digits>
1381 return detail::check_overflow<quanta::thermo::delta_fahrenheit, Digits...>();
1382}
1383
1385template<char... Digits>
1387 return detail::check_overflow<quanta::thermo::delta_millifahrenheit, Digits...>();
1388}
1389
1390#if QUANTA_HAS_UTF8_LITERALS
1393template<char... Digits>
1395 return detail::check_overflow<quanta::thermo::delta_celsius, Digits...>();
1396}
1397
1400template<char... Digits>
1402 return detail::check_overflow<quanta::thermo::delta_millicelsius, Digits...>();
1403}
1404
1407template<char... Digits>
1408constexpr quanta::thermo::delta_kelvin operator""_Δk() {
1409 return detail::check_overflow<quanta::thermo::delta_kelvin, Digits...>();
1410}
1411
1414template<char... Digits>
1416 return detail::check_overflow<quanta::thermo::delta_millikelvin, Digits...>();
1417}
1418
1421template<char... Digits>
1423 return detail::check_overflow<quanta::thermo::delta_fahrenheit, Digits...>();
1424}
1425
1428template<char... Digits>
1430 return detail::check_overflow<quanta::thermo::delta_millifahrenheit, Digits...>();
1431}
1432#endif
1433
1434} // namespace thermo_literals
1435} // namespace literals
1436
1437namespace thermo {
1438using namespace literals::thermo_literals;
1439} // namespace thermo
1440} // namespace quanta
A temperature difference with a representation and precision.
Definition thermo.hpp:98
constexpr delta & operator++()
Definition thermo.hpp:184
constexpr delta< typename std::common_type< rep >::type, precision > operator+() const
Definition thermo.hpp:176
Rep rep
The representation type.
Definition thermo.hpp:105
constexpr delta & operator--()
Definition thermo.hpp:191
delta & operator=(const delta &)=default
static constexpr delta zero() noexcept
Returns a zero-length delta.
Definition thermo.hpp:233
constexpr delta(const Rep2 &r)
Constructs from a tick count.
Definition thermo.hpp:124
constexpr delta & operator%=(const delta &d)
Definition thermo.hpp:225
static constexpr delta min() noexcept
Returns the minimum (most negative) representable delta.
Definition thermo.hpp:236
constexpr delta & operator*=(const rep &r)
Definition thermo.hpp:208
constexpr delta operator++(int)
Definition thermo.hpp:189
typename Precision::type precision
The precision as a std::ratio.
Definition thermo.hpp:107
constexpr delta(const delta< Rep2, Precision2 > &temp)
Constructs from another delta with different representation or precision.
Definition thermo.hpp:144
constexpr delta operator--(int)
Definition thermo.hpp:196
constexpr rep count() const
Returns the tick count.
Definition thermo.hpp:174
constexpr delta()=default
Constructs a zero delta.
constexpr delta< typename std::common_type< rep >::type, precision > operator-() const
Definition thermo.hpp:180
constexpr delta & operator+=(const delta &d)
Definition thermo.hpp:198
static constexpr delta max() noexcept
Returns the maximum representable delta.
Definition thermo.hpp:239
constexpr delta & operator/=(const rep &r)
Definition thermo.hpp:213
constexpr delta & operator-=(const delta &d)
Definition thermo.hpp:203
delta(const delta &)=default
constexpr delta & operator%=(const rep &r)
Definition thermo.hpp:218
constexpr delta(const delta< Rep2, Precision2 > &d)
Explicit constructor for lossy conversions.
Definition thermo.hpp:167
An absolute temperature on a given scale.
Definition thermo.hpp:959
temperature & operator=(const temperature &)=default
constexpr temperature()=default
Constructs a temperature at the scale's zero point.
constexpr temperature(const temperature< Scale, Delta2 > &t)
Definition thermo.hpp:1006
constexpr temperature(const temperature< Scale, Delta2 > &t)
Definition thermo.hpp:999
constexpr temperature operator++(int)
Definition thermo.hpp:1036
constexpr temperature & operator--()
Definition thermo.hpp:1038
Scale scale
The temperature scale.
Definition thermo.hpp:964
constexpr temperature(const temperature< Scale2, Delta2 > &t)
Definition thermo.hpp:1022
constexpr temperature & operator++()
Definition thermo.hpp:1031
constexpr temperature(const Rep2 &r)
Constructs from a tick count.
Definition thermo.hpp:981
typename Delta::rep rep
The representation type.
Definition thermo.hpp:968
constexpr rep count() const
Returns the tick count.
Definition thermo.hpp:1029
constexpr temperature operator--(int)
Definition thermo.hpp:1043
temperature(const temperature &)=default
static constexpr temperature max() noexcept
Returns the maximum representable temperature.
Definition thermo.hpp:1074
static constexpr temperature min() noexcept
Returns the minimum representable temperature.
Definition thermo.hpp:1071
constexpr temperature(const temperature< Scale2, Delta2 > &t)
Definition thermo.hpp:1014
constexpr temperature operator-() const
Negates the temperature (e.g.
Definition thermo.hpp:1052
Delta delta_type
The delta type.
Definition thermo.hpp:966
constexpr temperature & operator-=(const delta< Rep2, Precision2 > &d)
Definition thermo.hpp:1065
constexpr temperature(const Delta &d)
Constructs from a delta.
Definition thermo.hpp:988
constexpr temperature & operator+=(const delta< Rep2, Precision2 > &d)
Definition thermo.hpp:1059
Configuration, traits and internals shared by every quanta domain.
quantity< watt_unit, Rep, Precision > power
Power quantity with configurable representation and precision.
Definition electro.hpp:900
Temperature types and utilities.
Definition thermo.hpp:50
temperature< celsius_scale, delta< int64_t, std::milli > > millicelsius
Celsius with 0.001 degree precision.
Definition thermo.hpp:1157
constexpr ToDelta delta_cast(const delta< Rep, Precision > &d)
Converts a delta to a different precision or representation.
Definition thermo.hpp:261
temperature< fahrenheit_scale, delta< int64_t, std::ratio< 5, 90 > > > decifahrenheit
Fahrenheit with 0.1 degree precision.
Definition thermo.hpp:1167
temperature< celsius_scale, delta< int64_t, std::deci > > decicelsius
Celsius with 0.1 degree precision.
Definition thermo.hpp:1155
delta< int64_t, std::ratio< 5, 9 > > delta_fahrenheit
Delta with 1°F precision.
Definition thermo.hpp:458
delta< int64_t, std::milli > delta_millikelvin
Delta with 0.001 degree precision (Celsius/Kelvin).
Definition thermo.hpp:456
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.
Definition thermo.hpp:1091
constexpr ToDelta round(const delta< Rep, Precision > &d)
Rounds a delta to the nearest representable value in the target precision.
Definition thermo.hpp:340
delta< int64_t, std::ratio< 5, 9000 > > delta_millifahrenheit
Delta with 0.001°F precision.
Definition thermo.hpp:462
delta< int64_t, std::deci > delta_decicelsius
Delta with 0.1 degree precision (Celsius/Kelvin).
Definition thermo.hpp:448
delta< int64_t, std::milli > delta_millicelsius
Delta with 0.001 degree precision (Celsius/Kelvin).
Definition thermo.hpp:450
constexpr ToDelta trunc(const delta< Rep, Precision > &d)
Rounds a delta toward zero to the nearest representable value in the target precision.
Definition thermo.hpp:320
delta< int64_t > delta_celsius
Delta with 1 degree precision (Celsius/Kelvin).
Definition thermo.hpp:446
temperature< kelvin_scale > kelvin
Kelvin with 1 degree precision.
Definition thermo.hpp:1159
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.
Definition thermo.hpp:382
delta< int64_t, std::deci > delta_decikelvin
Delta with 0.1 degree precision (Celsius/Kelvin).
Definition thermo.hpp:454
constexpr auto operator<=>(const delta< Rep1, Precision1 > &lhs, const delta< Rep2, Precision2 > &rhs)
Definition thermo.hpp:440
temperature< fahrenheit_scale, delta< int64_t, std::ratio< 5, 9000 > > > millifahrenheit
Fahrenheit with 0.001 degree precision.
Definition thermo.hpp:1169
temperature< fahrenheit_scale, delta< int64_t, std::ratio< 5, 9 > > > fahrenheit
Fahrenheit with 1 degree precision.
Definition thermo.hpp:1165
temperature< celsius_scale > celsius
Celsius with 1 degree precision.
Definition thermo.hpp:1153
constexpr delta< Rep, Precision > abs(const delta< Rep, Precision > &d)
Returns the absolute value of a delta.
Definition thermo.hpp:359
constexpr ToTemp temperature_cast(const temperature< Scale, Delta > &t)
Converts a temperature to a different scale or precision.
Definition thermo.hpp:934
delta< int64_t > delta_kelvin
Delta with 1 degree precision (Celsius/Kelvin).
Definition thermo.hpp:452
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.
Definition thermo.hpp:399
constexpr ToDelta ceil(const delta< Rep, Precision > &d)
Rounds a delta up to the nearest representable value in the target precision.
Definition thermo.hpp:282
delta< int64_t, std::ratio< 5, 90 > > delta_decifahrenheit
Delta with 0.1°F precision.
Definition thermo.hpp:460
temperature< kelvin_scale, delta< int64_t, std::deci > > decikelvin
Kelvin with 0.1 degree precision.
Definition thermo.hpp:1161
constexpr ToDelta floor(const delta< Rep, Precision > &d)
Rounds a delta down to the nearest representable value in the target precision.
Definition thermo.hpp:303
temperature< kelvin_scale, delta< int64_t, std::milli > > millikelvin
Kelvin with 0.001 degree precision.
Definition thermo.hpp:1163
constexpr bool operator==(const delta< Rep1, Precision1 > &lhs, const delta< Rep2, Precision2 > &rhs)
Definition thermo.hpp:433
std::string to_string(const delta< Rep, Precision > &d)
Renders a delta as its exact count with a precision-qualified unit, e.g.
Definition thermo.hpp:664
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.
Definition thermo.hpp:365
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.
Definition thermo.hpp:373
Type-safe units modeled after std::chrono.
Definition core.hpp:90
constexpr scaled_t scaled
Selects the scaled form in to_string().
Definition core.hpp:161
constexpr base_unit_t base_unit
Selects the base-unit form in to_string().
Definition core.hpp:140
constexpr bool treat_as_inexact_v
Definition core.hpp:119
Tag type selecting the base-unit form, see base_unit.
Definition core.hpp:124
Tag type selecting the scaled form, see scaled.
Definition core.hpp:145
Trait to detect delta specializations.
Definition thermo.hpp:64
Trait to indicate floating-point-like behavior.
Definition core.hpp:116