electro 0.3.0
Type-safe electrical units library modeled after std::chrono
Loading...
Searching...
No Matches
decibel.hpp
Go to the documentation of this file.
1#pragma once
2
57#include <cassert>
58#include <cmath>
59#include <electro/electro.hpp>
60
61namespace electro {
62
63// ============================================================================
64// Gain: a dimensionless logarithmic ratio
65// ============================================================================
66
75 static constexpr const char* symbol = "dB";
76};
77
79template<typename Rep, typename Precision = std::ratio<1>>
81
88
89// ============================================================================
90// References
91// ============================================================================
92
106 using precision = std::milli;
107 static constexpr int factor = 10;
108 static constexpr const char* symbol = "dBm";
109};
110
114 using precision = std::ratio<1>;
115 static constexpr int factor = 10;
116 static constexpr const char* symbol = "dBW";
117};
118
122 using precision = std::ratio<1>;
123 static constexpr int factor = 20;
124 static constexpr const char* symbol = "dBV";
125};
126
130 using precision = std::milli;
131 static constexpr int factor = 20;
132 static constexpr const char* symbol = "dBmV";
133};
134
138 using precision = std::micro;
139 static constexpr int factor = 20;
140 static constexpr const char* symbol = "dBµV";
141};
142
// end of References group
144
152template<typename Reference>
153concept decibel_reference = requires {
154 typename Reference::unit;
155 typename Reference::precision;
156 { Reference::factor } -> std::convertible_to<int>;
157 { Reference::symbol } -> std::convertible_to<const char*>;
158} && (Reference::factor == 10 || Reference::factor == 20);
159
161template<typename Reference>
162inline constexpr bool is_power_reference_v = false;
163
164template<decibel_reference Reference>
165inline constexpr bool is_power_reference_v<Reference> = Reference::factor == 10;
166
168template<typename Reference>
169inline constexpr bool is_field_reference_v = false;
170
171template<decibel_reference Reference>
172inline constexpr bool is_field_reference_v<Reference> = Reference::factor == 20;
173
174// ============================================================================
175// level
176// ============================================================================
177
178template<typename Reference, typename Rep, typename Precision = std::ratio<1>>
179class level;
180
185template<typename T>
186struct is_level : std::false_type {};
187
188template<typename Reference, typename Rep, typename Precision>
189struct is_level<level<Reference, Rep, Precision>> : std::true_type {};
190
191template<typename T>
192inline constexpr bool is_level_v = is_level<T>::value;
193
211template<typename Reference, typename Rep, typename Precision>
212class level {
213 static_assert(decibel_reference<Reference>, "Reference must satisfy the decibel_reference concept");
214
215public:
217 using reference = Reference;
219 using rep = Rep;
221 using precision = typename Precision::type;
224
226 constexpr level() = default;
227 level(const level&) = default;
228
237 template<typename Rep2>
238 requires std::convertible_to<const Rep2&, rep> && (treat_as_inexact_v<rep> || !treat_as_inexact_v<Rep2>)
239 constexpr explicit level(const Rep2& r)
240 : _o(static_cast<rep>(r)) {}
241
243 constexpr explicit level(const offset& o)
244 : _o(o) {}
245
251 template<typename Rep2, typename Precision2>
252 requires std::convertible_to<gain<Rep2, Precision2>, offset>
255
257 template<typename Rep2, typename Precision2>
258 requires(!std::is_same_v<level, level<Reference, Rep2, Precision2>>) &&
259 (!std::convertible_to<gain<Rep2, Precision2>, offset>) &&
260 std::constructible_from<offset, gain<Rep2, Precision2>>
261 constexpr explicit level(const level<Reference, Rep2, Precision2>& l)
262 : _o(offset(l.offset_from_reference())) {}
263
264 ~level() = default;
265 level& operator=(const level&) = default;
266
268 constexpr offset offset_from_reference() const { return _o; }
269
271 constexpr rep count() const { return _o.count(); }
272
281 constexpr level operator-() const { return level(-_o); }
282 constexpr level operator+() const { return *this; }
283
284 constexpr level& operator+=(const offset& o) {
285 _o += o;
286 return *this;
287 }
288
289 constexpr level& operator-=(const offset& o) {
290 _o -= o;
291 return *this;
292 }
293
295 static constexpr level reference_level() noexcept { return level(offset::zero()); }
296
298 static constexpr level min() noexcept { return level(offset::min()); }
299
301 static constexpr level max() noexcept { return level(offset::max()); }
302
303private:
304 offset _o{};
305};
306
312template<typename ToLevel, typename Reference, typename Rep, typename Precision>
314 static_assert(
315 std::is_same_v<typename ToLevel::reference, Reference>, "level_cast cannot convert between different references"
316 );
317 return ToLevel(quantity_cast<typename ToLevel::offset>(l.offset_from_reference()));
318}
319
321template<typename ToLevel, typename Reference, typename Rep, typename Precision>
323 static_assert(std::is_same_v<typename ToLevel::reference, Reference>, "floor cannot convert between references");
324 return ToLevel(floor<typename ToLevel::offset>(l.offset_from_reference()));
325}
326
328template<typename ToLevel, typename Reference, typename Rep, typename Precision>
330 static_assert(std::is_same_v<typename ToLevel::reference, Reference>, "ceil cannot convert between references");
331 return ToLevel(ceil<typename ToLevel::offset>(l.offset_from_reference()));
332}
333
335template<typename ToLevel, typename Reference, typename Rep, typename Precision>
337 static_assert(std::is_same_v<typename ToLevel::reference, Reference>, "round cannot convert between references");
338 return ToLevel(round<typename ToLevel::offset>(l.offset_from_reference()));
339}
340
341// ============================================================================
342// Affine arithmetic: level +/- gain -> level, level - level -> gain
343// ============================================================================
344
346template<typename R1, typename P1, typename R2, typename P2>
347using _common_gain = std::common_type_t<gain<R1, P1>, gain<R2, P2>>;
351template<typename Reference, typename Rep1, typename Precision1, typename Rep2, typename Precision2>
352constexpr auto
358
360template<typename Reference, typename Rep1, typename Precision1, typename Rep2, typename Precision2>
361constexpr auto
365
367template<typename Reference, typename Rep1, typename Precision1, typename Rep2, typename Precision2>
368constexpr auto
374
382template<typename Reference, typename Rep1, typename Precision1, typename Rep2, typename Precision2>
384 return a.offset_from_reference() - b.offset_from_reference();
385}
386
387template<typename Reference, typename Rep1, typename Precision1, typename Rep2, typename Precision2>
389 return a.offset_from_reference() == b.offset_from_reference();
390}
391
392template<typename Reference, typename Rep1, typename Precision1, typename Rep2, typename Precision2>
393 requires std::three_way_comparable<std::common_type_t<Rep1, Rep2>>
395 return a.offset_from_reference() <=> b.offset_from_reference();
396}
397
398// ============================================================================
399// Linear domain conversions (not constexpr before C++26)
400// ============================================================================
401
403template<typename Offset>
404constexpr double _db_value(const Offset& o) {
405 return quantity_cast<gain<double>>(o).count();
406}
407
408// The inverse of _db_value, rounding to nearest for integral reps. This exists
409// because quantity_cast (and floor/ceil/round) truncate when converting from
410// an inexact rep, and truncation would round e.g. -73.5 dB the wrong way.
411template<typename Offset>
412inline Offset _db_offset(double db) {
415 } else {
416 using p = typename Offset::precision;
417 const double scaled = db * static_cast<double>(p::den) / static_cast<double>(p::num);
418 return Offset(static_cast<typename Offset::rep>(std::llround(scaled)));
419 }
420}
437template<typename Reference, typename Rep, typename Precision>
440 return linear(std::pow(10.0, _db_value(l.offset_from_reference()) / Reference::factor));
441}
442
461template<typename ToLevel, typename Unit, typename Rep, typename Precision>
463 using ref = typename ToLevel::reference;
464 static_assert(
465 std::is_same_v<typename ref::unit, Unit>, "to_level: quantity unit does not match the level reference"
466 );
467
469 const double ratio = quantity_cast<linear>(q).count();
470 if (!(ratio > 0.0)) {
471 assert(ratio >= 0.0 && "to_level: a negative quantity has no logarithmic level");
472 return ToLevel::min();
473 }
474 return ToLevel(_db_offset<typename ToLevel::offset>(ref::factor * std::log10(ratio)));
475}
476
486template<typename Rep, typename Precision>
488 return std::pow(10.0, _db_value(g) / 10.0);
489}
490
500template<typename Rep, typename Precision>
502 return std::pow(10.0, _db_value(g) / 20.0);
503}
504
507 assert(ratio > 0.0 && "power_gain: ratio must be positive");
508 return gain<double>(10.0 * std::log10(ratio));
509}
510
513 assert(ratio > 0.0 && "amplitude_gain: ratio must be positive");
514 return gain<double>(20.0 * std::log10(ratio));
515}
516
533template<typename Reference, typename Rep1, typename Precision1, typename Rep2, typename Precision2>
540
541// ============================================================================
542// Type aliases
543// ============================================================================
544
551template<typename Rep, typename Precision = std::ratio<1>>
553
555template<typename Rep, typename Precision = std::ratio<1>>
557
559template<typename Rep, typename Precision = std::ratio<1>>
561
563template<typename Rep, typename Precision = std::ratio<1>>
565
567template<typename Rep, typename Precision = std::ratio<1>>
569
584
// end of LevelTypes group
586
587// ============================================================================
588// String conversion and formatting
589// ============================================================================
590
600template<typename Precision>
602
604// The number of decimal places needed to render a count at the given precision
605// as a fixed-point decimal, or -1 when the precision is not 1, 1/10, 1/100, ...
606consteval int _decimal_places(intmax_t num, intmax_t den) {
607 if (num != 1) {
608 return -1;
609 }
610 int places = 0;
611 while (den % 10 == 0) {
612 den /= 10;
613 ++places;
614 }
615 return den == 1 ? places : -1;
616}
617
618template<typename Precision>
619inline constexpr bool _is_decimal_precision = _decimal_places(Precision::num, Precision::den) >= 0;
620
621template<typename Precision, typename Rep>
622inline std::string _format_db(const Rep& count) {
623 static_assert(_is_decimal_precision<Precision>, "precision cannot be rendered as a fixed-point decimal");
624 if constexpr (treat_as_inexact_v<Rep>) {
625 return std::to_string(static_cast<double>(count) * Precision::num / Precision::den);
626 } else {
627 constexpr int places = _decimal_places(Precision::num, Precision::den);
628 if constexpr (places == 0) {
629 return std::to_string(count);
630 } else {
631 const bool negative = count < Rep(0);
632 // Compute the magnitude in unsigned arithmetic so that the most
633 // negative representable count does not overflow on negation.
634 const unsigned long long magnitude =
635 negative ? 0ULL - static_cast<unsigned long long>(count) : static_cast<unsigned long long>(count);
636
637 const unsigned long long whole = magnitude / Precision::den;
638 const unsigned long long frac = magnitude % Precision::den;
639
640 std::string frac_digits = std::to_string(frac);
641 frac_digits.insert(0, static_cast<size_t>(places) - frac_digits.size(), '0');
642
643 return (negative ? "-" : "") + std::to_string(whole) + "." + frac_digits;
644 }
645 }
646}
655template<typename Rep, typename Precision>
660
664template<typename Reference, typename Rep, typename Precision>
666inline std::string to_string(const level<Reference, Rep, Precision>& l) {
667 return _format_db<typename Precision::type>(l.count()) + Reference::symbol;
668}
669
670} // namespace electro
671
672namespace std {
673
674template<typename Reference, typename Rep1, typename Precision1, typename Rep2, typename Precision2>
675struct common_type<electro::level<Reference, Rep1, Precision1>, electro::level<Reference, Rep2, Precision2>> {
676private:
677 using common_offset = std::common_type_t<electro::gain<Rep1, Precision1>, electro::gain<Rep2, Precision2>>;
678
679public:
681};
682
683#if CONFIG_ELECTRO_STD_FORMAT
684// "{}" renders the exact value as a fixed-point decimal ("10.50dB"). A
685// non-empty spec is applied to the value in decibels (as double):
686// std::format("{:.1f}", 1050_cdB) == "10.5dB".
687template<typename Rep, typename Precision>
689struct formatter<electro::quantity<electro::decibel_unit, Rep, Precision>, char> {
690private:
691 std::formatter<double> _num;
692 bool _has_spec = false;
693
694public:
695 constexpr auto parse(format_parse_context& ctx) {
696 auto it = ctx.begin();
697 if (it == ctx.end() || *it == '}') {
698 return it;
699 }
700 _has_spec = true;
701 return _num.parse(ctx);
702 }
703
704 template<typename FormatContext>
705 auto format(const electro::quantity<electro::decibel_unit, Rep, Precision>& g, FormatContext& ctx) const {
706 if (_has_spec) {
707 double value = static_cast<double>(g.count()) * Precision::num / Precision::den;
708 auto out = _num.format(value, ctx);
710 }
711 return std::format_to(
713 );
714 }
715};
716
717// See the gain formatter above; the level's reference symbol is appended
718// ("13.01dBm", "{:.1f}" -> "13.0dBm").
719template<typename Reference, typename Rep, typename Precision>
721struct formatter<electro::level<Reference, Rep, Precision>, char> {
722private:
723 std::formatter<double> _num;
724 bool _has_spec = false;
725
726public:
727 constexpr auto parse(format_parse_context& ctx) {
728 auto it = ctx.begin();
729 if (it == ctx.end() || *it == '}') {
730 return it;
731 }
732 _has_spec = true;
733 return _num.parse(ctx);
734 }
735
736 template<typename FormatContext>
737 auto format(const electro::level<Reference, Rep, Precision>& l, FormatContext& ctx) const {
738 if (_has_spec) {
739 double value = static_cast<double>(l.count()) * Precision::num / Precision::den;
740 auto out = _num.format(value, ctx);
741 return electro::_format_append(out, Reference::symbol);
742 }
743 return std::format_to(
744 ctx.out(), "{}{}", electro::_format_db<typename Precision::type>(l.count()), Reference::symbol
745 );
746 }
747};
748#endif
749
750} // namespace std
751
753
755template<char... Digits>
756constexpr electro::decibels operator""_dB() {
757 return detail::check_overflow<electro::decibels, Digits...>();
758}
759
761template<char... Digits>
762constexpr electro::centidecibels operator""_cdB() {
763 return detail::check_overflow<electro::centidecibels, Digits...>();
764}
765
767template<char... Digits>
768constexpr electro::dbm operator""_dBm() {
769 return electro::dbm(detail::check_overflow<electro::decibels, Digits...>());
770}
771
773template<char... Digits>
774constexpr electro::centi_dbm operator""_cdBm() {
775 return electro::centi_dbm(detail::check_overflow<electro::centidecibels, Digits...>());
776}
777
779template<char... Digits>
780constexpr electro::dbw operator""_dBW() {
781 return electro::dbw(detail::check_overflow<electro::decibels, Digits...>());
782}
783
785template<char... Digits>
786constexpr electro::dbv operator""_dBV() {
787 return electro::dbv(detail::check_overflow<electro::decibels, Digits...>());
788}
789
791template<char... Digits>
792constexpr electro::dbmv operator""_dBmV() {
793 return electro::dbmv(detail::check_overflow<electro::decibels, Digits...>());
794}
795
797template<char... Digits>
798constexpr electro::dbuv operator""_dBuV() {
799 return electro::dbuv(detail::check_overflow<electro::decibels, Digits...>());
800}
801
803template<char... Digits>
804constexpr electro::dbuv operator""_dBµV() {
805 return electro::dbuv(detail::check_overflow<electro::decibels, Digits...>());
806}
807
808} // namespace electro_literals
An absolute point on a logarithmic scale, relative to a reference.
Definition decibel.hpp:212
level(const level &)=default
constexpr level & operator-=(const offset &o)
Definition decibel.hpp:289
constexpr level()=default
Constructs a level at the reference point (0 dB offset).
constexpr level(const Rep2 &r)
Constructs from an offset count in dB.
Definition decibel.hpp:239
gain< Rep, Precision > offset
The gain type measuring the offset from the reference.
Definition decibel.hpp:223
level & operator=(const level &)=default
~level()=default
Reference reference
The reference tag type.
Definition decibel.hpp:217
static constexpr level min() noexcept
Returns the minimum representable level.
Definition decibel.hpp:298
constexpr level operator+() const
Definition decibel.hpp:282
static constexpr level max() noexcept
Returns the maximum representable level.
Definition decibel.hpp:301
Rep rep
The representation type.
Definition decibel.hpp:219
static constexpr level reference_level() noexcept
Returns the reference point itself (a 0 dB offset).
Definition decibel.hpp:295
constexpr offset offset_from_reference() const
Returns the offset from the reference point.
Definition decibel.hpp:268
constexpr level & operator+=(const offset &o)
Definition decibel.hpp:284
constexpr level(const level< Reference, Rep2, Precision2 > &l)
Explicit constructor for lossy precision conversions.
Definition decibel.hpp:261
typename Precision::type precision
The offset precision, in dB, as a std::ratio.
Definition decibel.hpp:221
constexpr rep count() const
Returns the raw offset count, in units of Precision.
Definition decibel.hpp:271
constexpr level(const level< Reference, Rep2, Precision2 > &l)
Converts from a level with the same reference.
Definition decibel.hpp:253
constexpr level operator-() const
Reflects the level about its reference point.
Definition decibel.hpp:281
constexpr level(const offset &o)
Constructs from an offset relative to the reference.
Definition decibel.hpp:243
A quantity of an electrical unit with a representation and precision.
Definition electro.hpp:286
static constexpr quantity zero() noexcept
Returns a zero quantity.
Definition electro.hpp:410
static constexpr quantity max() noexcept
Returns the maximum representable quantity.
Definition electro.hpp:416
constexpr rep count() const
Returns the tick count.
Definition electro.hpp:351
static constexpr quantity min() noexcept
Returns the minimum representable quantity.
Definition electro.hpp:413
Concept for decibel reference tags.
Definition decibel.hpp:153
dbw_level< int64_t > dbw
Power level in dBW, with 1 dB precision.
Definition decibel.hpp:575
dbm_level< int64_t > dbm
Power level in dBm, with 1 dB precision.
Definition decibel.hpp:571
dbmv_level< int64_t > dbmv
Voltage level in dBmV, with 1 dB precision.
Definition decibel.hpp:579
dbm_level< int64_t, std::centi > centi_dbm
Power level in dBm, with 0.01 dB precision.
Definition decibel.hpp:573
dbv_level< int64_t > dbv
Voltage level in dBV, with 1 dB precision.
Definition decibel.hpp:577
dbuv_level< int64_t > dbuv
Voltage level in dBµV, with 1 dB precision.
Definition decibel.hpp:581
User-defined literals for electrical quantity types.
Definition decibel.hpp:752
Electrical unit types and utilities.
Definition decibel.hpp:61
auto to_linear(const level< Reference, Rep, Precision > &l)
Converts a level to the equivalent linear quantity.
Definition decibel.hpp:438
gain< double > amplitude_gain(double ratio)
The gain corresponding to a linear amplitude ratio (20*log10(ratio)).
Definition decibel.hpp:512
auto add_powers(const level< Reference, Rep1, Precision1 > &a, const level< Reference, Rep2, Precision2 > &b)
Combines the powers of two uncorrelated signals.
Definition decibel.hpp:535
gain< double > power_gain(double ratio)
The gain corresponding to a linear power ratio (10*log10(ratio)).
Definition decibel.hpp:506
constexpr auto operator+(const level< Reference, Rep1, Precision1 > &l, const quantity< decibel_unit, Rep2, Precision2 > &g)
Applies a gain to a level.
Definition decibel.hpp:353
double amplitude_ratio(const quantity< decibel_unit, Rep, Precision > &g)
The linear amplitude ratio a gain represents (10^(dB/20)).
Definition decibel.hpp:501
constexpr auto operator-(const level< Reference, Rep1, Precision1 > &l, const quantity< decibel_unit, Rep2, Precision2 > &g)
Applies a loss to a level.
Definition decibel.hpp:369
gain< int64_t > decibels
Gain with 1 dB precision.
Definition decibel.hpp:83
constexpr auto operator<=>(const level< Reference, Rep1, Precision1 > &a, const level< Reference, Rep2, Precision2 > &b)
Definition decibel.hpp:394
constexpr bool is_field_reference_v
True if the reference measures a field quantity (20*log10).
Definition decibel.hpp:169
ToLevel to_level(const quantity< Unit, Rep, Precision > &q)
Converts a linear quantity to a level.
Definition decibel.hpp:462
constexpr bool is_field_reference_v< Reference >
Definition decibel.hpp:172
constexpr ToLevel ceil(const level< Reference, Rep, Precision > &l)
Converts a level to the target type, rounding toward positive infinity.
Definition decibel.hpp:329
constexpr bool is_level_v
Definition decibel.hpp:192
constexpr bool is_power_reference_v< Reference >
Definition decibel.hpp:165
constexpr bool treat_as_inexact_v
Definition electro.hpp:162
gain< int64_t, std::centi > centidecibels
Gain with 0.01 dB precision.
Definition decibel.hpp:85
constexpr ToLevel round(const level< Reference, Rep, Precision > &l)
Converts a level to the target type, rounding to nearest (ties to even).
Definition decibel.hpp:336
constexpr bool is_power_reference_v
True if the reference measures a power quantity (10*log10).
Definition decibel.hpp:162
double power_ratio(const quantity< decibel_unit, Rep, Precision > &g)
The linear power ratio a gain represents (10^(dB/10)).
Definition decibel.hpp:487
constexpr ToLevel level_cast(const level< Reference, Rep, Precision > &l)
Converts a level to a different precision or representation.
Definition decibel.hpp:313
constexpr ToLevel floor(const level< Reference, Rep, Precision > &l)
Converts a level to the target type, rounding toward negative infinity.
Definition decibel.hpp:322
std::string to_string(const quantity< decibel_unit, Rep, Precision > &g)
Converts a gain to a string, e.g.
Definition decibel.hpp:657
constexpr bool operator==(const level< Reference, Rep1, Precision1 > &a, const level< Reference, Rep2, Precision2 > &b)
Definition decibel.hpp:388
A dimensionless logarithmic ratio, measured in decibels.
Definition decibel.hpp:74
static constexpr const char * symbol
Definition decibel.hpp:75
Trait to detect level specializations.
Definition decibel.hpp:186
Reference of 1 µV, for dBµV.
Definition decibel.hpp:136
static constexpr const char * symbol
Definition decibel.hpp:140
static constexpr int factor
Definition decibel.hpp:139
Reference of 1 mV, for dBmV.
Definition decibel.hpp:128
static constexpr int factor
Definition decibel.hpp:131
static constexpr const char * symbol
Definition decibel.hpp:132
Reference of 1 mW, for dBm.
Definition decibel.hpp:104
static constexpr int factor
Definition decibel.hpp:107
static constexpr const char * symbol
Definition decibel.hpp:108
Trait giving the display suffix for a (unit, precision) pair.
Definition electro.hpp:1170
Reference of 1 V, for dBV.
Definition decibel.hpp:120
static constexpr const char * symbol
Definition decibel.hpp:124
std::ratio< 1 > precision
Definition decibel.hpp:122
static constexpr int factor
Definition decibel.hpp:123
Voltage, measured in volts.
Definition electro.hpp:215
Reference of 1 W, for dBW.
Definition decibel.hpp:112
static constexpr const char * symbol
Definition decibel.hpp:116
static constexpr int factor
Definition decibel.hpp:115
std::ratio< 1 > precision
Definition decibel.hpp:114
Power, measured in watts.
Definition electro.hpp:230