frequency 1.2.1
Type-safe frequency handling library modeled after std::chrono
Loading...
Searching...
No Matches
frequency.hpp
Go to the documentation of this file.
1#pragma once
2
8#include <cmath>
9#include <compare>
10#include <concepts>
11#include <cstdint>
12#include <cstdlib>
13#include <limits>
14#include <ratio>
15#include <string>
16#ifndef CONFIG_FREQUENCY_STD_FORMAT
17#if __has_include(<format>) && defined(__cpp_lib_format)
18#define CONFIG_FREQUENCY_STD_FORMAT 1
19#else
20#define CONFIG_FREQUENCY_STD_FORMAT 0
21#endif
22#endif
23
24#if CONFIG_FREQUENCY_STD_FORMAT
25#include <format>
26#endif
27#include <assert.h>
28
89namespace freq {
90
91template<typename Rep, typename Precision = std::ratio<1>>
92class frequency;
93
98template<typename T>
99struct is_frequency : std::false_type {};
100
101template<typename Rep, typename Precision>
102struct is_frequency<frequency<Rep, Precision>> : std::true_type {};
103
104template<typename T>
106
108template<typename T, template<typename...> class Template>
109struct _is_specialization_of : std::false_type {};
110
111template<template<typename...> class Template, typename... Args>
112struct _is_specialization_of<Template<Args...>, Template> : std::true_type {};
113
114template<typename T, template<typename...> class Template>
115inline constexpr bool _is_specialization_of_v = _is_specialization_of<T, Template>::value;
116
117template<typename Rep>
118concept not_frequency = !_is_specialization_of_v<Rep, frequency>;
119
128template<typename T>
129concept duration_like = requires(T t) {
130 { t.count() };
131 typename T::period;
132 typename T::rep;
133};
134
144template<typename T>
145concept distance_like = requires(T t) {
146 { t.count() };
147 typename T::period;
148 typename T::rep;
149};
150
155template<typename Rep>
156struct frequency_values {
158 static constexpr Rep zero() noexcept { return Rep(0); }
159
161 static constexpr Rep max() noexcept { return std::numeric_limits<Rep>::max(); }
162
164 static constexpr Rep min() noexcept { return std::numeric_limits<Rep>::lowest(); }
165};
166
167template<typename T>
168struct _is_ratio : std::false_type {};
169
170template<std::intmax_t Num, std::intmax_t Denom>
171struct _is_ratio<std::ratio<Num, Denom>> : std::true_type {};
172
194template<typename T>
195struct treat_as_inexact : std::bool_constant<std::floating_point<T>> {};
196
197template<typename T>
198inline constexpr bool treat_as_inexact_v = treat_as_inexact<T>::value;
199
200consteval intmax_t _gcd(intmax_t m, intmax_t n) noexcept {
201 while (n != 0) {
202 intmax_t rem = m % n;
203 m = n;
204 n = rem;
205 }
206 return m;
207}
208
209// Runtime GCD for integer types (using Euclidean algorithm)
210template<typename T>
211constexpr T _runtime_gcd(T m, T n) noexcept {
212 if (m < 0) {
213 m = -m;
214 }
215 if (n < 0) {
216 n = -n;
217 }
218 while (n != 0) {
219 T rem = m % n;
220 m = n;
221 n = rem;
222 }
223 return m;
224}
225
226template<typename R1, typename R2>
227inline constexpr intmax_t _safe_ratio_divide_den = [] {
228 constexpr intmax_t g1 = _gcd(R1::num, R2::num);
229 constexpr intmax_t g2 = _gcd(R1::den, R2::den);
230 return (R1::den / g2) * (R2::num / g1);
231}();
232
233template<typename From, typename To>
234concept _harmonic_precision = _safe_ratio_divide_den<From, To> == 1;
281template<typename Rep, typename Precision>
283 static_assert(!is_frequency<Rep>::value, "rep cannot be a frequency::frequency");
284 static_assert(_is_ratio<Precision>::value, "precision must be a specialization of std::ratio");
285 static_assert(Precision::num > 0, "precision must be positive");
286
287public:
289 using rep = Rep;
291 using precision = typename Precision::type;
292
294 constexpr frequency() = default;
295 frequency(const frequency&) = default;
296
306 template<typename Rep2>
307 requires std::convertible_to<const Rep2&, rep> && (treat_as_inexact_v<rep> || !treat_as_inexact_v<Rep2>)
308 constexpr explicit frequency(const Rep2& r)
309 : _r(static_cast<rep>(r)) {}
310
321 template<typename Rep2, typename Precision2>
322 requires std::convertible_to<const Rep2&, rep> &&
323 (treat_as_inexact_v<rep> || (_harmonic_precision<Precision2, precision> && !treat_as_inexact_v<Rep2>))
324 constexpr frequency(const frequency<Rep2, Precision2>& f)
325 : _r(frequency_cast<frequency>(f).count()) {}
326
334 template<typename Rep2, typename Precision2>
335 requires(!std::is_same_v<frequency, frequency<Rep2, Precision2>>) && (!treat_as_inexact_v<rep>) &&
336 (!_harmonic_precision<Precision2, precision>)
337 constexpr explicit frequency(const frequency<Rep2, Precision2>& f)
339
340 ~frequency() = default;
341 frequency& operator=(const frequency&) = default;
342
344 constexpr rep count() const { return _r; }
345
349
353
354 constexpr frequency& operator++() {
355 ++_r;
356 return *this;
357 }
358
359 constexpr frequency operator++(int) { return frequency(_r++); }
360
361 constexpr frequency& operator--() {
362 --_r;
363 return *this;
364 }
365
366 constexpr frequency operator--(int) { return frequency(_r--); }
367
368 constexpr frequency& operator+=(const frequency& f) {
369 _r += f.count();
370 return *this;
371 }
372
373 constexpr frequency& operator-=(const frequency& f) {
374 _r -= f.count();
375 return *this;
376 }
377
378 constexpr frequency& operator*=(const rep& r) {
379 _r *= r;
380 return *this;
381 }
382
383 constexpr frequency& operator/=(const rep& r) {
384 _r /= r;
385 return *this;
386 }
387
388 constexpr frequency& operator%=(const rep& r)
389 requires(!treat_as_inexact_v<rep>)
390 {
391 _r %= r;
392 return *this;
393 }
394
395 constexpr frequency& operator%=(const frequency& f)
396 requires(!treat_as_inexact_v<rep>)
397 {
398 _r %= f.count();
399 return *this;
400 }
401
403 static constexpr frequency zero() noexcept { return frequency(frequency_values<rep>::zero()); }
404
406 static constexpr frequency min() noexcept { return frequency(frequency_values<rep>::min()); }
407
409 static constexpr frequency max() noexcept { return frequency(frequency_values<rep>::max()); }
410
442 template<duration_like Duration>
443 constexpr Duration period() const {
444 // Period ratio is the inverse of frequency precision
445 // For frequency<Rep, ratio<N,D>>, the period of 1 tick is ratio<D,N> seconds
446 using period_ratio = std::ratio_divide<std::ratio<1>, precision>;
447 using duration_period = typename Duration::period;
448 using cf = std::ratio_divide<period_ratio, duration_period>;
449 using duration_rep = typename Duration::rep;
450
451 if (_r == rep(0)) {
452 return Duration::max();
453 }
454
455 // Integer-only path when both types are integral
456 if constexpr (std::is_integral_v<rep> && std::is_integral_v<duration_rep>) {
457#ifdef __SIZEOF_INT128__
458 using cr = std::common_type_t<duration_rep, rep, intmax_t>;
459 using wider_t = std::conditional_t<std::is_signed_v<cr>, __int128, unsigned __int128>;
460#else
461 using wider_t = intmax_t;
462#endif
463
464 wider_t count = static_cast<wider_t>(_r);
465
466 if constexpr (cf::den == 1 && cf::num == 1) {
467 // period_ticks = 1 / freq_count (in the target duration units)
468 return Duration(static_cast<duration_rep>(1 / count));
469 } else if constexpr (cf::den == 1) {
470 // period_ticks = num / freq_count
471 // Use GCD: g = gcd(num, count), then (num/g) / (count/g)
472 wider_t num = static_cast<wider_t>(cf::num);
476 return Duration(static_cast<duration_rep>(reduced_num / reduced_count));
477 } else if constexpr (cf::num == 1) {
478 // period_ticks = 1 / (freq_count * den)
479 wider_t denom = count * static_cast<wider_t>(cf::den);
480 return Duration(static_cast<duration_rep>(1 / denom));
481 } else {
482 // period_ticks = num / (freq_count * den)
483 // Use GCD: g = gcd(num, count), then (num/g) / ((count/g) * den)
484 wider_t num = static_cast<wider_t>(cf::num);
488 wider_t denom = reduced_count * static_cast<wider_t>(cf::den);
489 return Duration(static_cast<duration_rep>(reduced_num / denom));
490 }
491 } else {
492 // Floating-point path
493 using cr = std::common_type_t<duration_rep, double>;
494
495 if constexpr (cf::den == 1 && cf::num == 1) {
496 return Duration(static_cast<duration_rep>(1.0 / static_cast<double>(_r)));
497 } else if constexpr (cf::den == 1) {
498 return Duration(static_cast<duration_rep>(static_cast<cr>(cf::num) / static_cast<double>(_r)));
499 } else if constexpr (cf::num == 1) {
500 return Duration(static_cast<duration_rep>(1.0 / (static_cast<double>(_r) * static_cast<cr>(cf::den))));
501 } else {
502 return Duration(
503 static_cast<duration_rep>(
504 static_cast<cr>(cf::num) / (static_cast<double>(_r) * static_cast<cr>(cf::den))
505 )
506 );
507 }
508 }
509 }
510
523 constexpr frequency harmonic(unsigned int n) const { return *this * n; }
524
537 constexpr frequency subharmonic(unsigned int n) const {
538 assert(n > 0 && "subharmonic divisor must be positive");
539 return *this / n;
540 }
541
565 template<typename T = double>
567 double multiplier = std::pow(2.0, static_cast<double>(octaves));
568 if constexpr (std::is_integral_v<rep>) {
569 return frequency(static_cast<rep>(std::round(static_cast<double>(_r) * multiplier)));
570 } else {
571 return frequency(static_cast<rep>(static_cast<double>(_r) * multiplier));
572 }
573 }
574
599 template<typename T = double>
601 double multiplier = std::pow(2.0, static_cast<double>(semitones) / 12.0);
602 if constexpr (std::is_integral_v<rep>) {
603 return frequency(static_cast<rep>(std::round(static_cast<double>(_r) * multiplier)));
604 } else {
605 return frequency(static_cast<rep>(static_cast<double>(_r) * multiplier));
606 }
607 }
608
620 template<typename T = double>
622 return static_cast<T>(std::log2(static_cast<double>(_r) / static_cast<double>(other._r)));
623 }
624
636 template<typename T = double>
638 return static_cast<T>(12.0 * std::log2(static_cast<double>(_r) / static_cast<double>(other._r)));
639 }
640
658 template<distance_like Distance, duration_like Duration>
660 // Get the period of this frequency
661 using period_rep = double;
662 using period_ratio = std::ratio_divide<std::ratio<1>, precision>;
663
664 if (_r == rep(0)) {
665 return Distance::max();
666 }
667
668 // Calculate period in our internal representation (1/frequency)
669 period_rep wave_period = 1.0 / static_cast<double>(_r);
670
671 // Convert to common time representation
672 using duration_period = typename Duration::period;
673 using time_cf = std::ratio_divide<period_ratio, duration_period>;
674 using time_cr = std::common_type_t<period_rep, typename Duration::rep, double>;
675
676 // Calculate period in time_per_unit_distance units
678 if constexpr (time_cf::den == 1 && time_cf::num == 1) {
680 } else if constexpr (time_cf::den == 1) {
681 period_in_duration_units = static_cast<time_cr>(wave_period) * static_cast<time_cr>(time_cf::num);
682 } else if constexpr (time_cf::num == 1) {
683 period_in_duration_units = static_cast<time_cr>(wave_period) / static_cast<time_cr>(time_cf::den);
684 } else {
685 period_in_duration_units = static_cast<time_cr>(wave_period) * static_cast<time_cr>(time_cf::num) /
686 static_cast<time_cr>(time_cf::den);
687 }
688
689 // Wavelength = period / time_per_unit_distance
690 double wavelength_count = period_in_duration_units / static_cast<double>(time_per_unit_distance.count());
691
692 return Distance(static_cast<typename Distance::rep>(wavelength_count));
693 }
694
710 template<distance_like Distance>
711 constexpr Distance wavelength(double velocity = 299792458.0) const {
712 if (_r == rep(0)) {
713 return Distance::max();
714 }
715
716 // Convert frequency to Hz
717 double freq_hz =
718 static_cast<double>(_r) * static_cast<double>(precision::num) / static_cast<double>(precision::den);
719
720 // Calculate wavelength in meters: wavelength = velocity / frequency
722
723 // Convert from meters to the target distance type's units
724 // Distance::period represents the ratio of the distance unit to meters
725 using distance_period = typename Distance::period;
726 double wavelength_in_units =
727 wavelength_meters * static_cast<double>(distance_period::den) / static_cast<double>(distance_period::num);
728
729 return Distance(static_cast<typename Distance::rep>(wavelength_in_units));
730 }
731
732private:
733 rep _r{};
734};
735
749template<typename ToFreq, typename Rep, typename Precision>
751 if constexpr (std::is_same_v<ToFreq, frequency<Rep, Precision>>) {
752 return f;
753 } else {
754 using to_rep = typename ToFreq::rep;
755 using to_precision = typename ToFreq::precision;
756 using cf = std::ratio_divide<Precision, to_precision>;
757
758 // Use wider intermediate type for integer-to-integer conversions
759 if constexpr (std::is_integral_v<Rep> && std::is_integral_v<to_rep>) {
760#ifdef __SIZEOF_INT128__
761 using cr = std::common_type_t<to_rep, Rep, intmax_t>;
762 using wider_t = std::conditional_t<std::is_signed_v<cr>, __int128, unsigned __int128>;
763#else
764 using wider_t = intmax_t;
765#endif
766
767 if constexpr (cf::den == 1 && cf::num == 1) {
768 return ToFreq(static_cast<to_rep>(f.count()));
769 } else if constexpr (cf::den == 1) {
770 wider_t result = static_cast<wider_t>(f.count()) * static_cast<wider_t>(cf::num);
771 return ToFreq(static_cast<to_rep>(result));
772 } else if constexpr (cf::num == 1) {
773 wider_t result = static_cast<wider_t>(f.count()) / static_cast<wider_t>(cf::den);
774 return ToFreq(static_cast<to_rep>(result));
775 } else {
776 // Use GCD to reduce operands: count * num / den
777 // Compute g = gcd(count, den), then (count/g) * num / (den/g)
778 wider_t count = static_cast<wider_t>(f.count());
779 wider_t den = static_cast<wider_t>(cf::den);
780 wider_t g = _runtime_gcd(count, den);
781 wider_t reduced_count = count / g;
783 wider_t result = reduced_count * static_cast<wider_t>(cf::num) / reduced_den;
784 return ToFreq(static_cast<to_rep>(result));
785 }
786 } else {
787 // Floating-point path
788 using cr = std::common_type_t<to_rep, Rep, intmax_t>;
789 if constexpr (cf::den == 1 && cf::num == 1) {
790 return ToFreq(static_cast<to_rep>(f.count()));
791 } else if constexpr (cf::den == 1) {
792 return ToFreq(static_cast<to_rep>(static_cast<cr>(f.count()) * static_cast<cr>(cf::num)));
793 } else if constexpr (cf::num == 1) {
794 return ToFreq(static_cast<to_rep>(static_cast<cr>(f.count()) / static_cast<cr>(cf::den)));
795 } else {
796 return ToFreq(
797 static_cast<to_rep>(
798 static_cast<cr>(f.count()) * static_cast<cr>(cf::num) / static_cast<cr>(cf::den)
799 )
800 );
801 }
802 }
803 }
804}
805
825template<typename ToFreq, typename Rep, typename Precision>
827 using to_rep = typename ToFreq::rep;
829
830 if constexpr (std::is_integral_v<Rep> && std::is_integral_v<to_rep>) {
831 if (result > f) {
832 return ToFreq(result.count() - to_rep(1));
833 }
834 }
835
836 return result;
837}
838
858template<typename ToFreq, typename Rep, typename Precision>
860 using to_rep = typename ToFreq::rep;
862
863 if constexpr (std::is_integral_v<Rep> && std::is_integral_v<to_rep>) {
864 if (result < f) {
865 return ToFreq(result.count() + to_rep(1));
866 }
867 }
868
869 return result;
870}
871
894template<typename ToFreq, typename Rep, typename Precision>
896 using to_rep = typename ToFreq::rep;
897
898 if constexpr (std::is_integral_v<Rep> && std::is_integral_v<to_rep>) {
902
903 auto diff_lower = f - lower;
904 auto diff_upper = upper - f;
905
906 if (diff_lower < diff_upper) {
907 return lower;
908 } else if (diff_lower > diff_upper) {
909 return upper;
910 } else {
911 // Tie: round to even
912 return (lower.count() % to_rep(2) == to_rep(0)) ? lower : upper;
913 }
914 } else {
916 }
917}
918
954template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
956 -> std::common_type_t<frequency<Rep1, Precision1>, frequency<Rep2, Precision2>> {
957 using cf = std::common_type_t<frequency<Rep1, Precision1>, frequency<Rep2, Precision2>>;
958 return abs(cf(f1) - cf(f2));
959}
960
976template<typename Rep, typename Precision>
980
982template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
984 -> std::common_type_t<frequency<Rep1, Precision1>, frequency<Rep2, Precision2>> {
985 using cf = std::common_type_t<frequency<Rep1, Precision1>, frequency<Rep2, Precision2>>;
986 return cf(cf(lhs).count() + cf(rhs).count());
987}
988
990template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
992 -> std::common_type_t<frequency<Rep1, Precision1>, frequency<Rep2, Precision2>> {
993 using cf = std::common_type_t<frequency<Rep1, Precision1>, frequency<Rep2, Precision2>>;
994 return cf(cf(lhs).count() - cf(rhs).count());
995}
996
998template<typename Rep1, typename Precision, typename Rep2>
999 requires not_frequency<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>>
1000constexpr auto operator*(const frequency<Rep1, Precision>& f, const Rep2& r)
1003 return cf(cf(f).count() * r);
1004}
1005
1007template<typename Rep1, typename Rep2, typename Precision>
1008 requires not_frequency<Rep1> && std::convertible_to<const Rep1&, std::common_type_t<Rep1, Rep2>>
1009constexpr auto operator*(const Rep1& r, const frequency<Rep2, Precision>& f)
1011 return f * r;
1012}
1013
1015template<typename Rep1, typename Precision, typename Rep2>
1016 requires not_frequency<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>>
1017constexpr auto operator/(const frequency<Rep1, Precision>& f, const Rep2& s)
1020 return cf(cf(f).count() / s);
1021}
1022
1024template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
1026 -> std::common_type_t<Rep1, Rep2> {
1027 using cf = std::common_type_t<frequency<Rep1, Precision1>, frequency<Rep2, Precision2>>;
1028 return cf(lhs).count() / cf(rhs).count();
1029}
1030
1032template<typename Rep1, typename Precision, typename Rep2>
1033 requires not_frequency<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>> &&
1035constexpr auto operator%(const frequency<Rep1, Precision>& f, const Rep2& s)
1038 return cf(cf(f).count() % s);
1039}
1040
1042template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
1044constexpr auto operator%(const frequency<Rep1, Precision1>& lhs, const frequency<Rep2, Precision2>& rhs)
1045 -> std::common_type_t<frequency<Rep1, Precision1>, frequency<Rep2, Precision2>> {
1046 using cf = std::common_type_t<frequency<Rep1, Precision1>, frequency<Rep2, Precision2>>;
1047 return cf(cf(lhs).count() % cf(rhs).count());
1048}
1049
1050template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
1052 using ct = std::common_type_t<frequency<Rep1, Precision1>, frequency<Rep2, Precision2>>;
1053 return ct(lhs).count() == ct(rhs).count();
1054}
1055
1056template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
1057 requires std::three_way_comparable<std::common_type_t<Rep1, Rep2>>
1059 using ct = std::common_type_t<frequency<Rep1, Precision1>, frequency<Rep2, Precision2>>;
1060 return ct(lhs).count() <=> ct(rhs).count();
1061}
1062
1100
// end of FrequencyTypes group
1102
1104// SI prefix for a hertz-per-count ratio; nullptr when unmapped.
1105template<typename Ratio>
1106struct _si_prefix {
1107 static constexpr const char* value = nullptr;
1108};
1109template<>
1110struct _si_prefix<std::femto> {
1111 static constexpr const char* value = "f";
1112};
1113template<>
1114struct _si_prefix<std::pico> {
1115 static constexpr const char* value = "p";
1116};
1117template<>
1118struct _si_prefix<std::nano> {
1119 static constexpr const char* value = "n";
1120};
1121template<>
1122struct _si_prefix<std::micro> {
1123 static constexpr const char* value = "µ";
1124};
1125template<>
1126struct _si_prefix<std::milli> {
1127 static constexpr const char* value = "m";
1128};
1129template<>
1130struct _si_prefix<std::ratio<1>> {
1131 static constexpr const char* value = "";
1132};
1133template<>
1134struct _si_prefix<std::kilo> {
1135 static constexpr const char* value = "k";
1136};
1137template<>
1138struct _si_prefix<std::mega> {
1139 static constexpr const char* value = "M";
1140};
1141template<>
1142struct _si_prefix<std::giga> {
1143 static constexpr const char* value = "G";
1144};
1145template<>
1146struct _si_prefix<std::tera> {
1147 static constexpr const char* value = "T";
1148};
1149template<typename Ratio>
1150inline constexpr const char* _si_prefix_v = _si_prefix<typename Ratio::type>::value;
1151
1152// Appends a NUL-terminated string to a format output iterator.
1153template<typename OutputIt>
1154constexpr OutputIt _format_append(OutputIt out, const char* s) {
1155 for (; *s != '\0'; ++s) {
1156 *out++ = *s;
1157 }
1158 return out;
1159}
1160
1161#if CONFIG_FREQUENCY_STD_FORMAT
1162// Reports an unusable format spec from a formatter's parse().
1163//
1164// std::format constant-evaluates parse() to check the format string, so
1165// throwing there makes a bad spec a compile error rather than a runtime fault.
1166// Where exceptions are unavailable, calling a non-constexpr function fails that
1167// same constant evaluation and so reports the error at compile time too; a
1168// runtime parse (std::vformat with a runtime format string) has no way to
1169// report it and terminates.
1170[[noreturn]] inline void _format_error(const char* what) {
1171#if defined(__cpp_exceptions) && __cpp_exceptions
1172 throw std::format_error(what);
1173#else
1174 (void)what;
1175 std::abort();
1176#endif
1177}
1178#endif
1185template<typename Rep, typename Precision>
1186 requires(!treat_as_inexact_v<Rep>)
1188 using precision = typename frequency<Rep, Precision>::precision;
1189 static_assert(_si_prefix_v<precision> != nullptr, "frequency: precision has no SI prefix");
1190 return std::to_string(f.count()) + _si_prefix_v<precision> + "Hz";
1191}
1192
1193} // namespace freq
1194
1195namespace std {
1196
1197template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>
1198struct common_type<freq::frequency<Rep1, Precision1>, freq::frequency<Rep2, Precision2>> {
1199private:
1200 using common_precision = std::ratio<
1201 freq::_gcd(Precision1::num, Precision2::num),
1202 (Precision1::den / freq::_gcd(Precision1::den, Precision2::den)) * Precision2::den>;
1203
1204public:
1205 using type = freq::frequency<std::common_type_t<Rep1, Rep2>, common_precision>;
1206};
1207
1208#if CONFIG_FREQUENCY_STD_FORMAT
1209// "{}" prints the exact stored count with a precision-qualified unit
1210// (kilohertz(433) -> "433kHz"). A non-empty spec is applied to the value in
1211// hertz (as double): std::format("{:.1f}", millihertz(1500)) == "1.5Hz".
1212template<typename Rep, typename Precision>
1213struct formatter<freq::frequency<Rep, Precision>> {
1214private:
1215 using _precision = typename freq::frequency<Rep, Precision>::precision;
1216 static constexpr const char* _prefix = freq::_si_prefix_v<_precision>;
1217 std::formatter<double> _num;
1218 bool _has_spec = false;
1219
1220public:
1221 constexpr auto parse(format_parse_context& ctx) {
1222 auto it = ctx.begin();
1223 if (it == ctx.end() || *it == '}') {
1224 if constexpr (_prefix == nullptr) {
1225 freq::_format_error("frequency: precision has no SI prefix; use an explicit format spec");
1226 }
1227 return it;
1228 }
1229 _has_spec = true;
1230 return _num.parse(ctx);
1231 }
1232
1233 template<typename FormatContext>
1234 auto format(const freq::frequency<Rep, Precision>& f, FormatContext& ctx) const {
1235 if (_has_spec) {
1236 double hz = static_cast<double>(f.count()) * _precision::num / _precision::den;
1237 auto out = _num.format(hz, ctx);
1238 return freq::_format_append(out, "Hz");
1239 }
1240 auto out = std::format_to(ctx.out(), "{}", f.count());
1241 out = freq::_format_append(out, _prefix);
1242 return freq::_format_append(out, "Hz");
1243 }
1244};
1245#endif
1246
1247} // namespace std
1248
1254namespace detail {
1255
1256template<unsigned long long Value, unsigned long long Power>
1257struct pow10 {
1258 static constexpr unsigned long long value = 10 * pow10<Value, Power - 1>::value;
1259};
1260
1261template<unsigned long long Value>
1262struct pow10<Value, 0> {
1263 static constexpr unsigned long long value = Value;
1264};
1265
1266template<char... Digits>
1267struct parse_int;
1268
1269template<char D, char... Rest>
1270struct parse_int<D, Rest...> {
1271 static_assert(D >= '0' && D <= '9', "invalid digit");
1272 static constexpr unsigned long long value = pow10<D - '0', sizeof...(Rest)>::value + parse_int<Rest...>::value;
1273};
1274
1275template<char D>
1276struct parse_int<D> {
1277 static_assert(D >= '0' && D <= '9', "invalid digit");
1278 static constexpr unsigned long long value = D - '0';
1279};
1280
1281template<typename Freq, char... Digits>
1282constexpr Freq check_overflow() {
1283 using parsed = parse_int<Digits...>;
1284 constexpr typename Freq::rep repval = parsed::value;
1285 static_assert(
1286 repval >= 0 && static_cast<unsigned long long>(repval) == parsed::value,
1287 "literal value cannot be represented by frequency type"
1288 );
1289 return Freq(repval);
1290}
1291
1292} // namespace detail
1296template<char... Digits>
1297constexpr freq::millihertz operator""_mHz() {
1298 return detail::check_overflow<freq::millihertz, Digits...>();
1299}
1300
1302template<char... Digits>
1303constexpr freq::hertz operator""_Hz() {
1304 return detail::check_overflow<freq::hertz, Digits...>();
1305}
1306
1308template<char... Digits>
1309constexpr freq::kilohertz operator""_kHz() {
1310 return detail::check_overflow<freq::kilohertz, Digits...>();
1311}
1312
1314template<char... Digits>
1315constexpr freq::megahertz operator""_MHz() {
1316 return detail::check_overflow<freq::megahertz, Digits...>();
1317}
1318
1320template<char... Digits>
1321constexpr freq::gigahertz operator""_GHz() {
1322 return detail::check_overflow<freq::gigahertz, Digits...>();
1323}
1324
1326template<char... Digits>
1327constexpr freq::terahertz operator""_THz() {
1328 return detail::check_overflow<freq::terahertz, Digits...>();
1329}
1330
1331} // namespace frequency_literals
A frequency value with a representation and precision.
constexpr Duration period() const
Returns the period of this frequency as a duration.
frequency semitone_shift(T semitones) const
Returns this frequency shifted by a number of semitones.
constexpr frequency & operator++()
static constexpr frequency max() noexcept
Returns the maximum representable frequency.
constexpr frequency & operator%=(const rep &r)
constexpr frequency(const Rep2 &r)
Constructs from a tick count.
constexpr frequency< typename std::common_type< rep >::type, precision > operator-() const
constexpr frequency(const frequency< Rep2, Precision2 > &f)
Explicit constructor for lossy precision conversions.
constexpr frequency & operator-=(const frequency &f)
Rep rep
The representation type.
T octaves_from(const frequency &other) const
Calculates the interval in octaves between this frequency and another.
constexpr Distance wavelength(const Duration &time_per_unit_distance) const
Calculates the wavelength for this frequency.
constexpr frequency & operator+=(const frequency &f)
static constexpr frequency min() noexcept
Returns the minimum representable frequency.
constexpr frequency()=default
Constructs a zero frequency.
static constexpr frequency zero() noexcept
Returns a zero frequency.
constexpr frequency subharmonic(unsigned int n) const
Returns the nth subharmonic of this frequency.
frequency(const frequency &)=default
T semitones_from(const frequency &other) const
Calculates the interval in semitones between this frequency and another.
frequency & operator=(const frequency &)=default
constexpr frequency & operator/=(const rep &r)
constexpr frequency< typename std::common_type< rep >::type, precision > operator+() const
typename Precision::type precision
The precision as a std::ratio.
frequency octave_shift(T octaves) const
Returns this frequency shifted by a number of octaves.
constexpr frequency operator++(int)
constexpr frequency operator--(int)
constexpr rep count() const
Returns the tick count.
constexpr frequency & operator--()
constexpr frequency & operator%=(const frequency &f)
constexpr frequency harmonic(unsigned int n) const
Returns the nth harmonic of this frequency.
~frequency()=default
constexpr frequency & operator*=(const rep &r)
constexpr Distance wavelength(double velocity=299792458.0) const
Calculates the wavelength for this frequency given a propagation velocity.
frequency< int64_t, std::tera > terahertz
Frequency with 1,000,000,000,000 Hz (terahertz) precision.
frequency< int64_t, std::mega > megahertz
Frequency with 1,000,000 Hz (megahertz) precision.
frequency< int64_t, std::kilo > kilohertz
Frequency with 1000 Hz (kilohertz) precision.
frequency< int64_t, std::giga > gigahertz
Frequency with 1,000,000,000 Hz (gigahertz) precision.
frequency< int64_t, std::milli > millihertz
Frequency with 0.001 Hz (millihertz) precision.
frequency< int64_t > hertz
Frequency with 1 Hz precision.
Frequency types and utilities.
Definition frequency.hpp:89
constexpr bool operator==(const frequency< Rep1, Precision1 > &lhs, const frequency< Rep2, Precision2 > &rhs)
constexpr auto operator<=>(const frequency< Rep1, Precision1 > &lhs, const frequency< Rep2, Precision2 > &rhs)
std::string to_string(const frequency< Rep, Precision > &f)
Renders a frequency as its exact count with a precision-qualified unit, e.g.
constexpr auto operator/(const frequency< Rep1, Precision > &f, const Rep2 &s) -> frequency< std::common_type_t< Rep1, Rep2 >, Precision >
Divides a frequency by a scalar.
constexpr ToFreq ceil(const frequency< Rep, Precision > &f)
Converts a frequency to the target type, rounding toward positive infinity.
constexpr ToFreq frequency_cast(const frequency< Rep, Precision > &f)
Converts a frequency to a different precision or representation.
constexpr auto beat(const frequency< Rep1, Precision1 > &f1, const frequency< Rep2, Precision2 > &f2) -> std::common_type_t< frequency< Rep1, Precision1 >, frequency< Rep2, Precision2 > >
Calculates the beat frequency between two frequencies.
constexpr auto operator-(const frequency< Rep1, Precision1 > &lhs, const frequency< Rep2, Precision2 > &rhs) -> std::common_type_t< frequency< Rep1, Precision1 >, frequency< Rep2, Precision2 > >
Returns the difference of two frequencies.
constexpr auto operator*(const frequency< Rep1, Precision > &f, const Rep2 &r) -> frequency< std::common_type_t< Rep1, Rep2 >, Precision >
Multiplies a frequency by a scalar.
constexpr ToFreq round(const frequency< Rep, Precision > &f)
Converts a frequency to the target type, rounding to nearest (ties to even).
constexpr bool is_frequency_v
constexpr ToFreq floor(const frequency< Rep, Precision > &f)
Converts a frequency to the target type, rounding toward negative infinity.
constexpr auto operator+(const frequency< Rep1, Precision1 > &lhs, const frequency< Rep2, Precision2 > &rhs) -> std::common_type_t< frequency< Rep1, Precision1 >, frequency< Rep2, Precision2 > >
Returns the sum of two frequencies.
constexpr frequency< Rep, Precision > abs(const frequency< Rep, Precision > &f)
Returns the absolute value of a frequency.
User-defined literals for frequency types.
Trait to detect frequency specializations.
Definition frequency.hpp:99