thermo 2.1.1
Type-safe temperature handling library modeled after std::chrono
Loading...
Searching...
No Matches
thermo.hpp
Go to the documentation of this file.
1#pragma once
2
8#include <compare>
9#include <concepts>
10#include <cstdint>
11#include <cstdlib>
12#include <limits>
13#include <ratio>
14#include <string>
15#include <version> // pulls in __cpp_lib_format so the detection below works in non-IDF builds
16#ifndef CONFIG_THERMO_STD_FORMAT
17#if __has_include(<format>) && defined(__cpp_lib_format)
18#define CONFIG_THERMO_STD_FORMAT 1
19#else
20#define CONFIG_THERMO_STD_FORMAT 0
21#endif
22#endif
23
24#if CONFIG_THERMO_STD_FORMAT
25#include <format>
26#endif
27#include <assert.h>
28
29static_assert(__cplusplus >= 202302L, "thermo requires C++23 or later");
30
39namespace thermo {
40
41template<typename Rep, typename Precision = std::ratio<1>>
42class delta;
43
48template<typename _T>
49struct is_delta : std::false_type {};
50
51template<typename Rep, typename Precision>
52struct is_delta<delta<Rep, Precision>> : std::true_type {};
53
55template<typename T, template<typename...> class Template>
56struct _is_specialization_of : std::false_type {};
57
58template<template<typename...> class Template, typename... Args>
59struct _is_specialization_of<Template<Args...>, Template> : std::true_type {};
60
61template<typename T, template<typename...> class Template>
62inline constexpr bool _is_specialization_of_v = _is_specialization_of<T, Template>::value;
63
64template<typename Rep>
65concept not_delta = !_is_specialization_of_v<Rep, delta>;
66
67template<typename Rep>
68struct delta_values {
70 static constexpr Rep zero() noexcept { return Rep(0); }
71
73 static constexpr Rep max() noexcept { return std::numeric_limits<Rep>::max(); }
74
76 static constexpr Rep min() noexcept { return std::numeric_limits<Rep>::lowest(); }
77};
78
79template<typename T>
80struct _is_ratio : std::false_type {};
81
82template<std::intmax_t Num, std::intmax_t Denom>
83struct _is_ratio<std::ratio<Num, Denom>> : std::true_type {};
84
85template<typename T>
86struct treat_as_inexact : std::bool_constant<std::floating_point<T>> {};
87
88template<typename T>
89inline constexpr bool treat_as_inexact_v = treat_as_inexact<T>::value;
90
91consteval intmax_t _gcd(intmax_t m, intmax_t n) noexcept {
92 while (n != 0) {
93 intmax_t rem = m % n;
94 m = n;
95 n = rem;
96 }
97 return m;
98}
99
100template<typename R1, typename R2>
101inline constexpr intmax_t _safe_ratio_divide_den = [] {
102 constexpr intmax_t g1 = _gcd(R1::num, R2::num);
103 constexpr intmax_t g2 = _gcd(R1::den, R2::den);
104 return (R1::den / g2) * (R2::num / g1);
105}();
106
107template<typename From, typename To>
108concept _harmonic_precision = _safe_ratio_divide_den<From, To> == 1;
121template<typename Rep, typename Precision>
122class delta {
123 static_assert(!is_delta<Rep>::value, "rep cannot be a thermo::delta");
124 static_assert(_is_ratio<Precision>::value, "precision must be a specialization of std::ratio");
125 static_assert(Precision::num > 0, "precision must be positive");
126
127public:
129 using rep = Rep;
131 using precision = typename Precision::type;
132
134 constexpr delta() = default;
135 delta(const delta&) = default;
136
146 template<typename Rep2>
147 requires std::convertible_to<const Rep2&, rep> && (treat_as_inexact_v<rep> || !treat_as_inexact_v<Rep2>)
148 constexpr explicit delta(const Rep2& r)
149 : _r(static_cast<rep>(r)) {}
150
161 template<typename Rep2, typename Precision2>
162 requires std::convertible_to<const Rep2&, rep> &&
163 (treat_as_inexact_v<rep> || (_harmonic_precision<Precision2, precision> && !treat_as_inexact_v<Rep2>))
164 constexpr delta(const delta<Rep2, Precision2>& temp)
165 : _r(delta_cast<delta>(temp).count()) {}
166
167 ~delta() = default;
168 delta& operator=(const delta&) = default;
169
171 constexpr rep count() const { return _r; }
172
176
180
181 constexpr delta& operator++() {
182 ++_r;
183 return *this;
184 }
185
186 constexpr delta operator++(int) { return delta(_r++); }
187
188 constexpr delta& operator--() {
189 --_r;
190 return *this;
191 }
192
193 constexpr delta operator--(int) { return delta(_r--); }
194
195 constexpr delta& operator+=(const delta& d) {
196 _r += d.count();
197 return *this;
198 }
199
200 constexpr delta& operator-=(const delta& d) {
201 _r -= d.count();
202 return *this;
203 }
204
205 constexpr delta& operator*=(const rep& r) {
206 _r *= r;
207 return *this;
208 }
209
210 constexpr delta& operator/=(const rep& r) {
211 _r /= r;
212 return *this;
213 }
214
215 constexpr delta& operator%=(const rep& r)
216 requires(!treat_as_inexact_v<rep>)
217 {
218 _r %= r;
219 return *this;
220 }
221
222 constexpr delta& operator%=(const delta& d)
223 requires(!treat_as_inexact_v<rep>)
224 {
225 _r %= d.count();
226 return *this;
227 }
228
230 static constexpr delta zero() noexcept { return delta(delta_values<rep>::zero()); }
231
233 static constexpr delta min() noexcept { return delta(delta_values<rep>::min()); }
234
236 static constexpr delta max() noexcept { return delta(delta_values<rep>::max()); }
237
238private:
239 rep _r{};
240};
241
252template<typename ToDelta, typename Rep, typename Precision>
253constexpr ToDelta delta_cast(const delta<Rep, Precision>& d) {
254 if constexpr (std::is_same_v<ToDelta, delta<Rep, Precision>>) {
255 return d;
256 } else {
257 using to_rep = typename ToDelta::rep;
258 using to_precision = typename ToDelta::precision;
259 using cf = std::ratio_divide<Precision, to_precision>;
260 using cr = std::common_type_t<to_rep, Rep, intmax_t>;
261
262 if constexpr (cf::den == 1 && cf::num == 1) {
263 return ToDelta(static_cast<to_rep>(d.count()));
264 } else if constexpr (cf::den == 1) {
265 return ToDelta(static_cast<to_rep>(static_cast<cr>(d.count()) * static_cast<cr>(cf::num)));
266 } else if constexpr (cf::num == 1) {
267 return ToDelta(static_cast<to_rep>(static_cast<cr>(d.count()) / static_cast<cr>(cf::den)));
268 } else {
269 return ToDelta(
270 static_cast<to_rep>(static_cast<cr>(d.count()) * static_cast<cr>(cf::num) / static_cast<cr>(cf::den))
271 );
272 }
273 }
274}
275
288template<typename ToDelta, typename Rep, typename Precision>
289constexpr ToDelta ceil(const delta<Rep, Precision>& d) {
290 ToDelta result = delta_cast<ToDelta>(d);
291 if (result < d) {
292 return ToDelta(result.count() + 1);
293 }
294 return result;
295}
296
309template<typename ToDelta, typename Rep, typename Precision>
312 if (result > d) {
313 return ToDelta(result.count() - 1);
314 }
315 return result;
316}
317
330template<typename ToDelta, typename Rep, typename Precision>
332 return delta_cast<ToDelta>(d);
333}
334
348template<typename ToDelta, typename Rep, typename Precision>
351 if (result == d) {
352 return result;
353 }
354
355 // Calculate the midpoint between result and the next value
356 ToDelta next = (result < d) ? ToDelta(result.count() + 1) : ToDelta(result.count() - 1);
357
358 // Convert both to source type for comparison
361
362 // Calculate distances
365
366 // If closer to next, or exactly halfway and away from zero, use next
368 return next;
369 } else if (dist_to_next == dist_to_result) {
370 // Tie: round away from zero
371 if (d.count() >= 0) {
372 return (next > result) ? next : result;
373 } else {
374 return (next < result) ? next : result;
375 }
376 }
377
378 return result;
379}
380
382template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
384 -> std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>> {
385 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
386 return cd(cd(lhs).count() + cd(rhs).count());
387}
388
390template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
392 -> std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>> {
393 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
394 return cd(cd(lhs).count() - cd(rhs).count());
395}
396
398template<typename Rep1, typename Precision, typename Rep2>
399 requires not_delta<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>>
400constexpr auto operator*(const delta<Rep1, Precision>& d, const Rep2& r)
403 return cd(cd(d).count() * r);
404}
405
407template<typename Rep1, typename Rep2, typename Precision>
408 requires not_delta<Rep1> && std::convertible_to<const Rep1&, std::common_type_t<Rep1, Rep2>>
409constexpr auto operator*(const Rep1& r, const delta<Rep2, Precision>& d)
411 return d * r;
412}
413
415template<typename Rep1, typename Precision, typename Rep2>
416 requires not_delta<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>>
417constexpr auto operator/(const delta<Rep1, Precision>& d, const Rep2& s)
420 return cd(cd(d).count() / s);
421}
422
424template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
426 -> std::common_type_t<Rep1, Rep2> {
427 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
428 return cd(lhs).count() / cd(rhs).count();
429}
430
432template<typename Rep1, typename Precision, typename Rep2>
433 requires not_delta<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>> &&
435constexpr auto operator%(const delta<Rep1, Precision>& d, const Rep2& s)
438 return cd(cd(d).count() % s);
439}
440
442template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
444constexpr auto operator%(const delta<Rep1, Precision1>& lhs, const delta<Rep2, Precision2>& rhs)
445 -> std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>> {
446 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
447 return cd(cd(lhs).count() % cd(rhs).count());
448}
449
450template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
452 using ct = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
453 return ct(lhs).count() == ct(rhs).count();
454}
455
456template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
457 requires std::three_way_comparable<std::common_type_t<Rep1, Rep2>>
459 using ct = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
460 return ct(lhs).count() <=> ct(rhs).count();
461}
462
481
483// SI prefix for a degrees-per-count ratio; nullptr when unmapped.
484template<typename Ratio>
485struct _si_prefix {
486 static constexpr const char* value = nullptr;
487};
488template<>
489struct _si_prefix<std::ratio<1>> {
490 static constexpr const char* value = "";
491};
492template<>
493struct _si_prefix<std::ratio<1, 10>> {
494 static constexpr const char* value = "d";
495};
496template<>
497struct _si_prefix<std::ratio<1, 1000>> {
498 static constexpr const char* value = "m";
499};
500template<typename Ratio>
501inline constexpr const char* _si_prefix_v = _si_prefix<typename Ratio::type>::value;
502
503// Display family (degree size + unit suffix) for a delta precision, keyed on
504// NORMALIZED ratios. Undefined primary template: unmapped precisions have no
505// determinable unit and are not formattable.
506template<typename Precision>
507struct _delta_unit;
508struct _celsius_delta_unit {
509 using degree = std::ratio<1>;
510 static constexpr const char* suffix = "°C";
511};
513 using degree = std::ratio<5, 9>;
514 static constexpr const char* suffix = "°F";
515};
516template<>
518template<>
519struct _delta_unit<std::ratio<1, 10>> : _celsius_delta_unit {};
520template<>
521struct _delta_unit<std::ratio<1, 1000>> : _celsius_delta_unit {};
522template<>
523struct _delta_unit<std::ratio<5, 9>> : _fahrenheit_delta_unit {};
524template<>
525struct _delta_unit<std::ratio<1, 18>> : _fahrenheit_delta_unit {}; // 5/90 normalized
526template<>
527struct _delta_unit<std::ratio<1, 1800>> : _fahrenheit_delta_unit {}; // 5/9000 normalized
528
529// Appends a NUL-terminated string to a format output iterator.
530template<typename OutputIt>
531constexpr OutputIt _format_append(OutputIt out, const char* s) {
532 for (; *s != '\0'; ++s) {
533 *out++ = *s;
534 }
535 return out;
536}
537
538#if CONFIG_THERMO_STD_FORMAT
539// Reports an unusable format spec from a formatter's parse().
540//
541// std::format constant-evaluates parse() to check the format string, so
542// throwing there makes a bad spec a compile error rather than a runtime fault.
543// Where exceptions are unavailable, calling a non-constexpr function fails that
544// same constant evaluation and so reports the error at compile time too; a
545// runtime parse (std::vformat with a runtime format string) has no way to
546// report it and terminates.
547[[noreturn]] inline void _format_error(const char* what) {
548#if defined(__cpp_exceptions) && __cpp_exceptions
549 throw std::format_error(what);
550#else
551 (void)what;
552 std::abort();
553#endif
554}
555#endif
562template<typename Rep, typename Precision>
563 requires(!treat_as_inexact_v<Rep>)
564std::string to_string(const delta<Rep, Precision>& d) {
566 using dpc = std::ratio_divide<typename delta<Rep, Precision>::precision, typename unit::degree>;
567 static_assert(_si_prefix_v<dpc> != nullptr, "thermo: precision has no SI prefix");
568 return std::to_string(d.count()) + "Δ" + _si_prefix_v<dpc> + unit::suffix;
569}
570
571// ============================================================================
572// Temperature Scales and Absolute Temperatures
573// ============================================================================
574
581struct celsius_scale {
582 using offset = std::ratio<27315, 100>;
583 using degree = std::ratio<1>;
584 static constexpr const char* suffix = "°C";
585};
586
592struct kelvin_scale {
593 using offset = std::ratio<0>;
594 using degree = std::ratio<1>;
595 static constexpr const char* suffix = "K";
596};
597
603struct fahrenheit_scale {
604 using offset = std::ratio<45967, 180>;
605 using degree = std::ratio<5, 9>;
606 static constexpr const char* suffix = "°F";
607};
608
617template<typename Scale>
618inline constexpr bool _is_relative_scale = std::ratio_not_equal_v<typename Scale::offset, std::ratio<0>>;
621template<typename Scale, typename Delta = delta<int64_t>>
622class temperature;
623
625template<typename Scale1, typename Delta1, typename Scale2, typename Delta2>
627 using from_prec = typename Delta1::precision;
628 using to_prec = typename Delta2::precision;
629 using from_off = typename Scale1::offset;
630 using to_off = typename Scale2::offset;
631
632 using prec_ratio = std::ratio_divide<from_prec, to_prec>;
633 using offset_diff = std::ratio_subtract<from_off, to_off>;
634 using offset_in_target_units = std::ratio_divide<offset_diff, to_prec>;
635
636 static constexpr bool value = prec_ratio::den == 1 && offset_in_target_units::den == 1;
637};
638
639template<typename Scale1, typename Delta1, typename Scale2, typename Delta2>
640concept _lossless_temperature_conversion = _lossless_temperature_conversion_impl<Scale1, Delta1, Scale2, Delta2>::value;
641
646template<typename T>
647struct is_temperature : std::false_type {};
648
649template<typename Scale, typename Delta>
650struct is_temperature<temperature<Scale, Delta>> : std::true_type {};
651
652template<typename T>
653inline constexpr bool is_temperature_v = is_temperature<T>::value;
666template<typename ToTemp, typename Scale, typename Delta>
668
679template<typename Scale, typename Delta>
681 static_assert(is_delta<Delta>::value, "Delta must be a thermo::delta type");
682
683public:
685 using scale = Scale;
689 using rep = typename Delta::rep;
690
692 constexpr temperature() = default;
693 temperature(const temperature&) = default;
694
700 template<typename Rep2>
701 requires std::convertible_to<const Rep2&, rep> && (treat_as_inexact_v<rep> || !treat_as_inexact_v<Rep2>)
702 constexpr explicit temperature(const Rep2& r)
703 : _d(static_cast<rep>(r)) {}
704
709 constexpr explicit temperature(const Delta& d)
710 : _d(d) {}
711
712 // Same-scale, different precision - implicit when lossless
713 template<typename Delta2>
714 requires(!std::is_same_v<Delta, Delta2>) &&
719 : _d(delta_cast<Delta>(Delta2(t.count()))) {}
720
721 // Same-scale, different precision - explicit when lossy
722 template<typename Delta2>
723 requires(!std::is_same_v<Delta, Delta2>) && (!treat_as_inexact_v<rep>) &&
725 constexpr explicit temperature(const temperature<Scale, Delta2>& t)
726 : _d(delta_cast<Delta>(Delta2(t.count()))) {}
727
728 // Cross-scale/precision conversion - implicit when lossless
729 template<typename Scale2, typename Delta2>
730 requires(!std::is_same_v<temperature, temperature<Scale2, Delta2>>) &&
735
736 // Cross-scale/precision conversion - explicit when lossy
737 template<typename Scale2, typename Delta2>
738 requires(!std::is_same_v<temperature, temperature<Scale2, Delta2>>) &&
742
743 ~temperature() = default;
745
747 constexpr rep count() const { return _d.count(); }
748
749 constexpr temperature& operator++() {
750 ++_d;
751 return *this;
752 }
753
754 constexpr temperature operator++(int) { return temperature(_d++); }
755
756 constexpr temperature& operator--() {
757 --_d;
758 return *this;
759 }
760
761 constexpr temperature operator--(int) { return temperature(_d--); }
762
772 {
773 return temperature(-_d);
774 }
775
776 template<typename Rep2, typename Precision2>
778 _d += d;
779 return *this;
780 }
781
782 template<typename Rep2, typename Precision2>
784 _d -= d;
785 return *this;
786 }
787
789 static constexpr temperature min() noexcept { return temperature(Delta::min()); }
790
792 static constexpr temperature max() noexcept { return temperature(Delta::max()); }
793
794private:
795 Delta _d{};
796};
797
798template<typename ToTemp, typename Scale, typename Delta>
800 using ToScale = typename ToTemp::scale;
801 using ToDelta = typename ToTemp::delta_type;
802 using to_rep = typename ToDelta::rep;
803
804 if constexpr (std::is_same_v<Scale, ToScale> && std::is_same_v<Delta, ToDelta>) {
805 return t;
806 } else if constexpr (std::is_same_v<Scale, ToScale>) {
807 return ToTemp(delta_cast<ToDelta>(Delta(t.count())));
808 } else {
809 using from_prec = typename Delta::precision;
810 using from_off = typename Scale::offset;
811 using to_prec = typename ToDelta::precision;
812 using to_off = typename ToScale::offset;
813
814 using offset_diff = std::ratio_subtract<from_off, to_off>;
815 using common_rep = std::common_type_t<typename Delta::rep, to_rep, intmax_t>;
816
817 common_rep from_val = static_cast<common_rep>(t.count());
818
819 using prec_ratio = std::ratio_divide<from_prec, to_prec>;
820 using offset_ratio = std::ratio_divide<offset_diff, to_prec>;
821
822 if constexpr (treat_as_inexact_v<common_rep>) {
823 constexpr double pr = static_cast<double>(prec_ratio::num) / prec_ratio::den;
824 constexpr double or_ = static_cast<double>(offset_ratio::num) / offset_ratio::den;
826 return ToTemp(ToDelta(static_cast<to_rep>(result)));
827 } else {
828 constexpr intmax_t combined_den =
829 static_cast<intmax_t>(prec_ratio::den) * static_cast<intmax_t>(offset_ratio::den);
831 from_val * static_cast<common_rep>(prec_ratio::num) * static_cast<common_rep>(offset_ratio::den) +
832 static_cast<common_rep>(offset_ratio::num) * static_cast<common_rep>(prec_ratio::den);
834 return ToTemp(ToDelta(static_cast<to_rep>(result)));
835 }
836 }
837}
838
840template<typename Scale, typename Delta1, typename Delta2>
843 using cd = std::common_type_t<Delta1, Delta2>;
844 return temperature<Scale, cd>(cd(lhs.count()) + cd(rhs.count()));
845}
846
848template<typename Scale, typename Delta1, typename Delta2>
851 using cd = std::common_type_t<Delta1, Delta2>;
852 return temperature<Scale, cd>(cd(lhs.count()) - cd(rhs.count()));
853}
854
863template<typename Scale, typename Delta1, typename Delta2>
865 -> std::common_type_t<Delta1, Delta2> {
866 using cd = std::common_type_t<Delta1, Delta2>;
867 return cd(lhs.count() - rhs.count());
868}
869
871template<typename Scale, typename Delta1, typename Rep2, typename Precision2>
874 using result_delta = std::common_type_t<Delta1, delta<Rep2, Precision2>>;
876}
877
879template<typename Rep1, typename Precision1, typename Scale, typename Delta2>
884
886template<typename Scale, typename Delta1, typename Rep2, typename Precision2>
889 using result_delta = std::common_type_t<Delta1, delta<Rep2, Precision2>>;
891}
892
893template<typename Scale, typename Delta1, typename Delta2>
895 return lhs.count() == rhs.count();
896}
897
898template<typename Scale, typename Delta1, typename Delta2>
899 requires std::three_way_comparable<std::common_type_t<typename Delta1::rep, typename Delta2::rep>>
901 return lhs.count() <=> rhs.count();
902}
903
922
927template<typename Scale, typename Rep, typename Precision>
928 requires(!treat_as_inexact_v<Rep>)
930 using dpc = std::ratio_divide<typename delta<Rep, Precision>::precision, typename Scale::degree>;
931 static_assert(_si_prefix_v<dpc> != nullptr, "thermo: precision has no SI prefix");
932 return std::to_string(t.count()) + _si_prefix_v<dpc> + Scale::suffix;
933}
934
935} // namespace thermo
936
937namespace std {
938
939template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
940struct common_type<thermo::delta<Rep1, Precision1>, thermo::delta<Rep2, Precision2>> {
941private:
942 using common_precision = std::ratio<
943 thermo::_gcd(Precision1::num, Precision2::num),
944 (Precision1::den / thermo::_gcd(Precision1::den, Precision2::den)) * Precision2::den>;
945
946public:
947 using type = thermo::delta<std::common_type_t<Rep1, Rep2>, common_precision>;
948};
949
950template<typename Scale, typename Delta1, typename Delta2>
951struct common_type<thermo::temperature<Scale, Delta1>, thermo::temperature<Scale, Delta2>> {
953};
954
955#if CONFIG_THERMO_STD_FORMAT
956// "{}" prints the exact stored count with a precision-qualified unit
957// (millicelsius(22500) -> "22500m°C"). A non-empty spec is applied to the
958// value in scale degrees (as double), honoring the standard floating-point
959// format spec: std::format("{:.1f}", millicelsius(22534)) == "22.5°C".
960template<typename Scale, typename Rep, typename Precision>
961struct formatter<thermo::temperature<Scale, thermo::delta<Rep, Precision>>> {
962private:
963 using _delta_t = thermo::delta<Rep, Precision>;
964 using _dpc = typename ratio_divide<typename _delta_t::precision, typename Scale::degree>::type;
965 static constexpr const char* _prefix = thermo::_si_prefix_v<_dpc>;
966 std::formatter<double> _num;
967 bool _has_spec = false;
968
969public:
970 constexpr auto parse(format_parse_context& ctx) {
971 auto it = ctx.begin();
972 if (it == ctx.end() || *it == '}') {
973 if constexpr (_prefix == nullptr) {
974 thermo::_format_error("thermo: precision has no SI prefix; use an explicit format spec");
975 }
976 return it;
977 }
978 _has_spec = true;
979 return _num.parse(ctx);
980 }
981
982 template<typename FormatContext>
983 auto format(const thermo::temperature<Scale, _delta_t>& t, FormatContext& ctx) const {
984 if (_has_spec) {
985 double degrees = static_cast<double>(t.count()) * _dpc::num / _dpc::den;
986 auto out = _num.format(degrees, ctx);
987 return thermo::_format_append(out, Scale::suffix);
988 }
989 auto out = std::format_to(ctx.out(), "{}", t.count());
990 out = thermo::_format_append(out, _prefix);
991 return thermo::_format_append(out, Scale::suffix);
992 }
993};
994
995// "{}" prints the exact stored count with a precision-qualified unit
996// (delta_millicelsius(1500) -> "1500Δm°C"). A non-empty spec is applied to
997// the value in scale degrees (as double):
998// std::format("{:.1f}", delta_millicelsius(1500)) == "1.5Δ°C".
999template<typename Rep, typename Precision>
1000struct formatter<thermo::delta<Rep, Precision>> {
1001private:
1002 using _delta_t = thermo::delta<Rep, Precision>;
1003 using _unit = thermo::_delta_unit<typename _delta_t::precision>; // hard error if unmapped
1004 using _dpc = typename ratio_divide<typename _delta_t::precision, typename _unit::degree>::type;
1005 static constexpr const char* _prefix = thermo::_si_prefix_v<_dpc>;
1006 std::formatter<double> _num;
1007 bool _has_spec = false;
1008
1009public:
1010 constexpr auto parse(format_parse_context& ctx) {
1011 auto it = ctx.begin();
1012 if (it == ctx.end() || *it == '}') {
1013 if constexpr (_prefix == nullptr) {
1014 thermo::_format_error("thermo: precision has no SI prefix; use an explicit format spec");
1015 }
1016 return it;
1017 }
1018 _has_spec = true;
1019 return _num.parse(ctx);
1020 }
1021
1022 template<typename FormatContext>
1023 auto format(const _delta_t& d, FormatContext& ctx) const {
1024 if (_has_spec) {
1025 double degrees = static_cast<double>(d.count()) * _dpc::num / _dpc::den;
1026 auto out = _num.format(degrees, ctx);
1027 out = thermo::_format_append(out, "Δ");
1028 return thermo::_format_append(out, _unit::suffix);
1029 }
1030 auto out = std::format_to(ctx.out(), "{}", d.count());
1031 out = thermo::_format_append(out, "Δ");
1032 out = thermo::_format_append(out, _prefix);
1033 return thermo::_format_append(out, _unit::suffix);
1034 }
1035};
1036#endif
1037
1038} // namespace std
1039
1045namespace detail {
1046
1047template<unsigned long long Value, unsigned long long Power>
1048struct pow10 {
1049 static constexpr unsigned long long value = 10 * pow10<Value, Power - 1>::value;
1050};
1051
1052template<unsigned long long Value>
1053struct pow10<Value, 0> {
1054 static constexpr unsigned long long value = Value;
1055};
1056
1057template<char... Digits>
1058struct parse_int;
1059
1060template<char D, char... Rest>
1061struct parse_int<D, Rest...> {
1062 static_assert(D >= '0' && D <= '9', "invalid digit");
1063 static constexpr unsigned long long value = pow10<D - '0', sizeof...(Rest)>::value + parse_int<Rest...>::value;
1064};
1065
1066template<char D>
1067struct parse_int<D> {
1068 static_assert(D >= '0' && D <= '9', "invalid digit");
1069 static constexpr unsigned long long value = D - '0';
1070};
1071
1072template<typename Delta, char... Digits>
1073constexpr Delta check_overflow() {
1074 using parsed = parse_int<Digits...>;
1075 constexpr typename Delta::rep repval = parsed::value;
1076 static_assert(
1077 repval >= 0 && static_cast<unsigned long long>(repval) == parsed::value,
1078 "literal value cannot be represented by delta type"
1079 );
1080 return Delta(repval);
1081}
1082
1083} // namespace detail
1087template<char... Digits>
1088constexpr thermo::celsius operator""_c() {
1089 return thermo::celsius(detail::check_overflow<thermo::delta_celsius, Digits...>());
1090}
1091
1093template<char... Digits>
1094constexpr thermo::decicelsius operator""_dc() {
1095 return thermo::decicelsius(detail::check_overflow<thermo::delta_decicelsius, Digits...>());
1096}
1097
1099template<char... Digits>
1100constexpr thermo::millicelsius operator""_mc() {
1101 return thermo::millicelsius(detail::check_overflow<thermo::delta_millicelsius, Digits...>());
1102}
1103
1105template<char... Digits>
1106constexpr thermo::kelvin operator""_k() {
1107 return thermo::kelvin(detail::check_overflow<thermo::delta_kelvin, Digits...>());
1108}
1109
1111template<char... Digits>
1112constexpr thermo::decikelvin operator""_dk() {
1113 return thermo::decikelvin(detail::check_overflow<thermo::delta_decikelvin, Digits...>());
1114}
1115
1117template<char... Digits>
1118constexpr thermo::millikelvin operator""_mk() {
1119 return thermo::millikelvin(detail::check_overflow<thermo::delta_millikelvin, Digits...>());
1120}
1121
1123template<char... Digits>
1124constexpr thermo::fahrenheit operator""_f() {
1125 return thermo::fahrenheit(detail::check_overflow<thermo::delta_fahrenheit, Digits...>());
1126}
1127
1129template<char... Digits>
1130constexpr thermo::decifahrenheit operator""_df() {
1131 return thermo::decifahrenheit(detail::check_overflow<thermo::delta_decifahrenheit, Digits...>());
1132}
1133
1135template<char... Digits>
1136constexpr thermo::millifahrenheit operator""_mf() {
1137 return thermo::millifahrenheit(detail::check_overflow<thermo::delta_millifahrenheit, Digits...>());
1138}
1139
1141template<char... Digits>
1142constexpr thermo::delta_celsius operator""_Δc() {
1143 return detail::check_overflow<thermo::delta_celsius, Digits...>();
1144}
1145
1147template<char... Digits>
1148constexpr thermo::delta_millicelsius operator""_Δmc() {
1149 return detail::check_overflow<thermo::delta_millicelsius, Digits...>();
1150}
1151
1153template<char... Digits>
1154constexpr thermo::delta_kelvin operator""_Δk() {
1155 return detail::check_overflow<thermo::delta_kelvin, Digits...>();
1156}
1157
1159template<char... Digits>
1160constexpr thermo::delta_millikelvin operator""_Δmk() {
1161 return detail::check_overflow<thermo::delta_millikelvin, Digits...>();
1162}
1163
1165template<char... Digits>
1166constexpr thermo::delta_fahrenheit operator""_Δf() {
1167 return detail::check_overflow<thermo::delta_fahrenheit, Digits...>();
1168}
1169
1171template<char... Digits>
1172constexpr thermo::delta_millifahrenheit operator""_Δmf() {
1173 return detail::check_overflow<thermo::delta_millifahrenheit, Digits...>();
1174}
1175
1176} // namespace thermo_literals
A temperature difference with a representation and precision.
Definition thermo.hpp:122
constexpr delta & operator*=(const rep &r)
Definition thermo.hpp:205
static constexpr delta zero() noexcept
Returns a zero-length delta.
Definition thermo.hpp:230
constexpr delta & operator++()
Definition thermo.hpp:181
static constexpr delta min() noexcept
Returns the minimum (most negative) representable delta.
Definition thermo.hpp:233
constexpr delta()=default
Constructs a zero delta.
constexpr delta & operator/=(const rep &r)
Definition thermo.hpp:210
static constexpr delta max() noexcept
Returns the maximum representable delta.
Definition thermo.hpp:236
Rep rep
The representation type.
Definition thermo.hpp:129
constexpr delta & operator--()
Definition thermo.hpp:188
constexpr rep count() const
Returns the tick count.
Definition thermo.hpp:171
constexpr delta operator--(int)
Definition thermo.hpp:193
delta & operator=(const delta &)=default
constexpr delta & operator-=(const delta &d)
Definition thermo.hpp:200
constexpr delta & operator%=(const delta &d)
Definition thermo.hpp:222
typename Precision::type precision
The precision as a std::ratio.
Definition thermo.hpp:131
delta(const delta &)=default
constexpr delta< typename std::common_type< rep >::type, precision > operator+() const
Definition thermo.hpp:173
constexpr delta operator++(int)
Definition thermo.hpp:186
constexpr delta(const Rep2 &r)
Constructs from a tick count.
Definition thermo.hpp:148
constexpr delta & operator+=(const delta &d)
Definition thermo.hpp:195
constexpr delta< typename std::common_type< rep >::type, precision > operator-() const
Definition thermo.hpp:177
~delta()=default
constexpr delta & operator%=(const rep &r)
Definition thermo.hpp:215
An absolute temperature on a given scale.
Definition thermo.hpp:680
typename Delta::rep rep
The representation type.
Definition thermo.hpp:689
Delta delta_type
The delta type.
Definition thermo.hpp:687
constexpr temperature()=default
Constructs a temperature at the scale's zero point.
constexpr temperature operator-() const
Negates the temperature (e.g.
Definition thermo.hpp:770
constexpr temperature & operator++()
Definition thermo.hpp:749
constexpr temperature(const temperature< Scale, Delta2 > &t)
Definition thermo.hpp:718
constexpr temperature(const temperature< Scale2, Delta2 > &t)
Definition thermo.hpp:733
constexpr temperature(const Rep2 &r)
Constructs from a tick count.
Definition thermo.hpp:702
static constexpr temperature max() noexcept
Returns the maximum representable temperature.
Definition thermo.hpp:792
constexpr temperature operator--(int)
Definition thermo.hpp:761
constexpr temperature(const temperature< Scale, Delta2 > &t)
Definition thermo.hpp:725
temperature(const temperature &)=default
constexpr temperature(const Delta &d)
Constructs from a delta.
Definition thermo.hpp:709
static constexpr temperature min() noexcept
Returns the minimum representable temperature.
Definition thermo.hpp:789
~temperature()=default
constexpr temperature operator++(int)
Definition thermo.hpp:754
constexpr temperature & operator+=(const delta< Rep2, Precision2 > &d)
Definition thermo.hpp:777
Scale scale
The temperature scale.
Definition thermo.hpp:685
constexpr temperature & operator--()
Definition thermo.hpp:756
constexpr rep count() const
Returns the tick count.
Definition thermo.hpp:747
constexpr temperature & operator-=(const delta< Rep2, Precision2 > &d)
Definition thermo.hpp:783
temperature & operator=(const temperature &)=default
constexpr temperature(const temperature< Scale2, Delta2 > &t)
Definition thermo.hpp:740
User-defined literals for temperature types.
Definition thermo.hpp:1043
Temperature types and utilities.
Definition thermo.hpp:39
temperature< celsius_scale > celsius
Celsius with 1 degree precision.
Definition thermo.hpp:905
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:391
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:564
temperature< fahrenheit_scale, delta< int64_t, std::ratio< 5, 9000 > > > millifahrenheit
Fahrenheit with 0.001 degree precision.
Definition thermo.hpp:921
delta< int64_t, std::milli > delta_millicelsius
Delta with 0.001 degree precision (Celsius/Kelvin).
Definition thermo.hpp:468
constexpr ToDelta round(const delta< Rep, Precision > &d)
Rounds a delta to the nearest representable value in the target precision.
Definition thermo.hpp:349
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:383
constexpr ToDelta delta_cast(const delta< Rep, Precision > &d)
Converts a delta to a different precision or representation.
Definition thermo.hpp:253
constexpr bool operator==(const delta< Rep1, Precision1 > &lhs, const delta< Rep2, Precision2 > &rhs)
Definition thermo.hpp:451
delta< int64_t, std::milli > delta_millikelvin
Delta with 0.001 degree precision (Celsius/Kelvin).
Definition thermo.hpp:474
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:400
delta< int64_t, std::ratio< 5, 9000 > > delta_millifahrenheit
Delta with 0.001°F precision.
Definition thermo.hpp:480
temperature< fahrenheit_scale, delta< int64_t, std::ratio< 5, 9 > > > fahrenheit
Fahrenheit with 1 degree precision.
Definition thermo.hpp:917
constexpr ToDelta ceil(const delta< Rep, Precision > &d)
Rounds a delta up to the nearest representable value in the target precision.
Definition thermo.hpp:289
delta< int64_t, std::ratio< 5, 9 > > delta_fahrenheit
Delta with 1°F precision.
Definition thermo.hpp:476
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:864
constexpr ToDelta floor(const delta< Rep, Precision > &d)
Rounds a delta down to the nearest representable value in the target precision.
Definition thermo.hpp:310
delta< int64_t > delta_kelvin
Delta with 1 degree precision (Celsius/Kelvin).
Definition thermo.hpp:470
temperature< celsius_scale, delta< int64_t, std::deci > > decicelsius
Celsius with 0.1 degree precision.
Definition thermo.hpp:907
temperature< kelvin_scale, delta< int64_t, std::milli > > millikelvin
Kelvin with 0.001 degree precision.
Definition thermo.hpp:915
constexpr ToTemp temperature_cast(const temperature< Scale, Delta > &t)
Converts a temperature to a different scale or precision.
Definition thermo.hpp:799
constexpr auto operator<=>(const delta< Rep1, Precision1 > &lhs, const delta< Rep2, Precision2 > &rhs)
Definition thermo.hpp:458
temperature< fahrenheit_scale, delta< int64_t, std::ratio< 5, 90 > > > decifahrenheit
Fahrenheit with 0.1 degree precision.
Definition thermo.hpp:919
temperature< kelvin_scale, delta< int64_t, std::deci > > decikelvin
Kelvin with 0.1 degree precision.
Definition thermo.hpp:913
temperature< kelvin_scale > kelvin
Kelvin with 1 degree precision.
Definition thermo.hpp:911
temperature< celsius_scale, delta< int64_t, std::milli > > millicelsius
Celsius with 0.001 degree precision.
Definition thermo.hpp:909
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:417
delta< int64_t > delta_celsius
Delta with 1 degree precision (Celsius/Kelvin).
Definition thermo.hpp:464
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:331
Trait to detect delta specializations.
Definition thermo.hpp:49