Skip to content

Commit aabf807

Browse files
authored
Merge pull request #2 from cleishm/feat/format-spec-rendering
Generic to_string/formatters with format-spec rendering in hertz (1.2.0)
2 parents b1b8f32 + 139cf24 commit aabf807

7 files changed

Lines changed: 134 additions & 62 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ if(ESP_PLATFORM)
77
endif()
88

99
project(frequency
10-
VERSION 1.1.2
10+
VERSION 1.2.0
1111
DESCRIPTION "Type-safe frequency handling library modeled after std::chrono"
1212
HOMEPAGE_URL "https://github.com/cleishm/frequency-cpp"
1313
LANGUAGES CXX

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ if (kilohertz{1} == hertz{1000}) {
8888

8989
// String conversion
9090
std::string s = to_string(kilohertz(80)); // "80kHz"
91+
92+
// Formatting: a floating-point format spec renders in hertz, while the
93+
// default renders the exact stored value.
94+
millihertz reading{1500};
95+
std::string disp = std::format("{:.1f}", reading); // "1.5Hz"
96+
std::string raw = std::format("{}", reading); // "1500mHz" (exact stored value)
9197
```
9298
9399
## Frequency Types

docs/Doxyfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
DOXYFILE_ENCODING = UTF-8
88
PROJECT_NAME = "frequency"
9-
PROJECT_NUMBER = "1.0.0"
9+
PROJECT_NUMBER = "1.2.0"
1010
PROJECT_BRIEF = "Type-safe frequency handling library modeled after std::chrono"
1111
PROJECT_LOGO =
1212
OUTPUT_DIRECTORY = .

idf_component.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version: "1.1.3"
1+
version: "1.2.0"
22
description: "Type-safe frequency handling library modeled after std::chrono"
33
url: "https://github.com/cleishm/frequency-cpp"
44
repository: "https://github.com/cleishm/frequency-cpp.git"

include/frequency/frequency.hpp

Lines changed: 100 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,28 +1099,75 @@ using terahertz = frequency<int64_t, std::tera>;
10991099

11001100
/** @} */ // end of FrequencyTypes group
11011101

1102-
inline std::string to_string(millihertz f) {
1103-
return std::to_string(f.count()) + "mHz";
1104-
}
1105-
1106-
inline std::string to_string(hertz f) {
1107-
return std::to_string(f.count()) + "Hz";
1108-
}
1109-
1110-
inline std::string to_string(kilohertz f) {
1111-
return std::to_string(f.count()) + "kHz";
1112-
}
1113-
1114-
inline std::string to_string(megahertz f) {
1115-
return std::to_string(f.count()) + "MHz";
1116-
}
1117-
1118-
inline std::string to_string(gigahertz f) {
1119-
return std::to_string(f.count()) + "GHz";
1102+
/** @cond INTERNAL */
1103+
// SI prefix for a hertz-per-count ratio; nullptr when unmapped.
1104+
template<typename Ratio>
1105+
struct _si_prefix {
1106+
static constexpr const char* value = nullptr;
1107+
};
1108+
template<>
1109+
struct _si_prefix<std::femto> {
1110+
static constexpr const char* value = "f";
1111+
};
1112+
template<>
1113+
struct _si_prefix<std::pico> {
1114+
static constexpr const char* value = "p";
1115+
};
1116+
template<>
1117+
struct _si_prefix<std::nano> {
1118+
static constexpr const char* value = "n";
1119+
};
1120+
template<>
1121+
struct _si_prefix<std::micro> {
1122+
static constexpr const char* value = "µ";
1123+
};
1124+
template<>
1125+
struct _si_prefix<std::milli> {
1126+
static constexpr const char* value = "m";
1127+
};
1128+
template<>
1129+
struct _si_prefix<std::ratio<1>> {
1130+
static constexpr const char* value = "";
1131+
};
1132+
template<>
1133+
struct _si_prefix<std::kilo> {
1134+
static constexpr const char* value = "k";
1135+
};
1136+
template<>
1137+
struct _si_prefix<std::mega> {
1138+
static constexpr const char* value = "M";
1139+
};
1140+
template<>
1141+
struct _si_prefix<std::giga> {
1142+
static constexpr const char* value = "G";
1143+
};
1144+
template<>
1145+
struct _si_prefix<std::tera> {
1146+
static constexpr const char* value = "T";
1147+
};
1148+
template<typename Ratio>
1149+
inline constexpr const char* _si_prefix_v = _si_prefix<typename Ratio::type>::value;
1150+
1151+
// Appends a NUL-terminated string to a format output iterator.
1152+
template<typename OutputIt>
1153+
constexpr OutputIt _format_append(OutputIt out, const char* s) {
1154+
for (; *s != '\0'; ++s) {
1155+
*out++ = *s;
1156+
}
1157+
return out;
11201158
}
1159+
/** @endcond */
11211160

1122-
inline std::string to_string(terahertz f) {
1123-
return std::to_string(f.count()) + "THz";
1161+
/**
1162+
* @brief Renders a frequency as its exact count with a precision-qualified
1163+
* unit, e.g. `to_string(kilohertz(433)) == "433kHz"`.
1164+
*/
1165+
template<typename Rep, typename Precision>
1166+
requires(!treat_as_inexact_v<Rep>)
1167+
std::string to_string(const frequency<Rep, Precision>& f) {
1168+
using precision = typename frequency<Rep, Precision>::precision;
1169+
static_assert(_si_prefix_v<precision> != nullptr, "frequency: precision has no SI prefix");
1170+
return std::to_string(f.count()) + _si_prefix_v<precision> + "Hz";
11241171
}
11251172

11261173
} // namespace freq
@@ -1139,46 +1186,41 @@ struct common_type<freq::frequency<Rep1, Precision1>, freq::frequency<Rep2, Prec
11391186
};
11401187

11411188
#if CONFIG_FREQUENCY_STD_FORMAT
1142-
template<>
1143-
struct formatter<freq::millihertz> {
1144-
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
1145-
1146-
auto format(freq::millihertz f, format_context& ctx) const { return std::format_to(ctx.out(), "{}mHz", f.count()); }
1147-
};
1148-
1149-
template<>
1150-
struct formatter<freq::hertz> {
1151-
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
1152-
1153-
auto format(freq::hertz f, format_context& ctx) const { return std::format_to(ctx.out(), "{}Hz", f.count()); }
1154-
};
1155-
1156-
template<>
1157-
struct formatter<freq::kilohertz> {
1158-
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
1159-
1160-
auto format(freq::kilohertz f, format_context& ctx) const { return std::format_to(ctx.out(), "{}kHz", f.count()); }
1161-
};
1162-
1163-
template<>
1164-
struct formatter<freq::megahertz> {
1165-
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
1166-
1167-
auto format(freq::megahertz f, format_context& ctx) const { return std::format_to(ctx.out(), "{}MHz", f.count()); }
1168-
};
1169-
1170-
template<>
1171-
struct formatter<freq::gigahertz> {
1172-
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
1173-
1174-
auto format(freq::gigahertz f, format_context& ctx) const { return std::format_to(ctx.out(), "{}GHz", f.count()); }
1175-
};
1189+
// "{}" prints the exact stored count with a precision-qualified unit
1190+
// (kilohertz(433) -> "433kHz"). A non-empty spec is applied to the value in
1191+
// hertz (as double): std::format("{:.1f}", millihertz(1500)) == "1.5Hz".
1192+
template<typename Rep, typename Precision>
1193+
struct formatter<freq::frequency<Rep, Precision>> {
1194+
private:
1195+
using _precision = typename freq::frequency<Rep, Precision>::precision;
1196+
static constexpr const char* _prefix = freq::_si_prefix_v<_precision>;
1197+
std::formatter<double> _num;
1198+
bool _has_spec = false;
11761199

1177-
template<>
1178-
struct formatter<freq::terahertz> {
1179-
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
1200+
public:
1201+
constexpr auto parse(format_parse_context& ctx) {
1202+
auto it = ctx.begin();
1203+
if (it == ctx.end() || *it == '}') {
1204+
if (_prefix == nullptr) {
1205+
throw format_error("frequency: precision has no SI prefix; use an explicit format spec");
1206+
}
1207+
return it;
1208+
}
1209+
_has_spec = true;
1210+
return _num.parse(ctx);
1211+
}
11801212

1181-
auto format(freq::terahertz f, format_context& ctx) const { return std::format_to(ctx.out(), "{}THz", f.count()); }
1213+
template<typename FormatContext>
1214+
auto format(const freq::frequency<Rep, Precision>& f, FormatContext& ctx) const {
1215+
if (_has_spec) {
1216+
double hz = static_cast<double>(f.count()) * _precision::num / _precision::den;
1217+
auto out = _num.format(hz, ctx);
1218+
return freq::_format_append(out, "Hz");
1219+
}
1220+
auto out = std::format_to(ctx.out(), "{}", f.count());
1221+
out = freq::_format_append(out, _prefix);
1222+
return freq::_format_append(out, "Hz");
1223+
}
11821224
};
11831225
#endif
11841226

tests/frequency_test.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,32 @@ TEST_CASE("to_string", "[frequency][string]") {
248248
REQUIRE(to_string(kilohertz(80)) == "80kHz");
249249
REQUIRE(to_string(megahertz(80)) == "80MHz");
250250
REQUIRE(to_string(gigahertz(2)) == "2GHz");
251+
REQUIRE(to_string(terahertz(5)) == "5THz");
251252
}
252253

254+
#if CONFIG_FREQUENCY_STD_FORMAT
255+
TEST_CASE("std::format frequency", "[frequency][string]") {
256+
// Empty spec prints the exact stored count with a precision-qualified unit.
257+
REQUIRE(std::format("{}", millihertz(1500)) == "1500mHz");
258+
REQUIRE(std::format("{}", hertz(440)) == "440Hz");
259+
REQUIRE(std::format("{}", kilohertz(433)) == "433kHz");
260+
REQUIRE(std::format("{}", megahertz(868)) == "868MHz");
261+
REQUIRE(std::format("{}", gigahertz(2)) == "2GHz");
262+
REQUIRE(std::format("{}", terahertz(5)) == "5THz");
263+
}
264+
265+
TEST_CASE("std::format with spec renders in hertz", "[frequency][string]") {
266+
// A floating-point format spec renders the value in hertz.
267+
REQUIRE(std::format("{:.1f}", millihertz(1500)) == "1.5Hz");
268+
REQUIRE(std::format("{:.2f}", millihertz(1534)) == "1.53Hz");
269+
REQUIRE(std::format("{:.1f}", hertz(440)) == "440.0Hz");
270+
REQUIRE(std::format("{:g}", kilohertz(2)) == "2000Hz");
271+
272+
// Width and alignment pass through to the numeric part.
273+
REQUIRE(std::format("{:7.1f}", millihertz(1500)) == " 1.5Hz");
274+
}
275+
#endif
276+
253277
TEST_CASE("frequency period", "[frequency][period]") {
254278
using namespace std::chrono;
255279

vcpkg-port/vcpkg.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cleishm-frequency-cpp",
3-
"version": "1.0.0",
3+
"version": "1.2.0",
44
"description": "Type-safe frequency handling library modeled after std::chrono",
55
"homepage": "https://github.com/cleishm/frequency-cpp",
66
"license": "MIT",

0 commit comments

Comments
 (0)