thermo 2.1.0
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 <limits>
12#include <ratio>
13#include <string>
14#include <version> // pulls in __cpp_lib_format so the detection below works in non-IDF builds
15#ifndef CONFIG_THERMO_STD_FORMAT
16#if __has_include(<format>) && defined(__cpp_lib_format)
17#define CONFIG_THERMO_STD_FORMAT 1
18#else
19#define CONFIG_THERMO_STD_FORMAT 0
20#endif
21#endif
22
23#if CONFIG_THERMO_STD_FORMAT
24#include <format>
25#endif
26#include <assert.h>
27
28static_assert(__cplusplus >= 202302L, "thermo requires C++23 or later");
29
38namespace thermo {
39
40template<typename Rep, typename Precision = std::ratio<1>>
41class delta;
42
47template<typename _T>
48struct is_delta : std::false_type {};
49
50template<typename Rep, typename Precision>
51struct is_delta<delta<Rep, Precision>> : std::true_type {};
52
54template<typename T, template<typename...> class Template>
55struct _is_specialization_of : std::false_type {};
56
57template<template<typename...> class Template, typename... Args>
58struct _is_specialization_of<Template<Args...>, Template> : std::true_type {};
59
60template<typename T, template<typename...> class Template>
61inline constexpr bool _is_specialization_of_v = _is_specialization_of<T, Template>::value;
62
63template<typename Rep>
64concept not_delta = !_is_specialization_of_v<Rep, delta>;
65
66template<typename Rep>
67struct delta_values {
69 static constexpr Rep zero() noexcept { return Rep(0); }
70
72 static constexpr Rep max() noexcept { return std::numeric_limits<Rep>::max(); }
73
75 static constexpr Rep min() noexcept { return std::numeric_limits<Rep>::lowest(); }
76};
77
78template<typename T>
79struct _is_ratio : std::false_type {};
80
81template<std::intmax_t Num, std::intmax_t Denom>
82struct _is_ratio<std::ratio<Num, Denom>> : std::true_type {};
83
84template<typename T>
85struct treat_as_inexact : std::bool_constant<std::floating_point<T>> {};
86
87template<typename T>
88inline constexpr bool treat_as_inexact_v = treat_as_inexact<T>::value;
89
90consteval intmax_t _gcd(intmax_t m, intmax_t n) noexcept {
91 while (n != 0) {
92 intmax_t rem = m % n;
93 m = n;
94 n = rem;
95 }
96 return m;
97}
98
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);
104}();
105
106template<typename From, typename To>
107concept _harmonic_precision = _safe_ratio_divide_den<From, To> == 1;
120template<typename Rep, typename Precision>
121class delta {
122 static_assert(!is_delta<Rep>::value, "rep cannot be a thermo::delta");
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");
125
126public:
128 using rep = Rep;
130 using precision = typename Precision::type;
131
133 constexpr delta() = default;
134 delta(const delta&) = default;
135
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)) {}
149
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>))
163 constexpr delta(const delta<Rep2, Precision2>& temp)
164 : _r(delta_cast<delta>(temp).count()) {}
165
166 ~delta() = default;
167 delta& operator=(const delta&) = default;
168
170 constexpr rep count() const { return _r; }
171
175
179
180 constexpr delta& operator++() {
181 ++_r;
182 return *this;
183 }
184
185 constexpr delta operator++(int) { return delta(_r++); }
186
187 constexpr delta& operator--() {
188 --_r;
189 return *this;
190 }
191
192 constexpr delta operator--(int) { return delta(_r--); }
193
194 constexpr delta& operator+=(const delta& d) {
195 _r += d.count();
196 return *this;
197 }
198
199 constexpr delta& operator-=(const delta& d) {
200 _r -= d.count();
201 return *this;
202 }
203
204 constexpr delta& operator*=(const rep& r) {
205 _r *= r;
206 return *this;
207 }
208
209 constexpr delta& operator/=(const rep& r) {
210 _r /= r;
211 return *this;
212 }
213
214 constexpr delta& operator%=(const rep& r)
215 requires(!treat_as_inexact_v<rep>)
216 {
217 _r %= r;
218 return *this;
219 }
220
221 constexpr delta& operator%=(const delta& d)
222 requires(!treat_as_inexact_v<rep>)
223 {
224 _r %= d.count();
225 return *this;
226 }
227
229 static constexpr delta zero() noexcept { return delta(delta_values<rep>::zero()); }
230
232 static constexpr delta min() noexcept { return delta(delta_values<rep>::min()); }
233
235 static constexpr delta max() noexcept { return delta(delta_values<rep>::max()); }
236
237private:
238 rep _r{};
239};
240
251template<typename ToDelta, typename Rep, typename Precision>
252constexpr ToDelta delta_cast(const delta<Rep, Precision>& d) {
253 if constexpr (std::is_same_v<ToDelta, delta<Rep, Precision>>) {
254 return d;
255 } else {
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>;
260
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)));
267 } else {
268 return ToDelta(
269 static_cast<to_rep>(static_cast<cr>(d.count()) * static_cast<cr>(cf::num) / static_cast<cr>(cf::den))
270 );
271 }
272 }
273}
274
287template<typename ToDelta, typename Rep, typename Precision>
288constexpr ToDelta ceil(const delta<Rep, Precision>& d) {
289 ToDelta result = delta_cast<ToDelta>(d);
290 if (result < d) {
291 return ToDelta(result.count() + 1);
292 }
293 return result;
294}
295
308template<typename ToDelta, typename Rep, typename Precision>
311 if (result > d) {
312 return ToDelta(result.count() - 1);
313 }
314 return result;
315}
316
329template<typename ToDelta, typename Rep, typename Precision>
331 return delta_cast<ToDelta>(d);
332}
333
347template<typename ToDelta, typename Rep, typename Precision>
350 if (result == d) {
351 return result;
352 }
353
354 // Calculate the midpoint between result and the next value
355 ToDelta next = (result < d) ? ToDelta(result.count() + 1) : ToDelta(result.count() - 1);
356
357 // Convert both to source type for comparison
360
361 // Calculate distances
364
365 // If closer to next, or exactly halfway and away from zero, use next
367 return next;
368 } else if (dist_to_next == dist_to_result) {
369 // Tie: round away from zero
370 if (d.count() >= 0) {
371 return (next > result) ? next : result;
372 } else {
373 return (next < result) ? next : result;
374 }
375 }
376
377 return result;
378}
379
381template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
383 -> std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>> {
384 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
385 return cd(cd(lhs).count() + cd(rhs).count());
386}
387
389template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
391 -> std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>> {
392 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
393 return cd(cd(lhs).count() - cd(rhs).count());
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& r)
402 return cd(cd(d).count() * r);
403}
404
406template<typename Rep1, typename Rep2, typename Precision>
407 requires not_delta<Rep1> && std::convertible_to<const Rep1&, std::common_type_t<Rep1, Rep2>>
408constexpr auto operator*(const Rep1& r, const delta<Rep2, Precision>& d)
410 return d * r;
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>>
416constexpr auto operator/(const delta<Rep1, Precision>& d, const Rep2& s)
419 return cd(cd(d).count() / s);
420}
421
423template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
425 -> std::common_type_t<Rep1, Rep2> {
426 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
427 return cd(lhs).count() / cd(rhs).count();
428}
429
431template<typename Rep1, typename Precision, typename Rep2>
432 requires not_delta<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>> &&
434constexpr auto operator%(const delta<Rep1, Precision>& d, const Rep2& s)
437 return cd(cd(d).count() % s);
438}
439
441template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
443constexpr auto operator%(const delta<Rep1, Precision1>& lhs, const delta<Rep2, Precision2>& rhs)
444 -> std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>> {
445 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
446 return cd(cd(lhs).count() % cd(rhs).count());
447}
448
449template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
451 using ct = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
452 return ct(lhs).count() == ct(rhs).count();
453}
454
455template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
456 requires std::three_way_comparable<std::common_type_t<Rep1, Rep2>>
458 using ct = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
459 return ct(lhs).count() <=> ct(rhs).count();
460}
461
480
482// SI prefix for a degrees-per-count ratio; nullptr when unmapped.
483template<typename Ratio>
484struct _si_prefix {
485 static constexpr const char* value = nullptr;
486};
487template<>
488struct _si_prefix<std::ratio<1>> {
489 static constexpr const char* value = "";
490};
491template<>
492struct _si_prefix<std::ratio<1, 10>> {
493 static constexpr const char* value = "d";
494};
495template<>
496struct _si_prefix<std::ratio<1, 1000>> {
497 static constexpr const char* value = "m";
498};
499template<typename Ratio>
500inline constexpr const char* _si_prefix_v = _si_prefix<typename Ratio::type>::value;
501
502// Display family (degree size + unit suffix) for a delta precision, keyed on
503// NORMALIZED ratios. Undefined primary template: unmapped precisions have no
504// determinable unit and are not formattable.
505template<typename Precision>
506struct _delta_unit;
507struct _celsius_delta_unit {
508 using degree = std::ratio<1>;
509 static constexpr const char* suffix = "°C";
510};
512 using degree = std::ratio<5, 9>;
513 static constexpr const char* suffix = "°F";
514};
515template<>
517template<>
518struct _delta_unit<std::ratio<1, 10>> : _celsius_delta_unit {};
519template<>
520struct _delta_unit<std::ratio<1, 1000>> : _celsius_delta_unit {};
521template<>
522struct _delta_unit<std::ratio<5, 9>> : _fahrenheit_delta_unit {};
523template<>
524struct _delta_unit<std::ratio<1, 18>> : _fahrenheit_delta_unit {}; // 5/90 normalized
525template<>
526struct _delta_unit<std::ratio<1, 1800>> : _fahrenheit_delta_unit {}; // 5/9000 normalized
527
528// Appends a NUL-terminated string to a format output iterator.
529template<typename OutputIt>
530constexpr OutputIt _format_append(OutputIt out, const char* s) {
531 for (; *s != '\0'; ++s) {
532 *out++ = *s;
533 }
534 return out;
535}
542template<typename Rep, typename Precision>
543 requires(!treat_as_inexact_v<Rep>)
544std::string to_string(const delta<Rep, Precision>& d) {
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");
548 return std::to_string(d.count()) + "Δ" + _si_prefix_v<dpc> + unit::suffix;
549}
550
551// ============================================================================
552// Temperature Scales and Absolute Temperatures
553// ============================================================================
554
561struct celsius_scale {
562 using offset = std::ratio<27315, 100>;
563 using degree = std::ratio<1>;
564 static constexpr const char* suffix = "°C";
565};
566
572struct kelvin_scale {
573 using offset = std::ratio<0>;
574 using degree = std::ratio<1>;
575 static constexpr const char* suffix = "K";
576};
577
583struct fahrenheit_scale {
584 using offset = std::ratio<45967, 180>;
585 using degree = std::ratio<5, 9>;
586 static constexpr const char* suffix = "°F";
587};
588
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>>
602class temperature;
603
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;
611
612 using prec_ratio = std::ratio_divide<from_prec, to_prec>;
613 using offset_diff = std::ratio_subtract<from_off, to_off>;
614 using offset_in_target_units = std::ratio_divide<offset_diff, to_prec>;
615
616 static constexpr bool value = prec_ratio::den == 1 && offset_in_target_units::den == 1;
617};
618
619template<typename Scale1, typename Delta1, typename Scale2, typename Delta2>
620concept _lossless_temperature_conversion = _lossless_temperature_conversion_impl<Scale1, Delta1, Scale2, Delta2>::value;
621
626template<typename T>
627struct is_temperature : std::false_type {};
628
629template<typename Scale, typename Delta>
630struct is_temperature<temperature<Scale, Delta>> : std::true_type {};
631
632template<typename T>
633inline constexpr bool is_temperature_v = is_temperature<T>::value;
646template<typename ToTemp, typename Scale, typename Delta>
648
659template<typename Scale, typename Delta>
661 static_assert(is_delta<Delta>::value, "Delta must be a thermo::delta type");
662
663public:
665 using scale = Scale;
669 using rep = typename Delta::rep;
670
672 constexpr temperature() = default;
673 temperature(const temperature&) = default;
674
680 template<typename Rep2>
681 requires std::convertible_to<const Rep2&, rep> && (treat_as_inexact_v<rep> || !treat_as_inexact_v<Rep2>)
682 constexpr explicit temperature(const Rep2& r)
683 : _d(static_cast<rep>(r)) {}
684
689 constexpr explicit temperature(const Delta& d)
690 : _d(d) {}
691
692 // Same-scale, different precision - implicit when lossless
693 template<typename Delta2>
694 requires(!std::is_same_v<Delta, Delta2>) &&
699 : _d(delta_cast<Delta>(Delta2(t.count()))) {}
700
701 // Same-scale, different precision - explicit when lossy
702 template<typename Delta2>
703 requires(!std::is_same_v<Delta, Delta2>) && (!treat_as_inexact_v<rep>) &&
705 constexpr explicit temperature(const temperature<Scale, Delta2>& t)
706 : _d(delta_cast<Delta>(Delta2(t.count()))) {}
707
708 // Cross-scale/precision conversion - implicit when lossless
709 template<typename Scale2, typename Delta2>
710 requires(!std::is_same_v<temperature, temperature<Scale2, Delta2>>) &&
715
716 // Cross-scale/precision conversion - explicit when lossy
717 template<typename Scale2, typename Delta2>
718 requires(!std::is_same_v<temperature, temperature<Scale2, Delta2>>) &&
722
723 ~temperature() = default;
725
727 constexpr rep count() const { return _d.count(); }
728
729 constexpr temperature& operator++() {
730 ++_d;
731 return *this;
732 }
733
734 constexpr temperature operator++(int) { return temperature(_d++); }
735
736 constexpr temperature& operator--() {
737 --_d;
738 return *this;
739 }
740
741 constexpr temperature operator--(int) { return temperature(_d--); }
742
752 {
753 return temperature(-_d);
754 }
755
756 template<typename Rep2, typename Precision2>
758 _d += d;
759 return *this;
760 }
761
762 template<typename Rep2, typename Precision2>
764 _d -= d;
765 return *this;
766 }
767
769 static constexpr temperature min() noexcept { return temperature(Delta::min()); }
770
772 static constexpr temperature max() noexcept { return temperature(Delta::max()); }
773
774private:
775 Delta _d{};
776};
777
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;
783
784 if constexpr (std::is_same_v<Scale, ToScale> && std::is_same_v<Delta, ToDelta>) {
785 return t;
786 } else if constexpr (std::is_same_v<Scale, ToScale>) {
787 return ToTemp(delta_cast<ToDelta>(Delta(t.count())));
788 } else {
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;
793
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>;
796
797 common_rep from_val = static_cast<common_rep>(t.count());
798
799 using prec_ratio = std::ratio_divide<from_prec, to_prec>;
800 using offset_ratio = std::ratio_divide<offset_diff, to_prec>;
801
802 if constexpr (treat_as_inexact_v<common_rep>) {
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;
806 return ToTemp(ToDelta(static_cast<to_rep>(result)));
807 } else {
808 constexpr intmax_t combined_den =
809 static_cast<intmax_t>(prec_ratio::den) * static_cast<intmax_t>(offset_ratio::den);
811 from_val * static_cast<common_rep>(prec_ratio::num) * static_cast<common_rep>(offset_ratio::den) +
812 static_cast<common_rep>(offset_ratio::num) * static_cast<common_rep>(prec_ratio::den);
814 return ToTemp(ToDelta(static_cast<to_rep>(result)));
815 }
816 }
817}
818
820template<typename Scale, typename Delta1, typename Delta2>
823 using cd = std::common_type_t<Delta1, Delta2>;
824 return temperature<Scale, cd>(cd(lhs.count()) + cd(rhs.count()));
825}
826
828template<typename Scale, typename Delta1, typename Delta2>
831 using cd = std::common_type_t<Delta1, Delta2>;
832 return temperature<Scale, cd>(cd(lhs.count()) - cd(rhs.count()));
833}
834
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());
848}
849
851template<typename Scale, typename Delta1, typename Rep2, typename Precision2>
854 using result_delta = std::common_type_t<Delta1, delta<Rep2, Precision2>>;
856}
857
859template<typename Rep1, typename Precision1, typename Scale, typename Delta2>
864
866template<typename Scale, typename Delta1, typename Rep2, typename Precision2>
869 using result_delta = std::common_type_t<Delta1, delta<Rep2, Precision2>>;
871}
872
873template<typename Scale, typename Delta1, typename Delta2>
875 return lhs.count() == rhs.count();
876}
877
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();
882}
883
902
907template<typename Scale, typename Rep, typename Precision>
908 requires(!treat_as_inexact_v<Rep>)
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");
912 return std::to_string(t.count()) + _si_prefix_v<dpc> + Scale::suffix;
913}
914
915} // namespace thermo
916
917namespace std {
918
919template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
920struct common_type<thermo::delta<Rep1, Precision1>, thermo::delta<Rep2, Precision2>> {
921private:
922 using common_precision = std::ratio<
923 thermo::_gcd(Precision1::num, Precision2::num),
924 (Precision1::den / thermo::_gcd(Precision1::den, Precision2::den)) * Precision2::den>;
925
926public:
927 using type = thermo::delta<std::common_type_t<Rep1, Rep2>, common_precision>;
928};
929
930template<typename Scale, typename Delta1, typename Delta2>
931struct common_type<thermo::temperature<Scale, Delta1>, thermo::temperature<Scale, Delta2>> {
933};
934
935#if CONFIG_THERMO_STD_FORMAT
936// "{}" prints the exact stored count with a precision-qualified unit
937// (millicelsius(22500) -> "22500m°C"). A non-empty spec is applied to the
938// value in scale degrees (as double), honoring the standard floating-point
939// format spec: std::format("{:.1f}", millicelsius(22534)) == "22.5°C".
940template<typename Scale, typename Rep, typename Precision>
941struct formatter<thermo::temperature<Scale, thermo::delta<Rep, Precision>>> {
942private:
943 using _delta_t = thermo::delta<Rep, Precision>;
944 using _dpc = typename ratio_divide<typename _delta_t::precision, typename Scale::degree>::type;
945 static constexpr const char* _prefix = thermo::_si_prefix_v<_dpc>;
946 std::formatter<double> _num;
947 bool _has_spec = false;
948
949public:
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");
955 }
956 return it;
957 }
958 _has_spec = true;
959 return _num.parse(ctx);
960 }
961
962 template<typename FormatContext>
963 auto format(const thermo::temperature<Scale, _delta_t>& t, FormatContext& ctx) const {
964 if (_has_spec) {
965 double degrees = static_cast<double>(t.count()) * _dpc::num / _dpc::den;
966 auto out = _num.format(degrees, ctx);
967 return thermo::_format_append(out, Scale::suffix);
968 }
969 auto out = std::format_to(ctx.out(), "{}", t.count());
970 out = thermo::_format_append(out, _prefix);
971 return thermo::_format_append(out, Scale::suffix);
972 }
973};
974
975// "{}" prints the exact stored count with a precision-qualified unit
976// (delta_millicelsius(1500) -> "1500Δm°C"). A non-empty spec is applied to
977// the value in scale degrees (as double):
978// std::format("{:.1f}", delta_millicelsius(1500)) == "1.5Δ°C".
979template<typename Rep, typename Precision>
980struct formatter<thermo::delta<Rep, Precision>> {
981private:
982 using _delta_t = thermo::delta<Rep, Precision>;
983 using _unit = thermo::_delta_unit<typename _delta_t::precision>; // hard error if unmapped
984 using _dpc = typename ratio_divide<typename _delta_t::precision, typename _unit::degree>::type;
985 static constexpr const char* _prefix = thermo::_si_prefix_v<_dpc>;
986 std::formatter<double> _num;
987 bool _has_spec = false;
988
989public:
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");
995 }
996 return it;
997 }
998 _has_spec = true;
999 return _num.parse(ctx);
1000 }
1001
1002 template<typename FormatContext>
1003 auto format(const _delta_t& d, FormatContext& ctx) const {
1004 if (_has_spec) {
1005 double degrees = static_cast<double>(d.count()) * _dpc::num / _dpc::den;
1006 auto out = _num.format(degrees, ctx);
1007 out = thermo::_format_append(out, "Δ");
1008 return thermo::_format_append(out, _unit::suffix);
1009 }
1010 auto out = std::format_to(ctx.out(), "{}", d.count());
1011 out = thermo::_format_append(out, "Δ");
1012 out = thermo::_format_append(out, _prefix);
1013 return thermo::_format_append(out, _unit::suffix);
1014 }
1015};
1016#endif
1017
1018} // namespace std
1019
1025namespace detail {
1026
1027template<unsigned long long Value, unsigned long long Power>
1028struct pow10 {
1029 static constexpr unsigned long long value = 10 * pow10<Value, Power - 1>::value;
1030};
1031
1032template<unsigned long long Value>
1033struct pow10<Value, 0> {
1034 static constexpr unsigned long long value = Value;
1035};
1036
1037template<char... Digits>
1038struct parse_int;
1039
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;
1044};
1045
1046template<char D>
1047struct parse_int<D> {
1048 static_assert(D >= '0' && D <= '9', "invalid digit");
1049 static constexpr unsigned long long value = D - '0';
1050};
1051
1052template<typename Delta, char... Digits>
1053constexpr Delta check_overflow() {
1054 using parsed = parse_int<Digits...>;
1055 constexpr typename Delta::rep repval = parsed::value;
1056 static_assert(
1057 repval >= 0 && static_cast<unsigned long long>(repval) == parsed::value,
1058 "literal value cannot be represented by delta type"
1059 );
1060 return Delta(repval);
1061}
1062
1063} // namespace detail
1067template<char... Digits>
1068constexpr thermo::celsius operator""_c() {
1069 return thermo::celsius(detail::check_overflow<thermo::delta_celsius, Digits...>());
1070}
1071
1073template<char... Digits>
1074constexpr thermo::decicelsius operator""_dc() {
1075 return thermo::decicelsius(detail::check_overflow<thermo::delta_decicelsius, Digits...>());
1076}
1077
1079template<char... Digits>
1080constexpr thermo::millicelsius operator""_mc() {
1081 return thermo::millicelsius(detail::check_overflow<thermo::delta_millicelsius, Digits...>());
1082}
1083
1085template<char... Digits>
1086constexpr thermo::kelvin operator""_k() {
1087 return thermo::kelvin(detail::check_overflow<thermo::delta_kelvin, Digits...>());
1088}
1089
1091template<char... Digits>
1092constexpr thermo::decikelvin operator""_dk() {
1093 return thermo::decikelvin(detail::check_overflow<thermo::delta_decikelvin, Digits...>());
1094}
1095
1097template<char... Digits>
1098constexpr thermo::millikelvin operator""_mk() {
1099 return thermo::millikelvin(detail::check_overflow<thermo::delta_millikelvin, Digits...>());
1100}
1101
1103template<char... Digits>
1104constexpr thermo::fahrenheit operator""_f() {
1105 return thermo::fahrenheit(detail::check_overflow<thermo::delta_fahrenheit, Digits...>());
1106}
1107
1109template<char... Digits>
1110constexpr thermo::decifahrenheit operator""_df() {
1111 return thermo::decifahrenheit(detail::check_overflow<thermo::delta_decifahrenheit, Digits...>());
1112}
1113
1115template<char... Digits>
1116constexpr thermo::millifahrenheit operator""_mf() {
1117 return thermo::millifahrenheit(detail::check_overflow<thermo::delta_millifahrenheit, Digits...>());
1118}
1119
1121template<char... Digits>
1122constexpr thermo::delta_celsius operator""_Δc() {
1123 return detail::check_overflow<thermo::delta_celsius, Digits...>();
1124}
1125
1127template<char... Digits>
1128constexpr thermo::delta_millicelsius operator""_Δmc() {
1129 return detail::check_overflow<thermo::delta_millicelsius, Digits...>();
1130}
1131
1133template<char... Digits>
1134constexpr thermo::delta_kelvin operator""_Δk() {
1135 return detail::check_overflow<thermo::delta_kelvin, Digits...>();
1136}
1137
1139template<char... Digits>
1140constexpr thermo::delta_millikelvin operator""_Δmk() {
1141 return detail::check_overflow<thermo::delta_millikelvin, Digits...>();
1142}
1143
1145template<char... Digits>
1146constexpr thermo::delta_fahrenheit operator""_Δf() {
1147 return detail::check_overflow<thermo::delta_fahrenheit, Digits...>();
1148}
1149
1151template<char... Digits>
1152constexpr thermo::delta_millifahrenheit operator""_Δmf() {
1153 return detail::check_overflow<thermo::delta_millifahrenheit, Digits...>();
1154}
1155
1156} // namespace thermo_literals
A temperature difference with a representation and precision.
Definition thermo.hpp:121
constexpr delta & operator*=(const rep &r)
Definition thermo.hpp:204
static constexpr delta zero() noexcept
Returns a zero-length delta.
Definition thermo.hpp:229
constexpr delta & operator++()
Definition thermo.hpp:180
static constexpr delta min() noexcept
Returns the minimum (most negative) representable delta.
Definition thermo.hpp:232
constexpr delta()=default
Constructs a zero delta.
constexpr delta & operator/=(const rep &r)
Definition thermo.hpp:209
static constexpr delta max() noexcept
Returns the maximum representable delta.
Definition thermo.hpp:235
Rep rep
The representation type.
Definition thermo.hpp:128
constexpr delta & operator--()
Definition thermo.hpp:187
constexpr rep count() const
Returns the tick count.
Definition thermo.hpp:170
constexpr delta operator--(int)
Definition thermo.hpp:192
delta & operator=(const delta &)=default
constexpr delta & operator-=(const delta &d)
Definition thermo.hpp:199
constexpr delta & operator%=(const delta &d)
Definition thermo.hpp:221
typename Precision::type precision
The precision as a std::ratio.
Definition thermo.hpp:130
delta(const delta &)=default
constexpr delta< typename std::common_type< rep >::type, precision > operator+() const
Definition thermo.hpp:172
constexpr delta operator++(int)
Definition thermo.hpp:185
constexpr delta(const Rep2 &r)
Constructs from a tick count.
Definition thermo.hpp:147
constexpr delta & operator+=(const delta &d)
Definition thermo.hpp:194
constexpr delta< typename std::common_type< rep >::type, precision > operator-() const
Definition thermo.hpp:176
~delta()=default
constexpr delta & operator%=(const rep &r)
Definition thermo.hpp:214
An absolute temperature on a given scale.
Definition thermo.hpp:660
typename Delta::rep rep
The representation type.
Definition thermo.hpp:669
Delta delta_type
The delta type.
Definition thermo.hpp:667
constexpr temperature()=default
Constructs a temperature at the scale's zero point.
constexpr temperature operator-() const
Negates the temperature (e.g.
Definition thermo.hpp:750
constexpr temperature & operator++()
Definition thermo.hpp:729
constexpr temperature(const temperature< Scale, Delta2 > &t)
Definition thermo.hpp:698
constexpr temperature(const temperature< Scale2, Delta2 > &t)
Definition thermo.hpp:713
constexpr temperature(const Rep2 &r)
Constructs from a tick count.
Definition thermo.hpp:682
static constexpr temperature max() noexcept
Returns the maximum representable temperature.
Definition thermo.hpp:772
constexpr temperature operator--(int)
Definition thermo.hpp:741
constexpr temperature(const temperature< Scale, Delta2 > &t)
Definition thermo.hpp:705
temperature(const temperature &)=default
constexpr temperature(const Delta &d)
Constructs from a delta.
Definition thermo.hpp:689
static constexpr temperature min() noexcept
Returns the minimum representable temperature.
Definition thermo.hpp:769
~temperature()=default
constexpr temperature operator++(int)
Definition thermo.hpp:734
constexpr temperature & operator+=(const delta< Rep2, Precision2 > &d)
Definition thermo.hpp:757
Scale scale
The temperature scale.
Definition thermo.hpp:665
constexpr temperature & operator--()
Definition thermo.hpp:736
constexpr rep count() const
Returns the tick count.
Definition thermo.hpp:727
constexpr temperature & operator-=(const delta< Rep2, Precision2 > &d)
Definition thermo.hpp:763
temperature & operator=(const temperature &)=default
constexpr temperature(const temperature< Scale2, Delta2 > &t)
Definition thermo.hpp:720
User-defined literals for temperature types.
Definition thermo.hpp:1023
Temperature types and utilities.
Definition thermo.hpp:38
temperature< celsius_scale > celsius
Celsius with 1 degree precision.
Definition thermo.hpp:885
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:390
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:544
temperature< fahrenheit_scale, delta< int64_t, std::ratio< 5, 9000 > > > millifahrenheit
Fahrenheit with 0.001 degree precision.
Definition thermo.hpp:901
delta< int64_t, std::milli > delta_millicelsius
Delta with 0.001 degree precision (Celsius/Kelvin).
Definition thermo.hpp:467
constexpr ToDelta round(const delta< Rep, Precision > &d)
Rounds a delta to the nearest representable value in the target precision.
Definition thermo.hpp:348
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:382
constexpr ToDelta delta_cast(const delta< Rep, Precision > &d)
Converts a delta to a different precision or representation.
Definition thermo.hpp:252
constexpr bool operator==(const delta< Rep1, Precision1 > &lhs, const delta< Rep2, Precision2 > &rhs)
Definition thermo.hpp:450
delta< int64_t, std::milli > delta_millikelvin
Delta with 0.001 degree precision (Celsius/Kelvin).
Definition thermo.hpp:473
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:399
delta< int64_t, std::ratio< 5, 9000 > > delta_millifahrenheit
Delta with 0.001°F precision.
Definition thermo.hpp:479
temperature< fahrenheit_scale, delta< int64_t, std::ratio< 5, 9 > > > fahrenheit
Fahrenheit with 1 degree precision.
Definition thermo.hpp:897
constexpr ToDelta ceil(const delta< Rep, Precision > &d)
Rounds a delta up to the nearest representable value in the target precision.
Definition thermo.hpp:288
delta< int64_t, std::ratio< 5, 9 > > delta_fahrenheit
Delta with 1°F precision.
Definition thermo.hpp:475
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:844
constexpr ToDelta floor(const delta< Rep, Precision > &d)
Rounds a delta down to the nearest representable value in the target precision.
Definition thermo.hpp:309
delta< int64_t > delta_kelvin
Delta with 1 degree precision (Celsius/Kelvin).
Definition thermo.hpp:469
temperature< celsius_scale, delta< int64_t, std::deci > > decicelsius
Celsius with 0.1 degree precision.
Definition thermo.hpp:887
temperature< kelvin_scale, delta< int64_t, std::milli > > millikelvin
Kelvin with 0.001 degree precision.
Definition thermo.hpp:895
constexpr ToTemp temperature_cast(const temperature< Scale, Delta > &t)
Converts a temperature to a different scale or precision.
Definition thermo.hpp:779
constexpr auto operator<=>(const delta< Rep1, Precision1 > &lhs, const delta< Rep2, Precision2 > &rhs)
Definition thermo.hpp:457
temperature< fahrenheit_scale, delta< int64_t, std::ratio< 5, 90 > > > decifahrenheit
Fahrenheit with 0.1 degree precision.
Definition thermo.hpp:899
temperature< kelvin_scale, delta< int64_t, std::deci > > decikelvin
Kelvin with 0.1 degree precision.
Definition thermo.hpp:893
temperature< kelvin_scale > kelvin
Kelvin with 1 degree precision.
Definition thermo.hpp:891
temperature< celsius_scale, delta< int64_t, std::milli > > millicelsius
Celsius with 0.001 degree precision.
Definition thermo.hpp:889
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:416
delta< int64_t > delta_celsius
Delta with 1 degree precision (Celsius/Kelvin).
Definition thermo.hpp:463
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:330
Trait to detect delta specializations.
Definition thermo.hpp:48