diff --git a/wpimath/src/main/native/include/frc/fmt/Units.h b/wpimath/src/main/native/include/frc/fmt/Units.h index ccae610cfb..90633bcbc9 100644 --- a/wpimath/src/main/native/include/frc/fmt/Units.h +++ b/wpimath/src/main/native/include/frc/fmt/Units.h @@ -9,22 +9,8 @@ #include "units/base.h" template class NonLinearScale> -struct fmt::formatter> { - char presentation = 'f'; - - constexpr auto parse(fmt::format_parse_context& ctx) { - auto it = ctx.begin(), end = ctx.end(); - if (it != end && (*it == 'f' || *it == 'e')) { - presentation = *it++; - } - - if (it != end && *it != '}') { - throw fmt::format_error("invalid format"); - } - - return it; - } - +struct fmt::formatter> + : fmt::formatter { template auto format(const units::unit_t& obj, FormatContext& ctx) { @@ -34,7 +20,8 @@ struct fmt::formatter> { auto out = ctx.out(); - out = fmt::format_to(out, "{}", units::convert(obj())); + out = fmt::formatter::format( + units::convert(obj()), ctx); if constexpr (units::traits::unit_traits< Units>::base_unit_type::meter_ratio::num != 0) { diff --git a/wpimath/src/main/native/include/units/base.h b/wpimath/src/main/native/include/units/base.h index 579ec88ed1..7c6244eb02 100644 --- a/wpimath/src/main/native/include/units/base.h +++ b/wpimath/src/main/native/include/units/base.h @@ -74,36 +74,40 @@ #include #include -#if !defined(UNIT_LIB_DISABLE_IOSTREAM) +#if defined(UNIT_LIB_ENABLE_IOSTREAM) #include - #include #include - - //------------------------------ - // STRING FORMATTER - //------------------------------ - - namespace units - { - namespace detail - { - template std::string to_string(const T& t) - { - std::string str{ std::to_string(t) }; - int offset{ 1 }; - - // remove trailing decimal points for integer value units. Locale aware! - struct lconv * lc; - lc = localeconv(); - char decimalPoint = *lc->decimal_point; - if (str.find_last_not_of('0') == str.find(decimalPoint)) { offset = 0; } - str.erase(str.find_last_not_of('0') + offset, std::string::npos); - return str; - } - } - } + #include +#else + #include + #include + #include #endif +//------------------------------ +// STRING FORMATTER +//------------------------------ + +namespace units +{ + namespace detail + { + template std::string to_string(const T& t) + { + std::string str{ std::to_string(t) }; + int offset{ 1 }; + + // remove trailing decimal points for integer value units. Locale aware! + struct lconv * lc; + lc = localeconv(); + char decimalPoint = *lc->decimal_point; + if (str.find_last_not_of('0') == str.find(decimalPoint)) { offset = 0; } + str.erase(str.find_last_not_of('0') + offset, std::string::npos); + return str; + } + } +} + namespace units { template inline constexpr const char* name(const T&); @@ -172,10 +176,33 @@ namespace units * @param namespaceName namespace in which the new units will be encapsulated. * @param nameSingular singular version of the unit name, e.g. 'meter' * @param abbrev - abbreviated unit name, e.g. 'm' - * @note When UNIT_LIB_DISABLE_IOSTREAM is defined, the macro does not generate any code + * @note When UNIT_LIB_ENABLE_IOSTREAM isn't defined, the macro does not generate any code */ -#if defined(UNIT_LIB_DISABLE_IOSTREAM) - #define UNIT_ADD_IO(namespaceName, nameSingular, abbrev) +#if !defined(UNIT_LIB_ENABLE_IOSTREAM) + #define UNIT_ADD_IO(namespaceName, nameSingular, abbrev)\ + }\ + template <>\ + struct fmt::formatter \ + : fmt::formatter \ + {\ + template \ + auto format(const units::namespaceName::nameSingular ## _t& obj,\ + FormatContext& ctx) -> decltype(ctx.out()) \ + {\ + auto out = ctx.out();\ + out = fmt::formatter::format(obj(), ctx);\ + return fmt::format_to(out, " " #abbrev);\ + }\ + };\ + namespace units\ + {\ + namespace namespaceName\ + {\ + inline std::string to_string(const nameSingular ## _t& obj)\ + {\ + return units::detail::to_string(obj()) + std::string(" "#abbrev);\ + }\ + } #else #define UNIT_ADD_IO(namespaceName, nameSingular, abbrev)\ namespace namespaceName\ @@ -2180,7 +2207,7 @@ namespace units return UnitType(value); } -#if !defined(UNIT_LIB_DISABLE_IOSTREAM) +#if defined(UNIT_LIB_ENABLE_IOSTREAM) template class NonLinearScale> inline std::ostream& operator<<(std::ostream& os, const unit_t& obj) noexcept { @@ -2815,11 +2842,31 @@ namespace units namespace dimensionless { typedef unit_t dB_t; -#if !defined(UNIT_LIB_DISABLE_IOSTREAM) +#if defined(UNIT_LIB_ENABLE_IOSTREAM) inline std::ostream& operator<<(std::ostream& os, const dB_t& obj) { os << obj() << " dB"; return os; } -#endif typedef dB_t dBi_t; } +#else +} +} +template <> +struct fmt::formatter : fmt::formatter +{ + template + auto format(const units::dimensionless::dB_t& obj, + FormatContext& ctx) -> decltype(ctx.out()) + { + auto out = ctx.out(); + out = fmt::formatter::format(obj(), ctx); + return fmt::format_to(out, " dB"); + } +}; + +namespace units { +namespace dimensionless { + typedef dB_t dBi_t; + } +#endif //------------------------------ // DECIBEL ARITHMETIC @@ -3365,3 +3412,5 @@ namespace units namespace units::literals {} using namespace units::literals; #endif // UNIT_HAS_LITERAL_SUPPORT + +#include "frc/fmt/Units.h" diff --git a/wpimath/src/test/native/cpp/FormatterTest.cpp b/wpimath/src/test/native/cpp/FormatterTest.cpp index 6828b7d7a0..644ebef44f 100644 --- a/wpimath/src/test/native/cpp/FormatterTest.cpp +++ b/wpimath/src/test/native/cpp/FormatterTest.cpp @@ -20,5 +20,5 @@ TEST(FormatterTest, Eigen) { } TEST(FormatterTest, Units) { - EXPECT_EQ("4 m s^-1", fmt::format("{}", 4_mps)); + EXPECT_EQ("4 mps", fmt::format("{}", 4_mps)); } diff --git a/wpimath/src/test/native/cpp/UnitsTest.cpp b/wpimath/src/test/native/cpp/UnitsTest.cpp index 6e9e7119f2..2117cb8f9f 100644 --- a/wpimath/src/test/native/cpp/UnitsTest.cpp +++ b/wpimath/src/test/native/cpp/UnitsTest.cpp @@ -1337,7 +1337,7 @@ TEST_F(UnitContainer, convertMethod) { EXPECT_NEAR(9.84252, test, 5.0e-6); } -#ifndef UNIT_LIB_DISABLE_IOSTREAM +#ifdef UNIT_LIB_ENABLE_IOSTREAM TEST_F(UnitContainer, cout) { testing::internal::CaptureStdout(); std::cout << degree_t(349.87); @@ -1418,6 +1418,88 @@ TEST_F(UnitContainer, cout) { EXPECT_STREQ("5.670367e-08 kg s^-3 K^-4", output.c_str()); #endif } +#endif + +TEST_F(UnitContainer, fmtlib) { + testing::internal::CaptureStdout(); + fmt::print("{}", degree_t(349.87)); + std::string output = testing::internal::GetCapturedStdout(); + EXPECT_STREQ("349.87 deg", output.c_str()); + + testing::internal::CaptureStdout(); + fmt::print("{}", meter_t(1.0)); + output = testing::internal::GetCapturedStdout(); + EXPECT_STREQ("1 m", output.c_str()); + + testing::internal::CaptureStdout(); + fmt::print("{}", dB_t(31.0)); + output = testing::internal::GetCapturedStdout(); + EXPECT_STREQ("31 dB", output.c_str()); + + testing::internal::CaptureStdout(); + fmt::print("{}", volt_t(21.79)); + output = testing::internal::GetCapturedStdout(); + EXPECT_STREQ("21.79 V", output.c_str()); + + testing::internal::CaptureStdout(); + fmt::print("{}", dBW_t(12.0)); + output = testing::internal::GetCapturedStdout(); + EXPECT_STREQ("12 dBW", output.c_str()); + + testing::internal::CaptureStdout(); + fmt::print("{}", dBm_t(120.0)); + output = testing::internal::GetCapturedStdout(); + EXPECT_STREQ("120 dBm", output.c_str()); + + testing::internal::CaptureStdout(); + fmt::print("{}", miles_per_hour_t(72.1)); + output = testing::internal::GetCapturedStdout(); + EXPECT_STREQ("72.1 mph", output.c_str()); + + // undefined unit + testing::internal::CaptureStdout(); + fmt::print("{}", units::math::cpow<4>(meter_t(2))); + output = testing::internal::GetCapturedStdout(); + EXPECT_STREQ("16 m^4", output.c_str()); + + testing::internal::CaptureStdout(); + fmt::print("{}", units::math::cpow<3>(foot_t(2))); + output = testing::internal::GetCapturedStdout(); + EXPECT_STREQ("8 cu_ft", output.c_str()); + + testing::internal::CaptureStdout(); + fmt::print("{:.9}", units::math::cpow<4>(foot_t(2))); + output = testing::internal::GetCapturedStdout(); + EXPECT_STREQ("0.138095597 m^4", output.c_str()); + + // constants + testing::internal::CaptureStdout(); + fmt::print("{:.8}", constants::k_B); + output = testing::internal::GetCapturedStdout(); +#if defined(_MSC_VER) && (_MSC_VER <= 1800) + EXPECT_STREQ("1.3806485e-023 m^2 kg s^-2 K^-1", output.c_str()); +#else + EXPECT_STREQ("1.3806485e-23 m^2 kg s^-2 K^-1", output.c_str()); +#endif + + testing::internal::CaptureStdout(); + fmt::print("{:.9}", constants::mu_B); + output = testing::internal::GetCapturedStdout(); +#if defined(_MSC_VER) && (_MSC_VER <= 1800) + EXPECT_STREQ("9.27400999e-024 m^2 A", output.c_str()); +#else + EXPECT_STREQ("9.27400999e-24 m^2 A", output.c_str()); +#endif + + testing::internal::CaptureStdout(); + fmt::print("{:.7}", constants::sigma); + output = testing::internal::GetCapturedStdout(); +#if defined(_MSC_VER) && (_MSC_VER <= 1800) + EXPECT_STREQ("5.670367e-008 kg s^-3 K^-4", output.c_str()); +#else + EXPECT_STREQ("5.670367e-08 kg s^-3 K^-4", output.c_str()); +#endif +} TEST_F(UnitContainer, to_string) { foot_t a(3.5); @@ -1476,7 +1558,6 @@ TEST_F(UnitContainer, nameAndAbbreviation) { EXPECT_STREQ("m", b.abbreviation()); EXPECT_STREQ("meter", b.name()); } -#endif TEST_F(UnitContainer, negative) { meter_t a(5.3);