diff --git a/CMakeLists.txt b/CMakeLists.txt index 48f01b9..b2cc4e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,28 @@ set_target_properties(fpm-test PROPERTIES CXX_STANDARD 11) target_link_libraries(fpm-test PRIVATE fpm gtest_main) gtest_add_tests(TARGET fpm-test) +add_executable(fpm-test20 + #tests/arithmetic.cpp + #tests/arithmetic_int.cpp + #tests/basic_math.cpp + #tests/constants.cpp + #tests/conversion.cpp + #tests/classification.cpp + #tests/customizations.cpp + #tests/detail.cpp + #tests/input.cpp + #tests/manip.cpp + #tests/nearest.cpp + #tests/output.cpp + #tests/power.cpp + #tests/trigonometry.cpp + tests/formatting.cpp + tests/formatting_wchar.cpp +) +set_target_properties(fpm-test20 PROPERTIES CXX_STANDARD 20) +target_link_libraries(fpm-test20 PRIVATE fpm gtest_main) +gtest_add_tests(TARGET fpm-test20 TEST_SUFFIX .cpp20) + endif() # diff --git a/include/fpm/ios.hpp b/include/fpm/ios.hpp index 69581fb..8f9a374 100644 --- a/include/fpm/ios.hpp +++ b/include/fpm/ios.hpp @@ -737,4 +737,348 @@ std::basic_istream& operator>>(std::basic_istream& } +#if __cplusplus >= 202002L +# include +# ifdef __cpp_lib_format +# include +# include +# include +# include +template +struct std::formatter, CharT> +{ + static_assert( + // You can add `char32_t` but it is not tested. + // `char8_t` and `char16_t` are variable-width and therefore not supported. + // std::format is implemented only for `char` and `wchar_t` anyway + std::is_same::value || std::is_same::value, + "Formatter is implemented only for fixed-width encodings" + ); + + /// Any character used for padding + char paddingChar = ' '; + enum class Alignment : char + { + Left = '<', ///< align left = padding on right + Right = '>', ///< align right = padding on left + Center = '^', ///< align center = padding on both sides, more on left + }; + Alignment alignmentChar = Alignment::Right; + + enum class SignControl : char + { + PositiveSign = '+', ///< Always show sign, '-' or '+' + PositiveSpace = ' ', ///< Always show sign, '-' or ' ' + NegativeOnly = '-' ///< Show only negative sign + }; + SignControl signControl = SignControl::NegativeOnly; + + /// Alternate Form. + /// Always contain decimal point (even if there is no digit behind it). + /// Special behaviour for 'g' and 'G' types. + bool hashOption = false; + /// Zero-padding between sign and numbers. + /// No effect for non-default alignment + bool zeroOption = false; + + std::size_t width = 0; + /// -1 means unchanged (this value cannot be set by the user as negative values are not possible) + std::size_t precision = -1; + + enum class FormatType : char + { + Default = '\0', + + Hex = 'a', + Hex_Upper = 'A', + + Scientific = 'e', + Scientific_Upper = 'E', + + Fixed = 'f', + Fixed_Upper = 'F', ///< Same behaviour as Fixed + + General = 'g', + General_Upper = 'G', + }; + FormatType type = FormatType::Default; + + template + constexpr typename ParseContext::iterator parse(ParseContext& ctx) + { + auto it = ctx.begin(); + + // Alignment, (+padding) + if(it != ctx.end()) + { + bool used = false; + + const auto c = *it; + if(it + 1 != ctx.end()) + { + const auto c1 = *(it + 1); + if( + c1 == static_cast(Alignment::Left) + || c1 == static_cast(Alignment::Right) + || c1 == static_cast(Alignment::Center) + ) + { + paddingChar = c; + alignmentChar = static_cast(c1); + ++it; + ++it; + used = true; + } + } + if( + !used + && ( + c == static_cast(Alignment::Left) + || c == static_cast(Alignment::Right) + || c == static_cast(Alignment::Center) + ) + ) + { + alignmentChar = static_cast(c); + ++it; + } + } + + // Sign Control + if(it != ctx.end()) + { + const auto c = *it; + if( + c == static_cast(SignControl::PositiveSign) + || c == static_cast(SignControl::PositiveSpace) + || c == static_cast(SignControl::NegativeOnly) + ) + { + signControl = static_cast(c); + ++it; + } + } + + // Alternate form + if(it != ctx.end() && *it == '#') + { + hashOption = true; + ++it; + } + + // Zero padding + if(it != ctx.end() && *it == '0') + { + zeroOption = true; + ++it; + } + + // Width + while(it != ctx.end()) + { + const auto c = *it; + if(c >= '0' && c <= '9') + { + width = width * 10 + (c - '0'); + ++it; + continue; + } + if(c == '{') + { + throw std::invalid_argument("Nested width is not supported"); + } + + break; + } + + // Precision + if(it != ctx.end() && *it == '.') + { + ++it; + precision = 0; + if(it != ctx.end()) + { + if(*it == '{') + { + throw std::invalid_argument("Nested precision is not supported"); + } + else while(it != ctx.end()) + { + const auto c = *it; + if(c >= '0' && c <= '9') + { + precision = precision * 10 + (c - '0'); + ++it; + continue; + } + break; + } + } + } + + // No locale-specific behaviour + + // type + if(it != ctx.end()) + { + switch(static_cast(*it)) + { + case FormatType::Default: + case FormatType::Hex: + case FormatType::Hex_Upper: + case FormatType::Scientific: + case FormatType::Scientific_Upper: + case FormatType::Fixed: + case FormatType::Fixed_Upper: + case FormatType::General: + case FormatType::General_Upper: + { + type = static_cast(*it); + ++it; + break; + } + default: + break; + } + } + + if(it != ctx.end() && *it != '}') + { + throw std::format_error("Invalid format - unexpected character '" + std::string(1, *it) + "'"); + } + return it; + } + + template + typename FormatContext::iterator format(const fpm::fixed& value, FormatContext& ctx) const + { + std::basic_ostringstream out; + if(signControl != SignControl::NegativeOnly && value >= decltype(value){0}) + { + if(signControl == SignControl::PositiveSign) + out << '+'; + else if(signControl == SignControl::PositiveSpace) + out << ' '; + else + throw std::format_error("Invalid sign behaviour"); + } + if(precision != static_cast(-1)) + out << std::setprecision(precision); + if(type != FormatType::Default) + { + // Format type itself + switch(type) + { + default: + case FormatType::Default: + break; + case FormatType::Hex: + case FormatType::Hex_Upper: + out << std::hexfloat; + break; + case FormatType::Scientific: + case FormatType::Scientific_Upper: + out << std::scientific; + break; + case FormatType::Fixed: + case FormatType::Fixed_Upper: + out << std::fixed; + break; + case FormatType::General: + case FormatType::General_Upper: + break; + } + // Upper-case versions + switch(type) + { + default: + break; + case FormatType::Hex_Upper: + case FormatType::Scientific_Upper: + case FormatType::Fixed_Upper: + case FormatType::General_Upper: + out << std::uppercase; + break; + } + } + out << value; + + // Padding + if(out.tellp() < width) + { + if(alignmentChar == Alignment::Left) + { + while(out.tellp() < width) + out << paddingChar; + } + else if(alignmentChar == Alignment::Right) + { + if(zeroOption) + { + const auto str = out.str(); + out.clear(); + out.seekp(0, std::ios::beg); + + const bool hasSign = !str.empty() && (str[0] == '+' || str[0] == '-'); + if(hasSign) + out << str[0]; + + // Zero-padding between sign character and the true value + while(out.tellp() < width - str.size() + (hasSign ? 1 : 0)) + out << '0'; + + // Value without sign + if(hasSign) + out << std::basic_string_view(str.data()).substr(1); + else + out << str; + } + else + { + const auto str = out.str(); + out.clear(); + out.seekp(0, std::ios::beg); + + while(out.tellp() < width - str.size()) + out << paddingChar; + out << str; + } + } + else if(alignmentChar == Alignment::Center) + { + const auto str = out.str(); + out.clear(); + out.seekp(0, std::ios::beg); + + if(str.length() < width) + { + const auto padding = width - str.length(); + const auto halfPadding = padding / 2; + + for(std::size_t i = 0; i < halfPadding; ++i) // Padding before the value + out << paddingChar; + + out << str; + + for(std::size_t i = 0; i < halfPadding; ++i) // Padding after the value + out << paddingChar; + if(halfPadding + halfPadding < padding) // This is the longer one (for odd numbers) + out << paddingChar; + } + } + else + throw std::runtime_error("Unknown alignment character"); + } + + // Copy to output + { + const auto str = out.str(); + std::copy(str.begin(), str.end(), ctx.out()); + return ctx.out(); + } + } +}; +# endif +#endif + #endif diff --git a/tests/formatting.cpp b/tests/formatting.cpp new file mode 100644 index 0000000..707a852 --- /dev/null +++ b/tests/formatting.cpp @@ -0,0 +1,264 @@ +#include "common.hpp" +#include + +#if __cplusplus >= 202002L +# include +# ifdef __cpp_lib_format +# include +# include + +template +inline void ExpectFormat( + const std::string_view format, + const fpm::fixed valFixed, + const double valDouble, + const std::string& expectedValue +) +{ + const auto resFixed = std::vformat(format, std::make_format_args(valFixed)); + const auto resDouble = std::vformat(format, std::make_format_args(valDouble)); + EXPECT_EQ(resFixed, expectedValue); + EXPECT_EQ(resDouble, expectedValue); + EXPECT_EQ(resFixed, resDouble); +} + +TEST(formatting, basic) +{ + EXPECT_EQ(std::format("{}", fpm::fixed_16_16{0}), std::string("0")); + EXPECT_EQ(std::format("{0}", fpm::fixed_16_16{0}), std::string("0")); + EXPECT_EQ(std::format("{:+}", fpm::fixed_16_16{0}), std::string("+0")); + EXPECT_EQ(std::format("{0:+}", fpm::fixed_16_16{0}), std::string("+0")); + + EXPECT_EQ(std::format("{}", fpm::fixed_16_16{1}), std::string("1")); + EXPECT_EQ(std::format("{0}", fpm::fixed_16_16{1}), std::string("1")); + EXPECT_EQ(std::format("{:+}", fpm::fixed_16_16{1}), std::string("+1")); + EXPECT_EQ(std::format("{0:+}", fpm::fixed_16_16{1}), std::string("+1")); + + EXPECT_EQ(std::format("{}", fpm::fixed_16_16{42}), std::string("42")); + EXPECT_EQ(std::format("{:+}", fpm::fixed_16_16{42}), std::string("+42")); + + EXPECT_EQ(std::format("{}", fpm::fixed_16_16{123}), std::string("123")); + EXPECT_EQ(std::format("{:+}", fpm::fixed_16_16{123}), std::string("+123")); + + EXPECT_EQ(std::format("{}", fpm::fixed_16_16{123.25}), std::string("123.25")); + EXPECT_EQ(std::format("{:+}", fpm::fixed_16_16{123.25}), std::string("+123.25")); + + EXPECT_EQ(std::format("{}", fpm::fixed_16_16{123.125}), std::string("123.125")); + EXPECT_EQ(std::format("{:+}", fpm::fixed_16_16{123.125}), std::string("+123.125")); + + EXPECT_EQ(std::format("{}", fpm::fixed_16_16{123.0625}), std::string("123.062")); + EXPECT_EQ(std::format("{:+}", fpm::fixed_16_16{123.0625}), std::string("+123.062")); +} + +TEST(formatting, width) +{ + EXPECT_EQ(std::format("{:0}", fpm::fixed_16_16{0}), std::string("0")); + EXPECT_EQ(std::format("{:1}", fpm::fixed_16_16{0}), std::string("0")); + EXPECT_EQ(std::format("{:2}", fpm::fixed_16_16{0}), std::string(" 0")); + EXPECT_EQ(std::format("{:3}", fpm::fixed_16_16{0}), std::string(" 0")); + EXPECT_EQ(std::format("{:4}", fpm::fixed_16_16{0}), std::string(" 0")); + EXPECT_EQ(std::format("{:5}", fpm::fixed_16_16{0}), std::string(" 0")); + EXPECT_EQ(std::format("{:6}", fpm::fixed_16_16{0}), std::string(" 0")); +} + +/*TEST(formatting, width_nested) +{ + for(int precision = 0; precision < 10; precision++) + { + const std::string strFixed = std::format("{:{}}", fpm::fixed_16_16{0}, precision); + const std::string strDouble = std::format("{:{}}", 0.0, precision); + EXPECT_EQ(strFixed, strDouble); + } +}*/ + +TEST(formatting, fill_and_align) +{ + ExpectFormat("{:6}", fpm::fixed_16_16{ 0}, 0.0, " 0"); + ExpectFormat("{:6}", fpm::fixed_16_16{ 1}, 1.0, " 1"); + ExpectFormat("{:6}", fpm::fixed_16_16{ 42}, 42.0, " 42"); + ExpectFormat("{:6}", fpm::fixed_16_16{123}, 123.0, " 123"); + ExpectFormat("{:6}", fpm::fixed_16_16{ -1}, -1.0, " -1"); + ExpectFormat("{:6}", fpm::fixed_16_16{-42}, -42.0, " -42"); + + ExpectFormat("{:*>6}", fpm::fixed_16_16{ 0}, 0.0, "*****0"); + ExpectFormat("{:*>6}", fpm::fixed_16_16{ 1}, 1.0, "*****1"); + ExpectFormat("{:*>6}", fpm::fixed_16_16{ 42}, 42.0, "****42"); + ExpectFormat("{:*>6}", fpm::fixed_16_16{123}, 123.0, "***123"); + ExpectFormat("{:*>6}", fpm::fixed_16_16{ -1}, -1.0, "****-1"); + ExpectFormat("{:*>6}", fpm::fixed_16_16{-42}, -42.0, "***-42"); + + ExpectFormat("{:x>6}", fpm::fixed_16_16{ 0}, 0.0, "xxxxx0"); + ExpectFormat("{:x>6}", fpm::fixed_16_16{ 1}, 1.0, "xxxxx1"); + ExpectFormat("{:x>6}", fpm::fixed_16_16{ 42}, 42.0, "xxxx42"); + ExpectFormat("{:x>6}", fpm::fixed_16_16{123}, 123.0, "xxx123"); + ExpectFormat("{:x>6}", fpm::fixed_16_16{ -1}, -1.0, "xxxx-1"); + ExpectFormat("{:x>6}", fpm::fixed_16_16{-42}, -42.0, "xxx-42"); + + ExpectFormat("{:+>6}", fpm::fixed_16_16{ 0}, 0.0, "+++++0"); + ExpectFormat("{:+>6}", fpm::fixed_16_16{ 1}, 1.0, "+++++1"); + ExpectFormat("{:+>6}", fpm::fixed_16_16{ 42}, 42.0, "++++42"); + ExpectFormat("{:+>6}", fpm::fixed_16_16{123}, 123.0, "+++123"); + ExpectFormat("{:+>6}", fpm::fixed_16_16{ -1}, -1.0, "++++-1"); + ExpectFormat("{:+>6}", fpm::fixed_16_16{-42}, -42.0, "+++-42"); + + ExpectFormat("{:>>6}", fpm::fixed_16_16{ 0}, 0.0, ">>>>>0"); + ExpectFormat("{:>>6}", fpm::fixed_16_16{ 1}, 1.0, ">>>>>1"); + ExpectFormat("{:>>6}", fpm::fixed_16_16{ 42}, 42.0, ">>>>42"); + ExpectFormat("{:>>6}", fpm::fixed_16_16{123}, 123.0, ">>>123"); + ExpectFormat("{:>>6}", fpm::fixed_16_16{ -1}, -1.0, ">>>>-1"); + ExpectFormat("{:>>6}", fpm::fixed_16_16{-42}, -42.0, ">>>-42"); + + ExpectFormat("{:0>6}", fpm::fixed_16_16{ 0}, 0.0, "000000"); + ExpectFormat("{:0>6}", fpm::fixed_16_16{ 1}, 1.0, "000001"); + ExpectFormat("{:0>6}", fpm::fixed_16_16{ 42}, 42.0, "000042"); + ExpectFormat("{:0>6}", fpm::fixed_16_16{123}, 123.0, "000123"); + ExpectFormat("{:0>6}", fpm::fixed_16_16{ -1}, -1.0, "0000-1"); + ExpectFormat("{:0>6}", fpm::fixed_16_16{-42}, -42.0, "000-42"); + + ExpectFormat("{:<6}", fpm::fixed_16_16{ 0}, 0.0, "0 "); + ExpectFormat("{:<6}", fpm::fixed_16_16{ 1}, 1.0, "1 "); + ExpectFormat("{:<6}", fpm::fixed_16_16{ 42}, 42.0, "42 "); + ExpectFormat("{:<6}", fpm::fixed_16_16{123}, 123.0, "123 "); + ExpectFormat("{:<6}", fpm::fixed_16_16{ -1}, -1.0, "-1 "); + ExpectFormat("{:<6}", fpm::fixed_16_16{-42}, -42.0, "-42 "); + + ExpectFormat("{:^6}", fpm::fixed_16_16{ 0}, 0.0, " 0 "); + ExpectFormat("{:^6}", fpm::fixed_16_16{ 1}, 1.0, " 1 "); + ExpectFormat("{:^6}", fpm::fixed_16_16{ 42}, 42.0, " 42 "); + ExpectFormat("{:^6}", fpm::fixed_16_16{123}, 123.0, " 123 "); + ExpectFormat("{:^6}", fpm::fixed_16_16{ -1}, -1.0, " -1 "); + ExpectFormat("{:^6}", fpm::fixed_16_16{-42}, -42.0, " -42 "); + + ExpectFormat("{:^2}", fpm::fixed_16_16{123}, 123.0, "123"); + ExpectFormat("{:^2}", fpm::fixed_16_16{12345}, 12345.0, "12345"); + ExpectFormat("{:^4}", fpm::fixed_16_16{12345}, 12345.0, "12345"); +} + +TEST(formatting, sign) +{ + ExpectFormat("{0:},{0:+},{0:-},{0: }", fpm::fixed_16_16{ 1}, 1.0, "1,+1,1, 1"); + ExpectFormat("{0:},{0:+},{0:-},{0: }", fpm::fixed_16_16{-1}, -1.0, "-1,-1,-1,-1"); +} + +TEST(formatting, zero_padding) +{ + ExpectFormat("{:02}", fpm::fixed_16_16{ 1}, 1.0, "01"); + ExpectFormat("{:02}", fpm::fixed_16_16{-1}, -1.0, "-1"); + + ExpectFormat("{:03}", fpm::fixed_16_16{ 1}, 1.0, "001"); + ExpectFormat("{:03}", fpm::fixed_16_16{-1}, -1.0, "-01"); + + ExpectFormat("{:04}", fpm::fixed_16_16{ 1}, 1.0, "0001"); + ExpectFormat("{:04}", fpm::fixed_16_16{-1}, -1.0, "-001"); + + ExpectFormat("{:06}", fpm::fixed_16_16{ 1}, 1.0, "000001"); + ExpectFormat("{:06}", fpm::fixed_16_16{-1}, -1.0, "-00001"); + + ExpectFormat("{:<02}", fpm::fixed_16_16{ 1}, 1.0, "1 "); + ExpectFormat("{:<02}", fpm::fixed_16_16{-1}, -1.0, "-1"); + + ExpectFormat("{:<06}", fpm::fixed_16_16{ 1}, 1.0, "1 "); + ExpectFormat("{:<06}", fpm::fixed_16_16{-1}, -1.0, "-1 "); +} + +TEST(formatting, precision) +{ + const double dblValue = 2.015625; + const auto fpmValue = fpm::fixed_16_16(2.015625); + + ExpectFormat("{:10f}", fpmValue, dblValue, " 2.015625"); + ExpectFormat("{:.5f}", fpmValue, dblValue, "2.01562"); + ExpectFormat("{:10.5f}", fpmValue, dblValue, " 2.01562"); + ExpectFormat("{:10.6f}", fpmValue, dblValue, " 2.015625"); + ExpectFormat("{:10.3f}", fpmValue, dblValue, " 2.016"); + ExpectFormat("{:10.2f}", fpmValue, dblValue, " 2.02"); +} + +TEST(formatting, precision_pi) +{ + const double dblValue = std::numbers::pi_v; + const auto fpmValue = fpm::fixed_8_24::pi(); // fpm::fixed_16_16 does not have enough precision + + ExpectFormat("{:10f}", fpmValue, dblValue, " 3.141593"); + ExpectFormat("{:.5f}", fpmValue, dblValue, "3.14159"); + + ExpectFormat("{:10.6f}", fpmValue, dblValue, " 3.141593"); + ExpectFormat("{:10.5f}", fpmValue, dblValue, " 3.14159"); + ExpectFormat("{:10.4f}", fpmValue, dblValue, " 3.1416"); + ExpectFormat("{:10.3f}", fpmValue, dblValue, " 3.142"); + ExpectFormat("{:10.2f}", fpmValue, dblValue, " 3.14"); + ExpectFormat("{:10.1f}", fpmValue, dblValue, " 3.1"); + ExpectFormat("{:10.0f}", fpmValue, dblValue, " 3"); +} + +/*TEST(formatting, precision_nested) +{ + const double dblValue = 2.015625; + const auto fpmValue = fpm::fixed_16_16(2.015625); + + for(int precision = 0; precision < 10; precision++) + { + const std::string strFixed = std::format("{:2.{}f}", fpmValue, precision); + const std::string strDouble = std::format("{:2.{}f}", dblValue, precision); + EXPECT_EQ(strFixed, strDouble); + } + + for(int precision = 0; precision < 10; precision++) + { + for(int width = 0; width < precision + 2; width++) + { + const std::string strFixed = std::format("{:{}.{}f}", fpmValue, width, precision); + const std::string strDouble = std::format("{:{}.{}f}", dblValue, width, precision); + EXPECT_EQ(strFixed, strDouble); + } + + for(int width = 0; width < precision + 4; width++) + { + const std::string strFixed = std::format("{:{}.{}f}", fpmValue, width, precision); + const std::string strDouble = std::format("{:{}.{}f}", dblValue, width, precision); + EXPECT_EQ(strFixed, strDouble); + } + } +}*/ + +TEST(formatting, types) +{ + const double dblValue = 2.015625; + const auto fpmValue = fpm::fixed_16_16(2.015625); + + // Fixed + { + ExpectFormat("{:10.6f}", fpmValue, dblValue, " 2.015625"); + ExpectFormat("{:10.6F}", fpmValue, dblValue, " 2.015625"); + + ExpectFormat("{:10.3f}", fpmValue, dblValue, " 2.016"); + ExpectFormat("{:10.3F}", fpmValue, dblValue, " 2.016"); + } + // Hex + { + // Implementation differs from `double` + // fpm: " 0x1.02p+1" + // double: "1.020000P+1" + //ExpectFormat("{:10.6a}", fpmValue, dblValue, "1.020000p+1"); + //ExpectFormat("{:10.6A}", fpmValue, dblValue, "1.020000P+1"); + } + // Scientific + { + ExpectFormat("{:10.6e}", fpmValue, dblValue, "2.015625e+00"); + ExpectFormat("{:10.6E}", fpmValue, dblValue, "2.015625E+00"); + + ExpectFormat("{:10.3e}", fpmValue, dblValue, " 2.016e+00"); + ExpectFormat("{:10.3E}", fpmValue, dblValue, " 2.016E+00"); + } + // General + { + ExpectFormat("{:10.6g}", fpmValue, dblValue, " 2.01562"); + ExpectFormat("{:10.6G}", fpmValue, dblValue, " 2.01562"); + + ExpectFormat("{:10.3g}", fpmValue, dblValue, " 2.02"); + ExpectFormat("{:10.3G}", fpmValue, dblValue, " 2.02"); + } +} + +#endif +#endif diff --git a/tests/formatting_wchar.cpp b/tests/formatting_wchar.cpp new file mode 100644 index 0000000..0acb2c4 --- /dev/null +++ b/tests/formatting_wchar.cpp @@ -0,0 +1,263 @@ +#include "common.hpp" +#include + +#if __cplusplus >= 202002L +# include +# ifdef __cpp_lib_format +# include +# include + +template +inline void ExpectFormat( + const std::wstring_view format, + const fpm::fixed valFixed, + const double valDouble, + const std::wstring& expectedValue +) +{ + const auto resFixed = std::vformat(format, std::make_wformat_args(valFixed)); + const auto resDouble = std::vformat(format, std::make_wformat_args(valDouble)); + EXPECT_EQ(resFixed, expectedValue); + EXPECT_EQ(resDouble, expectedValue); + EXPECT_EQ(resFixed, resDouble); +} + +TEST(formatting_wchar, basic) +{ + EXPECT_EQ(std::format(L"{}", fpm::fixed_16_16{0}), std::wstring(L"0")); + EXPECT_EQ(std::format(L"{0}", fpm::fixed_16_16{0}), std::wstring(L"0")); + EXPECT_EQ(std::format(L"{:+}", fpm::fixed_16_16{0}), std::wstring(L"+0")); + EXPECT_EQ(std::format(L"{0:+}", fpm::fixed_16_16{0}), std::wstring(L"+0")); + + EXPECT_EQ(std::format(L"{}", fpm::fixed_16_16{1}), std::wstring(L"1")); + EXPECT_EQ(std::format(L"{0}", fpm::fixed_16_16{1}), std::wstring(L"1")); + EXPECT_EQ(std::format(L"{:+}", fpm::fixed_16_16{1}), std::wstring(L"+1")); + EXPECT_EQ(std::format(L"{0:+}", fpm::fixed_16_16{1}), std::wstring(L"+1")); + + EXPECT_EQ(std::format(L"{}", fpm::fixed_16_16{42}), std::wstring(L"42")); + EXPECT_EQ(std::format(L"{:+}", fpm::fixed_16_16{42}), std::wstring(L"+42")); + + EXPECT_EQ(std::format(L"{}", fpm::fixed_16_16{123}), std::wstring(L"123")); + EXPECT_EQ(std::format(L"{:+}", fpm::fixed_16_16{123}), std::wstring(L"+123")); + + EXPECT_EQ(std::format(L"{}", fpm::fixed_16_16{123.25}), std::wstring(L"123.25")); + EXPECT_EQ(std::format(L"{:+}", fpm::fixed_16_16{123.25}), std::wstring(L"+123.25")); + + EXPECT_EQ(std::format(L"{}", fpm::fixed_16_16{123.125}), std::wstring(L"123.125")); + EXPECT_EQ(std::format(L"{:+}", fpm::fixed_16_16{123.125}), std::wstring(L"+123.125")); + + EXPECT_EQ(std::format(L"{}", fpm::fixed_16_16{123.0625}), std::wstring(L"123.062")); + EXPECT_EQ(std::format(L"{:+}", fpm::fixed_16_16{123.0625}), std::wstring(L"+123.062")); +} + +TEST(formatting_wchar, width) +{ + EXPECT_EQ(std::format(L"{:0}", fpm::fixed_16_16{0}), std::wstring(L"0")); + EXPECT_EQ(std::format(L"{:1}", fpm::fixed_16_16{0}), std::wstring(L"0")); + EXPECT_EQ(std::format(L"{:2}", fpm::fixed_16_16{0}), std::wstring(L" 0")); + EXPECT_EQ(std::format(L"{:3}", fpm::fixed_16_16{0}), std::wstring(L" 0")); + EXPECT_EQ(std::format(L"{:4}", fpm::fixed_16_16{0}), std::wstring(L" 0")); + EXPECT_EQ(std::format(L"{:5}", fpm::fixed_16_16{0}), std::wstring(L" 0")); + EXPECT_EQ(std::format(L"{:6}", fpm::fixed_16_16{0}), std::wstring(L" 0")); +} + +/*TEST(formatting_wchar, width_nested) +{ + for(int precision = 0; precision < 10; precision++) + { + const std::wstring strFixed = std::format(L"{:{}}", fpm::fixed_16_16{0}, precision); + const std::wstring strDouble = std::format(L"{:{}}", 0.0, precision); + EXPECT_EQ(strFixed, strDouble); + } +}*/ + +TEST(formatting_wchar, fill_and_align) +{ + ExpectFormat(L"{:6}", fpm::fixed_16_16{ 0}, 0.0, L" 0"); + ExpectFormat(L"{:6}", fpm::fixed_16_16{ 1}, 1.0, L" 1"); + ExpectFormat(L"{:6}", fpm::fixed_16_16{ 42}, 42.0, L" 42"); + ExpectFormat(L"{:6}", fpm::fixed_16_16{123}, 123.0, L" 123"); + ExpectFormat(L"{:6}", fpm::fixed_16_16{ -1}, -1.0, L" -1"); + ExpectFormat(L"{:6}", fpm::fixed_16_16{-42}, -42.0, L" -42"); + + ExpectFormat(L"{:*>6}", fpm::fixed_16_16{ 0}, 0.0, L"*****0"); + ExpectFormat(L"{:*>6}", fpm::fixed_16_16{ 1}, 1.0, L"*****1"); + ExpectFormat(L"{:*>6}", fpm::fixed_16_16{ 42}, 42.0, L"****42"); + ExpectFormat(L"{:*>6}", fpm::fixed_16_16{123}, 123.0, L"***123"); + ExpectFormat(L"{:*>6}", fpm::fixed_16_16{ -1}, -1.0, L"****-1"); + ExpectFormat(L"{:*>6}", fpm::fixed_16_16{-42}, -42.0, L"***-42"); + + ExpectFormat(L"{:x>6}", fpm::fixed_16_16{ 0}, 0.0, L"xxxxx0"); + ExpectFormat(L"{:x>6}", fpm::fixed_16_16{ 1}, 1.0, L"xxxxx1"); + ExpectFormat(L"{:x>6}", fpm::fixed_16_16{ 42}, 42.0, L"xxxx42"); + ExpectFormat(L"{:x>6}", fpm::fixed_16_16{123}, 123.0, L"xxx123"); + ExpectFormat(L"{:x>6}", fpm::fixed_16_16{ -1}, -1.0, L"xxxx-1"); + ExpectFormat(L"{:x>6}", fpm::fixed_16_16{-42}, -42.0, L"xxx-42"); + + ExpectFormat(L"{:+>6}", fpm::fixed_16_16{ 0}, 0.0, L"+++++0"); + ExpectFormat(L"{:+>6}", fpm::fixed_16_16{ 1}, 1.0, L"+++++1"); + ExpectFormat(L"{:+>6}", fpm::fixed_16_16{ 42}, 42.0, L"++++42"); + ExpectFormat(L"{:+>6}", fpm::fixed_16_16{123}, 123.0, L"+++123"); + ExpectFormat(L"{:+>6}", fpm::fixed_16_16{ -1}, -1.0, L"++++-1"); + ExpectFormat(L"{:+>6}", fpm::fixed_16_16{-42}, -42.0, L"+++-42"); + + ExpectFormat(L"{:>>6}", fpm::fixed_16_16{ 0}, 0.0, L">>>>>0"); + ExpectFormat(L"{:>>6}", fpm::fixed_16_16{ 1}, 1.0, L">>>>>1"); + ExpectFormat(L"{:>>6}", fpm::fixed_16_16{ 42}, 42.0, L">>>>42"); + ExpectFormat(L"{:>>6}", fpm::fixed_16_16{123}, 123.0, L">>>123"); + ExpectFormat(L"{:>>6}", fpm::fixed_16_16{ -1}, -1.0, L">>>>-1"); + ExpectFormat(L"{:>>6}", fpm::fixed_16_16{-42}, -42.0, L">>>-42"); + + ExpectFormat(L"{:0>6}", fpm::fixed_16_16{ 0}, 0.0, L"000000"); + ExpectFormat(L"{:0>6}", fpm::fixed_16_16{ 1}, 1.0, L"000001"); + ExpectFormat(L"{:0>6}", fpm::fixed_16_16{ 42}, 42.0, L"000042"); + ExpectFormat(L"{:0>6}", fpm::fixed_16_16{123}, 123.0, L"000123"); + ExpectFormat(L"{:0>6}", fpm::fixed_16_16{ -1}, -1.0, L"0000-1"); + ExpectFormat(L"{:0>6}", fpm::fixed_16_16{-42}, -42.0, L"000-42"); + + ExpectFormat(L"{:<6}", fpm::fixed_16_16{ 0}, 0.0, L"0 "); + ExpectFormat(L"{:<6}", fpm::fixed_16_16{ 1}, 1.0, L"1 "); + ExpectFormat(L"{:<6}", fpm::fixed_16_16{ 42}, 42.0, L"42 "); + ExpectFormat(L"{:<6}", fpm::fixed_16_16{123}, 123.0, L"123 "); + ExpectFormat(L"{:<6}", fpm::fixed_16_16{ -1}, -1.0, L"-1 "); + ExpectFormat(L"{:<6}", fpm::fixed_16_16{-42}, -42.0, L"-42 "); + + ExpectFormat(L"{:^6}", fpm::fixed_16_16{ 0}, 0.0, L" 0 "); + ExpectFormat(L"{:^6}", fpm::fixed_16_16{ 1}, 1.0, L" 1 "); + ExpectFormat(L"{:^6}", fpm::fixed_16_16{ 42}, 42.0, L" 42 "); + ExpectFormat(L"{:^6}", fpm::fixed_16_16{123}, 123.0, L" 123 "); + ExpectFormat(L"{:^6}", fpm::fixed_16_16{ -1}, -1.0, L" -1 "); + ExpectFormat(L"{:^6}", fpm::fixed_16_16{-42}, -42.0, L" -42 "); + + ExpectFormat(L"{:^2}", fpm::fixed_16_16{123}, 123.0, L"123"); + ExpectFormat(L"{:^2}", fpm::fixed_16_16{12345}, 12345.0, L"12345"); + ExpectFormat(L"{:^4}", fpm::fixed_16_16{12345}, 12345.0, L"12345"); +} + +TEST(formatting_wchar, sign) +{ + ExpectFormat(L"{0:},{0:+},{0:-},{0: }", fpm::fixed_16_16{ 1}, 1.0, L"1,+1,1, 1"); + ExpectFormat(L"{0:},{0:+},{0:-},{0: }", fpm::fixed_16_16{-1}, -1.0, L"-1,-1,-1,-1"); +} + +TEST(formatting_wchar, zero_padding) +{ + ExpectFormat(L"{:02}", fpm::fixed_16_16{ 1}, 1.0, L"01"); + ExpectFormat(L"{:02}", fpm::fixed_16_16{-1}, -1.0, L"-1"); + + ExpectFormat(L"{:03}", fpm::fixed_16_16{ 1}, 1.0, L"001"); + ExpectFormat(L"{:03}", fpm::fixed_16_16{-1}, -1.0, L"-01"); + + ExpectFormat(L"{:04}", fpm::fixed_16_16{ 1}, 1.0, L"0001"); + ExpectFormat(L"{:04}", fpm::fixed_16_16{-1}, -1.0, L"-001"); + + ExpectFormat(L"{:06}", fpm::fixed_16_16{ 1}, 1.0, L"000001"); + ExpectFormat(L"{:06}", fpm::fixed_16_16{-1}, -1.0, L"-00001"); + + ExpectFormat(L"{:<02}", fpm::fixed_16_16{ 1}, 1.0, L"1 "); + ExpectFormat(L"{:<02}", fpm::fixed_16_16{-1}, -1.0, L"-1"); + + ExpectFormat(L"{:<06}", fpm::fixed_16_16{ 1}, 1.0, L"1 "); + ExpectFormat(L"{:<06}", fpm::fixed_16_16{-1}, -1.0, L"-1 "); +} + +TEST(formatting_wchar, precision) +{ + const double dblValue = 2.015625; + const auto fpmValue = fpm::fixed_16_16(2.015625); + + ExpectFormat(L"{:10f}", fpmValue, dblValue, L" 2.015625"); + ExpectFormat(L"{:.5f}", fpmValue, dblValue, L"2.01562"); + ExpectFormat(L"{:10.5f}", fpmValue, dblValue, L" 2.01562"); + ExpectFormat(L"{:10.6f}", fpmValue, dblValue, L" 2.015625"); + ExpectFormat(L"{:10.3f}", fpmValue, dblValue, L" 2.016"); + ExpectFormat(L"{:10.2f}", fpmValue, dblValue, L" 2.02"); +} +TEST(formatting_wchar, precision_pi) +{ + const double dblValue = std::numbers::pi_v; + const auto fpmValue = fpm::fixed_8_24::pi(); // fpm::fixed_16_16 does not have enough precision + + ExpectFormat(L"{:10f}", fpmValue, dblValue, L" 3.141593"); + ExpectFormat(L"{:.5f}", fpmValue, dblValue, L"3.14159"); + + ExpectFormat(L"{:10.6f}", fpmValue, dblValue, L" 3.141593"); + ExpectFormat(L"{:10.5f}", fpmValue, dblValue, L" 3.14159"); + ExpectFormat(L"{:10.4f}", fpmValue, dblValue, L" 3.1416"); + ExpectFormat(L"{:10.3f}", fpmValue, dblValue, L" 3.142"); + ExpectFormat(L"{:10.2f}", fpmValue, dblValue, L" 3.14"); + ExpectFormat(L"{:10.1f}", fpmValue, dblValue, L" 3.1"); + ExpectFormat(L"{:10.0f}", fpmValue, dblValue, L" 3"); +} + +/*TEST(formatting_wchar, precision_nested) +{ + const double dblValue = 2.015625; + const auto fpmValue = fpm::fixed_16_16(2.015625); + + for(int precision = 0; precision < 10; precision++) + { + const std::wstring strFixed = std::format(L"{:2.{}f}", fpmValue, precision); + const std::wstring strDouble = std::format(L"{:2.{}f}", dblValue, precision); + EXPECT_EQ(strFixed, strDouble); + } + + for(int precision = 0; precision < 10; precision++) + { + for(int width = 0; width < precision + 2; width++) + { + const std::wstring strFixed = std::format(L"{:{}.{}f}", fpmValue, width, precision); + const std::wstring strDouble = std::format(L"{:{}.{}f}", dblValue, width, precision); + EXPECT_EQ(strFixed, strDouble); + } + + for(int width = 0; width < precision + 4; width++) + { + const std::string strFixed = std::format(L"{:{}.{}f}", fpmValue, width, precision); + const std::string strDouble = std::format(L"{:{}.{}f}", dblValue, width, precision); + EXPECT_EQ(strFixed, strDouble); + } + } +}*/ + +TEST(formatting_wchar, types) +{ + const double dblValue = 2.015625; + const auto fpmValue = fpm::fixed_16_16(2.015625); + + // Fixed + { + ExpectFormat(L"{:10.6f}", fpmValue, dblValue, L" 2.015625"); + ExpectFormat(L"{:10.6F}", fpmValue, dblValue, L" 2.015625"); + + ExpectFormat(L"{:10.3f}", fpmValue, dblValue, L" 2.016"); + ExpectFormat(L"{:10.3F}", fpmValue, dblValue, L" 2.016"); + } + // Hex + { + // Implementation differs from `double` + // fpm: " 0x1.02p+1" + // double: "1.020000P+1" + //ExpectFormat(L"{:10.6a}", fpmValue, dblValue, L"1.020000p+1"); + //ExpectFormat(L"{:10.6A}", fpmValue, dblValue, L"1.020000P+1"); + } + // Scientific + { + ExpectFormat(L"{:10.6e}", fpmValue, dblValue, L"2.015625e+00"); + ExpectFormat(L"{:10.6E}", fpmValue, dblValue, L"2.015625E+00"); + + ExpectFormat(L"{:10.3e}", fpmValue, dblValue, L" 2.016e+00"); + ExpectFormat(L"{:10.3E}", fpmValue, dblValue, L" 2.016E+00"); + } + // General + { + ExpectFormat(L"{:10.6g}", fpmValue, dblValue, L" 2.01562"); + ExpectFormat(L"{:10.6G}", fpmValue, dblValue, L" 2.01562"); + + ExpectFormat(L"{:10.3g}", fpmValue, dblValue, L" 2.02"); + ExpectFormat(L"{:10.3G}", fpmValue, dblValue, L" 2.02"); + } +} + +#endif +#endif