thermo 1.0.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#ifndef CONFIG_THERMO_STD_FORMAT
15#if __has_include(<format>) && defined(__cpp_lib_format)
16#define CONFIG_THERMO_STD_FORMAT 1
17#else
18#define CONFIG_THERMO_STD_FORMAT 0
19#endif
20#endif
21
22#if CONFIG_THERMO_STD_FORMAT
23#include <format>
24#endif
25#include <assert.h>
26
27static_assert(__cplusplus >= 202302L, "thermo requires C++23 or later");
28
37namespace thermo {
38
39template<typename Rep, typename Precision = std::ratio<1>>
40class delta;
41
46template<typename _T>
47struct is_delta : std::false_type {};
48
49template<typename Rep, typename Precision>
50struct is_delta<delta<Rep, Precision>> : std::true_type {};
51
53template<typename T, template<typename...> class Template>
54struct _is_specialization_of : std::false_type {};
55
56template<template<typename...> class Template, typename... Args>
57struct _is_specialization_of<Template<Args...>, Template> : std::true_type {};
58
59template<typename T, template<typename...> class Template>
60inline constexpr bool _is_specialization_of_v = _is_specialization_of<T, Template>::value;
61
62template<typename Rep>
63concept not_delta = !_is_specialization_of_v<Rep, delta>;
64
65template<typename Rep>
66struct delta_values {
68 static constexpr Rep zero() noexcept { return Rep(0); }
69
71 static constexpr Rep max() noexcept { return std::numeric_limits<Rep>::max(); }
72
74 static constexpr Rep min() noexcept { return std::numeric_limits<Rep>::lowest(); }
75};
76
77template<typename T>
78struct _is_ratio : std::false_type {};
79
80template<std::intmax_t Num, std::intmax_t Denom>
81struct _is_ratio<std::ratio<Num, Denom>> : std::true_type {};
82
83template<typename T>
84struct treat_as_inexact : std::bool_constant<std::floating_point<T>> {};
85
86template<typename T>
87inline constexpr bool treat_as_inexact_v = treat_as_inexact<T>::value;
88
89consteval intmax_t _gcd(intmax_t m, intmax_t n) noexcept {
90 while (n != 0) {
91 intmax_t rem = m % n;
92 m = n;
93 n = rem;
94 }
95 return m;
96}
97
98template<typename R1, typename R2>
99inline constexpr intmax_t _safe_ratio_divide_den = [] {
100 constexpr intmax_t g1 = _gcd(R1::num, R2::num);
101 constexpr intmax_t g2 = _gcd(R1::den, R2::den);
102 return (R1::den / g2) * (R2::num / g1);
103}();
104
105template<typename From, typename To>
106concept _harmonic_precision = _safe_ratio_divide_den<From, To> == 1;
119template<typename Rep, typename Precision>
120class delta {
121 static_assert(!is_delta<Rep>::value, "rep cannot be a thermo::delta");
122 static_assert(_is_ratio<Precision>::value, "precision must be a specialization of std::ratio");
123 static_assert(Precision::num > 0, "precision must be positive");
124
125public:
127 using rep = Rep;
129 using precision = typename Precision::type;
130
132 constexpr delta() = default;
133 delta(const delta&) = default;
134
144 template<typename Rep2>
145 requires std::convertible_to<const Rep2&, rep> && (treat_as_inexact_v<rep> || !treat_as_inexact_v<Rep2>)
146 constexpr explicit delta(const Rep2& r)
147 : _r(static_cast<rep>(r)) {}
148
159 template<typename Rep2, typename Precision2>
160 requires std::convertible_to<const Rep2&, rep> &&
161 (treat_as_inexact_v<rep> || (_harmonic_precision<Precision2, precision> && !treat_as_inexact_v<Rep2>))
162 constexpr delta(const delta<Rep2, Precision2>& temp)
163 : _r(delta_cast<delta>(temp).count()) {}
164
165 ~delta() = default;
166 delta& operator=(const delta&) = default;
167
169 constexpr rep count() const { return _r; }
170
174
178
179 constexpr delta& operator++() {
180 ++_r;
181 return *this;
182 }
183
184 constexpr delta operator++(int) { return delta(_r++); }
185
186 constexpr delta& operator--() {
187 --_r;
188 return *this;
189 }
190
191 constexpr delta operator--(int) { return delta(_r--); }
192
193 constexpr delta& operator+=(const delta& d) {
194 _r += d.count();
195 return *this;
196 }
197
198 constexpr delta& operator-=(const delta& d) {
199 _r -= d.count();
200 return *this;
201 }
202
203 constexpr delta& operator*=(const rep& r) {
204 _r *= r;
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 requires(!treat_as_inexact_v<rep>)
215 {
216 _r %= r;
217 return *this;
218 }
219
220 constexpr delta& operator%=(const delta& d)
221 requires(!treat_as_inexact_v<rep>)
222 {
223 _r %= d.count();
224 return *this;
225 }
226
228 static constexpr delta zero() noexcept { return delta(delta_values<rep>::zero()); }
229
231 static constexpr delta min() noexcept { return delta(delta_values<rep>::min()); }
232
234 static constexpr delta max() noexcept { return delta(delta_values<rep>::max()); }
235
236private:
237 rep _r{};
238};
239
250template<typename ToDelta, typename Rep, typename Precision>
251constexpr ToDelta delta_cast(const delta<Rep, Precision>& d) {
252 if constexpr (std::is_same_v<ToDelta, delta<Rep, Precision>>) {
253 return d;
254 } else {
255 using to_rep = typename ToDelta::rep;
256 using to_precision = typename ToDelta::precision;
257 using cf = std::ratio_divide<Precision, to_precision>;
258 using cr = std::common_type_t<to_rep, Rep, intmax_t>;
259
260 if constexpr (cf::den == 1 && cf::num == 1) {
261 return ToDelta(static_cast<to_rep>(d.count()));
262 } else if constexpr (cf::den == 1) {
263 return ToDelta(static_cast<to_rep>(static_cast<cr>(d.count()) * static_cast<cr>(cf::num)));
264 } else if constexpr (cf::num == 1) {
265 return ToDelta(static_cast<to_rep>(static_cast<cr>(d.count()) / static_cast<cr>(cf::den)));
266 } else {
267 return ToDelta(
268 static_cast<to_rep>(static_cast<cr>(d.count()) * static_cast<cr>(cf::num) / static_cast<cr>(cf::den))
269 );
270 }
271 }
272}
273
286template<typename ToDelta, typename Rep, typename Precision>
287constexpr ToDelta ceil(const delta<Rep, Precision>& d) {
288 ToDelta result = delta_cast<ToDelta>(d);
289 if (result < d) {
290 return ToDelta(result.count() + 1);
291 }
292 return result;
293}
294
307template<typename ToDelta, typename Rep, typename Precision>
310 if (result > d) {
311 return ToDelta(result.count() - 1);
312 }
313 return result;
314}
315
328template<typename ToDelta, typename Rep, typename Precision>
330 return delta_cast<ToDelta>(d);
331}
332
346template<typename ToDelta, typename Rep, typename Precision>
349 if (result == d) {
350 return result;
351 }
352
353 // Calculate the midpoint between result and the next value
354 ToDelta next = (result < d) ? ToDelta(result.count() + 1) : ToDelta(result.count() - 1);
355
356 // Convert both to source type for comparison
359
360 // Calculate distances
363
364 // If closer to next, or exactly halfway and away from zero, use next
366 return next;
367 } else if (dist_to_next == dist_to_result) {
368 // Tie: round away from zero
369 if (d.count() >= 0) {
370 return (next > result) ? next : result;
371 } else {
372 return (next < result) ? next : result;
373 }
374 }
375
376 return result;
377}
378
380template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
382 -> std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>> {
383 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
384 return cd(cd(lhs).count() + cd(rhs).count());
385}
386
388template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
390 -> std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>> {
391 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
392 return cd(cd(lhs).count() - cd(rhs).count());
393}
394
396template<typename Rep1, typename Precision, typename Rep2>
397 requires not_delta<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>>
398constexpr auto operator*(const delta<Rep1, Precision>& d, const Rep2& r)
401 return cd(cd(d).count() * r);
402}
403
405template<typename Rep1, typename Rep2, typename Precision>
406 requires not_delta<Rep1> && std::convertible_to<const Rep1&, std::common_type_t<Rep1, Rep2>>
407constexpr auto operator*(const Rep1& r, const delta<Rep2, Precision>& d)
409 return d * r;
410}
411
413template<typename Rep1, typename Precision, typename Rep2>
414 requires not_delta<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>>
415constexpr auto operator/(const delta<Rep1, Precision>& d, const Rep2& s)
418 return cd(cd(d).count() / s);
419}
420
422template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
424 -> std::common_type_t<Rep1, Rep2> {
425 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
426 return cd(lhs).count() / cd(rhs).count();
427}
428
430template<typename Rep1, typename Precision, typename Rep2>
431 requires not_delta<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>> &&
433constexpr auto operator%(const delta<Rep1, Precision>& d, const Rep2& s)
436 return cd(cd(d).count() % s);
437}
438
440template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
442constexpr auto operator%(const delta<Rep1, Precision1>& lhs, const delta<Rep2, Precision2>& rhs)
443 -> std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>> {
444 using cd = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
445 return cd(cd(lhs).count() % cd(rhs).count());
446}
447
448template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
450 using ct = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
451 return ct(lhs).count() == ct(rhs).count();
452}
453
454template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
455 requires std::three_way_comparable<std::common_type_t<Rep1, Rep2>>
457 using ct = std::common_type_t<delta<Rep1, Precision1>, delta<Rep2, Precision2>>;
458 return ct(lhs).count() <=> ct(rhs).count();
459}
460
479
480inline std::string to_string(delta<int64_t> d) {
481 return std::to_string(d.count()) + "Δ°C";
482}
483
485 return std::to_string(d.count()) + "Δd°C";
486}
487
489 return std::to_string(d.count()) + "Δm°C";
490}
491
492inline std::string to_string(delta_fahrenheit d) {
493 return std::to_string(d.count()) + "Δ°F";
494}
495
496inline std::string to_string(delta_decifahrenheit d) {
497 return std::to_string(d.count()) + "Δd°F";
498}
499
500inline std::string to_string(delta_millifahrenheit d) {
501 return std::to_string(d.count()) + "Δm°F";
502}
503
504// ============================================================================
505// Temperature Scales and Absolute Temperatures
506// ============================================================================
507
514struct celsius_scale {
515 using offset = std::ratio<27315, 100>;
516 static constexpr const char* suffix = "°C";
517};
518
524struct kelvin_scale {
525 using offset = std::ratio<0>;
526 static constexpr const char* suffix = "K";
527};
528
534struct fahrenheit_scale {
535 using offset = std::ratio<45967, 180>;
536 static constexpr const char* suffix = "°F";
537};
540template<typename Scale, typename Delta = delta<int64_t>>
541class temperature;
542
544template<typename Scale1, typename Delta1, typename Scale2, typename Delta2>
546 using from_prec = typename Delta1::precision;
547 using to_prec = typename Delta2::precision;
548 using from_off = typename Scale1::offset;
549 using to_off = typename Scale2::offset;
550
551 using prec_ratio = std::ratio_divide<from_prec, to_prec>;
552 using offset_diff = std::ratio_subtract<from_off, to_off>;
553 using offset_in_target_units = std::ratio_divide<offset_diff, to_prec>;
554
555 static constexpr bool value = prec_ratio::den == 1 && offset_in_target_units::den == 1;
556};
557
558template<typename Scale1, typename Delta1, typename Scale2, typename Delta2>
559concept _lossless_temperature_conversion = _lossless_temperature_conversion_impl<Scale1, Delta1, Scale2, Delta2>::value;
560
565template<typename T>
566struct is_temperature : std::false_type {};
567
568template<typename Scale, typename Delta>
569struct is_temperature<temperature<Scale, Delta>> : std::true_type {};
570
571template<typename T>
572inline constexpr bool is_temperature_v = is_temperature<T>::value;
585template<typename ToTemp, typename Scale, typename Delta>
587
598template<typename Scale, typename Delta>
600 static_assert(is_delta<Delta>::value, "Delta must be a thermo::delta type");
601
602public:
604 using scale = Scale;
608 using rep = typename Delta::rep;
609
611 constexpr temperature() = default;
612 temperature(const temperature&) = default;
613
619 template<typename Rep2>
620 requires std::convertible_to<const Rep2&, rep> && (treat_as_inexact_v<rep> || !treat_as_inexact_v<Rep2>)
621 constexpr explicit temperature(const Rep2& r)
622 : _d(static_cast<rep>(r)) {}
623
628 constexpr explicit temperature(const Delta& d)
629 : _d(d) {}
630
631 // Same-scale, different precision - implicit when lossless
632 template<typename Delta2>
633 requires(!std::is_same_v<Delta, Delta2>) &&
638 : _d(delta_cast<Delta>(Delta2(t.count()))) {}
639
640 // Same-scale, different precision - explicit when lossy
641 template<typename Delta2>
642 requires(!std::is_same_v<Delta, Delta2>) && (!treat_as_inexact_v<rep>) &&
644 constexpr explicit temperature(const temperature<Scale, Delta2>& t)
645 : _d(delta_cast<Delta>(Delta2(t.count()))) {}
646
647 // Cross-scale/precision conversion - implicit when lossless
648 template<typename Scale2, typename Delta2>
649 requires(!std::is_same_v<temperature, temperature<Scale2, Delta2>>) &&
654
655 // Cross-scale/precision conversion - explicit when lossy
656 template<typename Scale2, typename Delta2>
657 requires(!std::is_same_v<temperature, temperature<Scale2, Delta2>>) &&
661
662 ~temperature() = default;
664
666 constexpr rep count() const { return _d.count(); }
667
668 constexpr temperature& operator++() {
669 ++_d;
670 return *this;
671 }
672
673 constexpr temperature operator++(int) { return temperature(_d++); }
674
675 constexpr temperature& operator--() {
676 --_d;
677 return *this;
678 }
679
680 constexpr temperature operator--(int) { return temperature(_d--); }
681
682 template<typename Rep2, typename Precision2>
684 _d += d;
685 return *this;
686 }
687
688 template<typename Rep2, typename Precision2>
690 _d -= d;
691 return *this;
692 }
693
695 static constexpr temperature min() noexcept { return temperature(Delta::min()); }
696
698 static constexpr temperature max() noexcept { return temperature(Delta::max()); }
699
700private:
701 Delta _d{};
702};
703
704template<typename ToTemp, typename Scale, typename Delta>
706 using ToScale = typename ToTemp::scale;
707 using ToDelta = typename ToTemp::delta_type;
708 using to_rep = typename ToDelta::rep;
709
710 if constexpr (std::is_same_v<Scale, ToScale> && std::is_same_v<Delta, ToDelta>) {
711 return t;
712 } else if constexpr (std::is_same_v<Scale, ToScale>) {
713 return ToTemp(delta_cast<ToDelta>(Delta(t.count())));
714 } else {
715 using from_prec = typename Delta::precision;
716 using from_off = typename Scale::offset;
717 using to_prec = typename ToDelta::precision;
718 using to_off = typename ToScale::offset;
719
720 using offset_diff = std::ratio_subtract<from_off, to_off>;
721 using common_rep = std::common_type_t<typename Delta::rep, to_rep, intmax_t>;
722
723 common_rep from_val = static_cast<common_rep>(t.count());
724
725 using prec_ratio = std::ratio_divide<from_prec, to_prec>;
726 using offset_ratio = std::ratio_divide<offset_diff, to_prec>;
727
728 if constexpr (treat_as_inexact_v<common_rep>) {
729 constexpr double pr = static_cast<double>(prec_ratio::num) / prec_ratio::den;
730 constexpr double or_ = static_cast<double>(offset_ratio::num) / offset_ratio::den;
732 return ToTemp(ToDelta(static_cast<to_rep>(result)));
733 } else {
734 constexpr intmax_t combined_den =
735 static_cast<intmax_t>(prec_ratio::den) * static_cast<intmax_t>(offset_ratio::den);
737 from_val * static_cast<common_rep>(prec_ratio::num) * static_cast<common_rep>(offset_ratio::den) +
738 static_cast<common_rep>(offset_ratio::num) * static_cast<common_rep>(prec_ratio::den);
740 return ToTemp(ToDelta(static_cast<to_rep>(result)));
741 }
742 }
743}
744
746template<typename Scale, typename Delta1, typename Delta2>
749 using cd = std::common_type_t<Delta1, Delta2>;
750 return temperature<Scale, cd>(cd(lhs.count()) + cd(rhs.count()));
751}
752
754template<typename Scale, typename Delta1, typename Delta2>
757 using cd = std::common_type_t<Delta1, Delta2>;
758 return temperature<Scale, cd>(cd(lhs.count()) - cd(rhs.count()));
759}
760
769template<typename Scale, typename Delta1, typename Delta2>
771 -> std::common_type_t<Delta1, Delta2> {
772 using cd = std::common_type_t<Delta1, Delta2>;
773 return cd(lhs.count() - rhs.count());
774}
775
777template<typename Scale, typename Delta1, typename Rep2, typename Precision2>
780 using result_delta = std::common_type_t<Delta1, delta<Rep2, Precision2>>;
782}
783
785template<typename Rep1, typename Precision1, typename Scale, typename Delta2>
790
792template<typename Scale, typename Delta1, typename Rep2, typename Precision2>
795 using result_delta = std::common_type_t<Delta1, delta<Rep2, Precision2>>;
797}
798
799template<typename Scale, typename Delta1, typename Delta2>
801 return lhs.count() == rhs.count();
802}
803
804template<typename Scale, typename Delta1, typename Delta2>
805 requires std::three_way_comparable<std::common_type_t<typename Delta1::rep, typename Delta2::rep>>
807 return lhs.count() <=> rhs.count();
808}
809
828
829inline std::string to_string(celsius t) {
830 return std::to_string(t.count()) + "°C";
831}
832
833inline std::string to_string(decicelsius t) {
834 return std::to_string(t.count()) + "d°C";
835}
836
837inline std::string to_string(millicelsius t) {
838 return std::to_string(t.count()) + "m°C";
839}
840
841inline std::string to_string(kelvin t) {
842 return std::to_string(t.count()) + "K";
843}
844
845inline std::string to_string(decikelvin t) {
846 return std::to_string(t.count()) + "dK";
847}
848
849inline std::string to_string(millikelvin t) {
850 return std::to_string(t.count()) + "mK";
851}
852
853inline std::string to_string(fahrenheit t) {
854 return std::to_string(t.count()) + "°F";
855}
856
857inline std::string to_string(decifahrenheit t) {
858 return std::to_string(t.count()) + "d°F";
859}
860
861inline std::string to_string(millifahrenheit t) {
862 return std::to_string(t.count()) + "m°F";
863}
864
865} // namespace thermo
866
867namespace std {
868
869template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
870struct common_type<thermo::delta<Rep1, Precision1>, thermo::delta<Rep2, Precision2>> {
871private:
872 using common_precision = std::ratio<
873 thermo::_gcd(Precision1::num, Precision2::num),
874 (Precision1::den / thermo::_gcd(Precision1::den, Precision2::den)) * Precision2::den>;
875
876public:
877 using type = thermo::delta<std::common_type_t<Rep1, Rep2>, common_precision>;
878};
879
880template<typename Scale, typename Delta1, typename Delta2>
881struct common_type<thermo::temperature<Scale, Delta1>, thermo::temperature<Scale, Delta2>> {
883};
884
885#if CONFIG_THERMO_STD_FORMAT
886template<>
887struct formatter<thermo::celsius> {
888 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
889
890 auto format(thermo::celsius t, format_context& ctx) const { return std::format_to(ctx.out(), "{}°C", t.count()); }
891};
892
893template<>
894struct formatter<thermo::decicelsius> {
895 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
896
897 auto format(thermo::decicelsius t, format_context& ctx) const {
898 return std::format_to(ctx.out(), "{}d°C", t.count());
899 }
900};
901
902template<>
903struct formatter<thermo::millicelsius> {
904 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
905
906 auto format(thermo::millicelsius t, format_context& ctx) const {
907 return std::format_to(ctx.out(), "{}m°C", t.count());
908 }
909};
910
911template<>
912struct formatter<thermo::kelvin> {
913 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
914
915 auto format(thermo::kelvin t, format_context& ctx) const { return std::format_to(ctx.out(), "{}K", t.count()); }
916};
917
918template<>
919struct formatter<thermo::decikelvin> {
920 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
921
922 auto format(thermo::decikelvin t, format_context& ctx) const {
923 return std::format_to(ctx.out(), "{}dK", t.count());
924 }
925};
926
927template<>
928struct formatter<thermo::millikelvin> {
929 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
930
931 auto format(thermo::millikelvin t, format_context& ctx) const {
932 return std::format_to(ctx.out(), "{}mK", t.count());
933 }
934};
935
936template<>
937struct formatter<thermo::fahrenheit> {
938 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
939
940 auto format(thermo::fahrenheit t, format_context& ctx) const {
941 return std::format_to(ctx.out(), "{}°F", t.count());
942 }
943};
944
945template<>
946struct formatter<thermo::decifahrenheit> {
947 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
948
949 auto format(thermo::decifahrenheit t, format_context& ctx) const {
950 return std::format_to(ctx.out(), "{}d°F", t.count());
951 }
952};
953
954template<>
955struct formatter<thermo::millifahrenheit> {
956 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
957
958 auto format(thermo::millifahrenheit t, format_context& ctx) const {
959 return std::format_to(ctx.out(), "{}m°F", t.count());
960 }
961};
962
963template<>
964struct formatter<thermo::delta<int64_t>> {
965 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
966
967 auto format(thermo::delta<int64_t> d, format_context& ctx) const {
968 return std::format_to(ctx.out(), "{}Δ°C", d.count());
969 }
970};
971
972template<>
973struct formatter<thermo::delta<int64_t, std::deci>> {
974 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
975
976 auto format(thermo::delta<int64_t, std::deci> d, format_context& ctx) const {
977 return std::format_to(ctx.out(), "{}Δd°C", d.count());
978 }
979};
980
981template<>
982struct formatter<thermo::delta<int64_t, std::milli>> {
983 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
984
985 auto format(thermo::delta<int64_t, std::milli> d, format_context& ctx) const {
986 return std::format_to(ctx.out(), "{}Δm°C", d.count());
987 }
988};
989
990template<>
991struct formatter<thermo::delta_fahrenheit> {
992 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
993
994 auto format(thermo::delta_fahrenheit d, format_context& ctx) const {
995 return std::format_to(ctx.out(), "{}Δ°F", d.count());
996 }
997};
998
999template<>
1000struct formatter<thermo::delta_decifahrenheit> {
1001 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
1002
1003 auto format(thermo::delta_decifahrenheit d, format_context& ctx) const {
1004 return std::format_to(ctx.out(), "{}Δd°F", d.count());
1005 }
1006};
1007
1008template<>
1009struct formatter<thermo::delta_millifahrenheit> {
1010 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
1011
1012 auto format(thermo::delta_millifahrenheit d, format_context& ctx) const {
1013 return std::format_to(ctx.out(), "{}Δm°F", d.count());
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:120
constexpr delta & operator*=(const rep &r)
Definition thermo.hpp:203
static constexpr delta zero() noexcept
Returns a zero-length delta.
Definition thermo.hpp:228
constexpr delta & operator++()
Definition thermo.hpp:179
static constexpr delta min() noexcept
Returns the minimum (most negative) representable delta.
Definition thermo.hpp:231
constexpr delta()=default
Constructs a zero delta.
constexpr delta & operator/=(const rep &r)
Definition thermo.hpp:208
static constexpr delta max() noexcept
Returns the maximum representable delta.
Definition thermo.hpp:234
Rep rep
The representation type.
Definition thermo.hpp:127
constexpr delta & operator--()
Definition thermo.hpp:186
constexpr rep count() const
Returns the tick count.
Definition thermo.hpp:169
constexpr delta operator--(int)
Definition thermo.hpp:191
delta & operator=(const delta &)=default
constexpr delta & operator-=(const delta &d)
Definition thermo.hpp:198
constexpr delta & operator%=(const delta &d)
Definition thermo.hpp:220
typename Precision::type precision
The precision as a std::ratio.
Definition thermo.hpp:129
delta(const delta &)=default
constexpr delta< typename std::common_type< rep >::type, precision > operator+() const
Definition thermo.hpp:171
constexpr delta operator++(int)
Definition thermo.hpp:184
constexpr delta(const Rep2 &r)
Constructs from a tick count.
Definition thermo.hpp:146
constexpr delta & operator+=(const delta &d)
Definition thermo.hpp:193
constexpr delta< typename std::common_type< rep >::type, precision > operator-() const
Definition thermo.hpp:175
~delta()=default
constexpr delta & operator%=(const rep &r)
Definition thermo.hpp:213
An absolute temperature on a given scale.
Definition thermo.hpp:599
typename Delta::rep rep
The representation type.
Definition thermo.hpp:608
Delta delta_type
The delta type.
Definition thermo.hpp:606
constexpr temperature()=default
Constructs a temperature at the scale's zero point.
constexpr temperature & operator++()
Definition thermo.hpp:668
constexpr temperature(const temperature< Scale, Delta2 > &t)
Definition thermo.hpp:637
constexpr temperature(const temperature< Scale2, Delta2 > &t)
Definition thermo.hpp:652
constexpr temperature(const Rep2 &r)
Constructs from a tick count.
Definition thermo.hpp:621
static constexpr temperature max() noexcept
Returns the maximum representable temperature.
Definition thermo.hpp:698
constexpr temperature operator--(int)
Definition thermo.hpp:680
constexpr temperature(const temperature< Scale, Delta2 > &t)
Definition thermo.hpp:644
temperature(const temperature &)=default
constexpr temperature(const Delta &d)
Constructs from a delta.
Definition thermo.hpp:628
static constexpr temperature min() noexcept
Returns the minimum representable temperature.
Definition thermo.hpp:695
~temperature()=default
constexpr temperature operator++(int)
Definition thermo.hpp:673
constexpr temperature & operator+=(const delta< Rep2, Precision2 > &d)
Definition thermo.hpp:683
Scale scale
The temperature scale.
Definition thermo.hpp:604
constexpr temperature & operator--()
Definition thermo.hpp:675
constexpr rep count() const
Returns the tick count.
Definition thermo.hpp:666
constexpr temperature & operator-=(const delta< Rep2, Precision2 > &d)
Definition thermo.hpp:689
temperature & operator=(const temperature &)=default
constexpr temperature(const temperature< Scale2, Delta2 > &t)
Definition thermo.hpp:659
User-defined literals for temperature types.
Definition thermo.hpp:1023
Temperature types and utilities.
Definition thermo.hpp:37
temperature< celsius_scale > celsius
Celsius with 1 degree precision.
Definition thermo.hpp:811
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:389
temperature< fahrenheit_scale, delta< int64_t, std::ratio< 5, 900 > > > millifahrenheit
Fahrenheit with 0.001 degree precision.
Definition thermo.hpp:827
delta< int64_t, std::milli > delta_millicelsius
Delta with 0.001 degree precision (Celsius/Kelvin).
Definition thermo.hpp:466
constexpr ToDelta round(const delta< Rep, Precision > &d)
Rounds a delta to the nearest representable value in the target precision.
Definition thermo.hpp:347
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:381
constexpr ToDelta delta_cast(const delta< Rep, Precision > &d)
Converts a delta to a different precision or representation.
Definition thermo.hpp:251
constexpr bool operator==(const delta< Rep1, Precision1 > &lhs, const delta< Rep2, Precision2 > &rhs)
Definition thermo.hpp:449
delta< int64_t, std::milli > delta_millikelvin
Delta with 0.001 degree precision (Celsius/Kelvin).
Definition thermo.hpp:472
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:398
temperature< fahrenheit_scale, delta< int64_t, std::ratio< 5, 9 > > > fahrenheit
Fahrenheit with 1 degree precision.
Definition thermo.hpp:823
constexpr ToDelta ceil(const delta< Rep, Precision > &d)
Rounds a delta up to the nearest representable value in the target precision.
Definition thermo.hpp:287
delta< int64_t, std::ratio< 5, 9 > > delta_fahrenheit
Delta with 1°F precision.
Definition thermo.hpp:474
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:770
delta< int64_t, std::ratio< 5, 900 > > delta_millifahrenheit
Delta with 0.001°F precision.
Definition thermo.hpp:478
constexpr ToDelta floor(const delta< Rep, Precision > &d)
Rounds a delta down to the nearest representable value in the target precision.
Definition thermo.hpp:308
delta< int64_t > delta_kelvin
Delta with 1 degree precision (Celsius/Kelvin).
Definition thermo.hpp:468
temperature< celsius_scale, delta< int64_t, std::deci > > decicelsius
Celsius with 0.1 degree precision.
Definition thermo.hpp:813
temperature< kelvin_scale, delta< int64_t, std::milli > > millikelvin
Kelvin with 0.001 degree precision.
Definition thermo.hpp:821
constexpr ToTemp temperature_cast(const temperature< Scale, Delta > &t)
Converts a temperature to a different scale or precision.
Definition thermo.hpp:705
constexpr auto operator<=>(const delta< Rep1, Precision1 > &lhs, const delta< Rep2, Precision2 > &rhs)
Definition thermo.hpp:456
std::string to_string(delta< int64_t > d)
Definition thermo.hpp:480
temperature< fahrenheit_scale, delta< int64_t, std::ratio< 5, 90 > > > decifahrenheit
Fahrenheit with 0.1 degree precision.
Definition thermo.hpp:825
temperature< kelvin_scale, delta< int64_t, std::deci > > decikelvin
Kelvin with 0.1 degree precision.
Definition thermo.hpp:819
temperature< kelvin_scale > kelvin
Kelvin with 1 degree precision.
Definition thermo.hpp:817
temperature< celsius_scale, delta< int64_t, std::milli > > millicelsius
Celsius with 0.001 degree precision.
Definition thermo.hpp:815
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:415
delta< int64_t > delta_celsius
Delta with 1 degree precision (Celsius/Kelvin).
Definition thermo.hpp:462
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:329
Trait to detect delta specializations.
Definition thermo.hpp:47