Type-safe frequency handling, modeled after std::chrono::duration. Include <quanta/frequency>.
Features
- Type-safe frequencies with distinct types for different precisions
- Standard SI units: millihertz, hertz, kilohertz, megahertz, gigahertz, terahertz
- Integer and floating-point representations for exact or fractional precision
- Automatic conversions between precisions
- Lossless implicit conversions (lossy conversions require explicit casts)
- User-defined literals for concise notation
- **
std::format support** (when available)
- Fully constexpr - all operations can be evaluated at compile time
Usage
hertz audio_sample_rate{44100};
auto f1 = 1000_Hz;
auto f2 = 80_kHz;
auto f3 = 2400_MHz;
auto f4 = 5_GHz;
auto sum = 1000_Hz + 500_Hz;
auto diff = 5_GHz - 2400_MHz;
auto ratio = 1000_Hz / 100_Hz;
}
std::string raw = std::format("{}", reading);
std::string disp = std::format("{:.1f}", reading);
std::string hz = std::format("{:b.1f}", reading);
std::string sc = std::format("{:s}", 2400_MHz);
Frequency types and arithmetic, modeled after std::chrono.
frequency< int64_t > hertz
Frequency with 1 Hz precision.
frequency< int64_t, std::kilo > kilohertz
Frequency with 1000 Hz (kilohertz) precision.
frequency< int64_t, std::mega > megahertz
Frequency with 1,000,000 Hz (megahertz) precision.
frequency< int64_t, std::milli > millihertz
Frequency with 0.001 Hz (millihertz) precision.
frequency< int64_t, std::giga > gigahertz
Frequency with 1,000,000,000 Hz (gigahertz) precision.
std::string to_string(const quantity< decibel_unit, Rep, Precision > &g)
Converts a gain to a string, e.g.
Frequency types and utilities.
constexpr scaled_t scaled
Selects the scaled form in to_string().
Frequency Types
| Type | Unit | Precision |
millihertz | mHz | 0.001 Hz |
hertz | Hz | 1 Hz |
kilohertz | kHz | 1,000 Hz |
megahertz | MHz | 1,000,000 Hz |
gigahertz | GHz | 1,000,000,000 Hz |
terahertz | THz | 1,000,000,000,000 Hz |
All types use int64_t representation by default. For fractional precision, use frequency<double, Precision> (see Floating-Point Frequencies section).
Literals
1000_mHz
1000_Hz
80_kHz
2400_MHz
5_GHz
1_THz
Formatting
to_string() and std::format show a frequency in its own unit: {} prints the exact stored count followed by the precision's SI-prefixed unit, and a format spec such as {:.1f} changes only how the number is rendered (as a double), never the unit.
std::format(
"{:>8.1f}",
hertz(50));
To show a different unit, start the spec with a single marker letter; the rest of the spec is the normal std::format spec for a double:
| Marker | Unit | Example |
| *(none)* | the type's own unit | std::format("{:.1f}", millihertz(1500)) → "1500.0mHz" |
b | base unit (hertz) | std::format("{:b.1f}", millihertz(1500)) → "1.5Hz" |
s | scaled: the largest SI prefix (fHz … THz) in which the magnitude is at least 1, else fHz; zero and non-finite values in Hz | std::format("{:s}", megahertz(2400)) → "2.4GHz" |
std::format(
"{:s}",
hertz(-2500));
std::format(
"{:s*>8.2f}",
hertz(1500));
Notes:
- A spec that starts with
b or s is always a marker, so those letters cannot be used as the fill character at the start of a spec ({:s>8} is the s marker followed by >8).
- Values are converted to
double for the markers, so they work with integer representations too.
- With no spec (
{} on a floating-point frequency, {:b}, {:s}, and every to_string), values are rendered in the shortest fixed notation that round-trips: frequency<double>(1e6) → "1000000Hz", never "1e+06Hz".
- A precision with no SI prefix (e.g.
frequency<int64_t, std::deci>) has no unit of its own: {} and to_string(f) do not compile for it, and a spec without a marker renders the value in hertz ({:.1f} → "22.5Hz"). The b and s markers and tags work for every precision.
- To show a value in another unit, cast it, e.g.
std::format("{:.3f}", frequency<double, std::mega>(f)).
Period and Wavelength
using namespace std::chrono;
hertz(1000).period<milliseconds>();
megahertz(1).period<duration<double, std::micro>>();
(2400_MHz).wavelength<quanta::spatial::millimeters>();
length< int64_t, std::milli > millimeters
Length with 1 mm precision.
period<D>() accepts only std::chrono::duration types, and wavelength<L>() rejects them. The length types are described in spatial.
Conversion Rules
Conversions follow the same philosophy as std::chrono:
- Implicit conversions are allowed when lossless (e.g.,
kilohertz → hertz)
- Explicit conversions are required when lossy (e.g.,
hertz → kilohertz)
auto khz2 = frequency_cast<kilohertz>(
hertz{2500});
A frequency value with a representation and precision.
floor, ceil, trunc and round give explicit rounding control:
auto f = floor<kilohertz>(
hertz{-1500});
auto t = trunc<kilohertz>(
hertz{-1500});
auto r = round<kilohertz>(
hertz{2500});
Floating-Point Frequencies
The library supports floating-point representations for fractional precision. This is useful for:
- Musical tuning and audio applications (e.g., concert pitch A = 440.0 Hz)
- Scientific measurements with sub-unit precision
- Calculations involving irrational ratios
hertz_d concert_a{440.0};
megahertz_d precise_radio{88.5};
kilohertz_d exact_note{261.626};
hertz_d f1{1000.5};
hertz_d f2{500.25};
hertz_d sum = f1 + f2;
hertz_d float_freq = hertz_d(int_freq);
float_freq = float_freq * 1.5;
hertz_d a4{440.0};
hertz_d a5 = a4.octave_shift(1.0);
hertz_d tritone = a4.octave_shift(0.5);
frequency semitone_shift(T semitones) const
Returns this frequency shifted by a number of semitones.
Important considerations:
- Floating-point frequencies allow fractional values but sacrifice exact arithmetic
- Implicit conversions between integer and floating-point frequencies follow the same lossless/lossy rules as precision conversions
- Integer types are preferred when exact values and modulo operations are needed
- Floating-point types are preferred for calculations involving division, musical intervals, or measurements with fractional precision