|
1 | 1 | #pragma once
|
2 | 2 |
|
| 3 | +#include "fly/fly.hpp" |
3 | 4 | #include "fly/traits/traits.hpp"
|
4 | 5 | #include "fly/types/string/detail/classifier.hpp"
|
5 | 6 | #include "fly/types/string/detail/format_specifier.hpp"
|
@@ -188,8 +189,6 @@ struct Formatter<T, CharType, fly::enable_if<detail::BasicFormatTraits::is_integ
|
188 | 189 | * @param value The value to append.
|
189 | 190 | * @param base The base of the value.
|
190 | 191 | * @param context The context holding the formatting state.
|
191 |
| - * |
192 |
| - * @return The number of base-N digits converted. |
193 | 192 | */
|
194 | 193 | template <typename U, typename FormatContext>
|
195 | 194 | void append_number(U value, int base, FormatContext &context);
|
@@ -237,18 +236,55 @@ struct Formatter<T, CharType, fly::enable_if<std::is_floating_point<T>>>
|
237 | 236 | /**
|
238 | 237 | * Format a single replacement field with the provided floating point value.
|
239 | 238 | *
|
240 |
| - * Currently, major compilers do not support std::to_chars for floating point values. Until they |
241 |
| - * do, this implementation uses an IO stream to format the value. |
| 239 | + * Currently, not all major compilers support std::to_chars for floating point values. If it is |
| 240 | + * supported by the compiler, then std::to_chars is used for the conversion, and the result is |
| 241 | + * further formatted according to the replacement field specification. If it is not supported, |
| 242 | + * an IO stream is used to format the value. |
242 | 243 | *
|
243 | 244 | * @tparam FormatContext The type of the formatting context.
|
244 | 245 | *
|
245 | 246 | * @param value The value to format.
|
246 | 247 | * @param context The context holding the formatting state.
|
247 | 248 | */
|
248 | 249 | template <typename FormatContext>
|
249 |
| - void format(const T &value, FormatContext &context); |
| 250 | + void format(T value, FormatContext &context); |
250 | 251 |
|
251 | 252 | private:
|
| 253 | + using string_type = std::basic_string<CharType>; |
| 254 | + |
| 255 | +#if defined(FLY_COMPILER_SUPPORTS_FP_CHARCONV) |
| 256 | + |
| 257 | + /** |
| 258 | + * Structure to hold the information needed to fully format a floating point value as a string. |
| 259 | + */ |
| 260 | + struct FloatConversionResult |
| 261 | + { |
| 262 | + std::string_view m_digits; |
| 263 | + std::string_view m_exponent; |
| 264 | + bool m_append_decimal {false}; |
| 265 | + std::size_t m_zeroes_to_append {0}; |
| 266 | + }; |
| 267 | + |
| 268 | + /** |
| 269 | + * Convert a floating point value to a string. |
| 270 | + * |
| 271 | + * Internally, std::to_chars is used for the conversion, which does not handle all floating |
| 272 | + * point formatting options, such as alternate form. So rather than creating a fully-formatted |
| 273 | + * string, this method returns a structure holding the information needed to format the value as |
| 274 | + * a string. |
| 275 | + * |
| 276 | + * @param value The value to convert. |
| 277 | + * @param precision The floating point precision to use. |
| 278 | + * |
| 279 | + * @return A structure holding the information needed to fully format the value as a string. |
| 280 | + */ |
| 281 | + FloatConversionResult convert_value(T value, int precision); |
| 282 | + |
| 283 | +#endif // FLY_COMPILER_SUPPORTS_FP_CHARCONV |
| 284 | + |
| 285 | + static constexpr const auto s_plus_sign = FLY_CHR(CharType, '+'); |
| 286 | + static constexpr const auto s_minus_sign = FLY_CHR(CharType, '-'); |
| 287 | + static constexpr const auto s_space = FLY_CHR(CharType, ' '); |
252 | 288 | static constexpr const auto s_zero = FLY_CHR(CharType, '0');
|
253 | 289 |
|
254 | 290 | FormatSpecifier m_specifier {};
|
@@ -311,7 +347,7 @@ void Formatter<T, CharType, fly::enable_if<detail::is_like_supported_string<T>>>
|
311 | 347 | const std::size_t padding_size = std::max(value_size, min_width) - value_size;
|
312 | 348 | const auto padding_char = m_specifier.m_fill.value_or(s_space);
|
313 | 349 |
|
314 |
| - auto append_padding = [&context, padding_char](std::size_t count) mutable |
| 350 | + auto append_padding = [&context, padding_char](std::size_t count) |
315 | 351 | {
|
316 | 352 | for (std::size_t i = 0; i < count; ++i)
|
317 | 353 | {
|
@@ -517,7 +553,7 @@ void Formatter<T, CharType, fly::enable_if<detail::BasicFormatTraits::is_integra
|
517 | 553 | }
|
518 | 554 | };
|
519 | 555 |
|
520 |
| - auto append_padding = [&context](std::size_t count, CharType pad) mutable |
| 556 | + auto append_padding = [&context](std::size_t count, CharType pad) |
521 | 557 | {
|
522 | 558 | for (std::size_t i = 0; i < count; ++i)
|
523 | 559 | {
|
@@ -586,7 +622,7 @@ void Formatter<T, CharType, fly::enable_if<detail::BasicFormatTraits::is_integra
|
586 | 622 | const std::size_t padding_size = width > 1 ? width - 1 : 0;
|
587 | 623 | const auto padding_char = m_specifier.m_fill.value_or(s_space);
|
588 | 624 |
|
589 |
| - auto append_padding = [&context, padding_char](std::size_t count) mutable |
| 625 | + auto append_padding = [&context, padding_char](std::size_t count) |
590 | 626 | {
|
591 | 627 | for (std::size_t i = 0; i < count; ++i)
|
592 | 628 | {
|
@@ -649,7 +685,7 @@ void Formatter<T, CharType, fly::enable_if<detail::BasicFormatTraits::is_integra
|
649 | 685 |
|
650 | 686 | if constexpr (std::is_same_v<string_type, std::string>)
|
651 | 687 | {
|
652 |
| - for (char *it = begin; it != result.ptr; ++it) |
| 688 | + for (const char *it = begin; it != result.ptr; ++it) |
653 | 689 | {
|
654 | 690 | *context.out()++ = *it;
|
655 | 691 | }
|
@@ -689,11 +725,230 @@ Formatter<T, CharType, fly::enable_if<std::is_floating_point<T>>>::Formatter(
|
689 | 725 | {
|
690 | 726 | }
|
691 | 727 |
|
| 728 | +#if defined(FLY_COMPILER_SUPPORTS_FP_CHARCONV) |
| 729 | + |
692 | 730 | //==================================================================================================
|
693 | 731 | template <typename T, typename CharType>
|
694 | 732 | template <typename FormatContext>
|
695 | 733 | void Formatter<T, CharType, fly::enable_if<std::is_floating_point<T>>>::format(
|
696 |
| - const T &value, |
| 734 | + T value, |
| 735 | + FormatContext &context) |
| 736 | +{ |
| 737 | + const bool is_negative = std::signbit(value); |
| 738 | + value = std::abs(value); |
| 739 | + |
| 740 | + std::size_t prefix_size = 0; |
| 741 | + |
| 742 | + if (is_negative || (m_specifier.m_sign == FormatSpecifier::Sign::Always) || |
| 743 | + (m_specifier.m_sign == FormatSpecifier::Sign::NegativeOnlyWithPositivePadding)) |
| 744 | + { |
| 745 | + ++prefix_size; |
| 746 | + } |
| 747 | + |
| 748 | + const int precision = static_cast<int>(m_specifier.precision(context, 6)); |
| 749 | + const FloatConversionResult result = convert_value(value, precision); |
| 750 | + |
| 751 | + auto append_prefix = [this, &is_negative, &context]() |
| 752 | + { |
| 753 | + if (is_negative) |
| 754 | + { |
| 755 | + *context.out()++ = s_minus_sign; |
| 756 | + } |
| 757 | + else if (m_specifier.m_sign == FormatSpecifier::Sign::Always) |
| 758 | + { |
| 759 | + *context.out()++ = s_plus_sign; |
| 760 | + } |
| 761 | + else if (m_specifier.m_sign == FormatSpecifier::Sign::NegativeOnlyWithPositivePadding) |
| 762 | + { |
| 763 | + *context.out()++ = s_space; |
| 764 | + } |
| 765 | + }; |
| 766 | + |
| 767 | + auto append_padding = [&context](std::size_t count, CharType pad) |
| 768 | + { |
| 769 | + for (std::size_t i = 0; i < count; ++i) |
| 770 | + { |
| 771 | + *context.out()++ = pad; |
| 772 | + } |
| 773 | + }; |
| 774 | + |
| 775 | + auto append_number = [this, &context, &result]() |
| 776 | + { |
| 777 | + if constexpr (std::is_same_v<string_type, std::string>) |
| 778 | + { |
| 779 | + for (auto ch : result.m_digits) |
| 780 | + { |
| 781 | + *context.out()++ = ch; |
| 782 | + } |
| 783 | + if (result.m_append_decimal) |
| 784 | + { |
| 785 | + *context.out()++ = '.'; |
| 786 | + } |
| 787 | + for (std::size_t i = 0; i < result.m_zeroes_to_append; ++i) |
| 788 | + { |
| 789 | + *context.out()++ = '0'; |
| 790 | + } |
| 791 | + for (auto ch : result.m_exponent) |
| 792 | + { |
| 793 | + *context.out()++ = ch; |
| 794 | + } |
| 795 | + } |
| 796 | + else |
| 797 | + { |
| 798 | + using unicode = detail::BasicUnicode<char>; |
| 799 | + |
| 800 | + unicode::template convert_encoding_into<string_type>(result.m_digits, context.out()); |
| 801 | + |
| 802 | + if (result.m_append_decimal) |
| 803 | + { |
| 804 | + *context.out()++ = FLY_CHR(CharType, '.'); |
| 805 | + } |
| 806 | + for (std::size_t i = 0; i < result.m_zeroes_to_append; ++i) |
| 807 | + { |
| 808 | + *context.out()++ = FLY_CHR(CharType, '0'); |
| 809 | + } |
| 810 | + |
| 811 | + unicode::template convert_encoding_into<string_type>(result.m_exponent, context.out()); |
| 812 | + } |
| 813 | + }; |
| 814 | + |
| 815 | + const std::size_t value_size = prefix_size + result.m_digits.size() + result.m_exponent.size() + |
| 816 | + static_cast<std::size_t>(result.m_append_decimal) + result.m_zeroes_to_append; |
| 817 | + const std::size_t width = m_specifier.width(context, 0); |
| 818 | + const std::size_t padding_size = std::max(value_size, width) - value_size; |
| 819 | + const auto padding_char = m_specifier.m_fill.value_or(s_space); |
| 820 | + |
| 821 | + switch (m_specifier.m_alignment) |
| 822 | + { |
| 823 | + case FormatSpecifier::Alignment::Left: |
| 824 | + append_prefix(); |
| 825 | + append_number(); |
| 826 | + append_padding(padding_size, padding_char); |
| 827 | + break; |
| 828 | + |
| 829 | + case FormatSpecifier::Alignment::Right: |
| 830 | + append_padding(padding_size, padding_char); |
| 831 | + append_prefix(); |
| 832 | + append_number(); |
| 833 | + break; |
| 834 | + |
| 835 | + case FormatSpecifier::Alignment::Center: |
| 836 | + { |
| 837 | + const std::size_t left_padding = padding_size / 2; |
| 838 | + const std::size_t right_padding = |
| 839 | + (padding_size % 2 == 0) ? left_padding : left_padding + 1; |
| 840 | + |
| 841 | + append_padding(left_padding, padding_char); |
| 842 | + append_prefix(); |
| 843 | + append_number(); |
| 844 | + append_padding(right_padding, padding_char); |
| 845 | + break; |
| 846 | + } |
| 847 | + |
| 848 | + case FormatSpecifier::Alignment::Default: |
| 849 | + if (m_specifier.m_zero_padding) |
| 850 | + { |
| 851 | + append_prefix(); |
| 852 | + append_padding(padding_size, s_zero); |
| 853 | + append_number(); |
| 854 | + } |
| 855 | + else |
| 856 | + { |
| 857 | + append_padding(padding_size, padding_char); |
| 858 | + append_prefix(); |
| 859 | + append_number(); |
| 860 | + } |
| 861 | + break; |
| 862 | + } |
| 863 | +} |
| 864 | + |
| 865 | +//================================================================================================== |
| 866 | +template <typename T, typename CharType> |
| 867 | +auto Formatter<T, CharType, fly::enable_if<std::is_floating_point<T>>>::convert_value( |
| 868 | + T value, |
| 869 | + int precision) -> FloatConversionResult |
| 870 | +{ |
| 871 | + static thread_local std::array<char, std::numeric_limits<T>::digits> s_buffer; |
| 872 | + |
| 873 | + char *begin = s_buffer.data(); |
| 874 | + char *end = begin + s_buffer.size(); |
| 875 | + |
| 876 | + std::chars_format fmt = std::chars_format::general; |
| 877 | + char exponent = '\0'; |
| 878 | + |
| 879 | + switch (m_specifier.m_type) |
| 880 | + { |
| 881 | + case FormatSpecifier::Type::HexFloat: |
| 882 | + fmt = std::chars_format::hex; |
| 883 | + exponent = 'p'; |
| 884 | + break; |
| 885 | + case FormatSpecifier::Type::Scientific: |
| 886 | + fmt = std::chars_format::scientific; |
| 887 | + exponent = 'e'; |
| 888 | + break; |
| 889 | + case FormatSpecifier::Type::Fixed: |
| 890 | + fmt = std::chars_format::fixed; |
| 891 | + break; |
| 892 | + default: |
| 893 | + exponent = 'e'; |
| 894 | + break; |
| 895 | + } |
| 896 | + |
| 897 | + const auto to_chars_result = std::to_chars(begin, end, value, fmt, precision); |
| 898 | + |
| 899 | + FloatConversionResult conversion_result; |
| 900 | + conversion_result.m_digits = |
| 901 | + std::string_view(begin, static_cast<std::size_t>(to_chars_result.ptr - begin)); |
| 902 | + |
| 903 | + if (m_specifier.m_alternate_form) |
| 904 | + { |
| 905 | + conversion_result.m_append_decimal = true; |
| 906 | + |
| 907 | + for (const char *it = begin; it != to_chars_result.ptr; ++it) |
| 908 | + { |
| 909 | + if (*it == '.') |
| 910 | + { |
| 911 | + conversion_result.m_append_decimal = false; |
| 912 | + } |
| 913 | + else if (*it == exponent) |
| 914 | + { |
| 915 | + const auto position = static_cast<std::size_t>(it - begin); |
| 916 | + |
| 917 | + conversion_result.m_exponent = conversion_result.m_digits.substr(position); |
| 918 | + conversion_result.m_digits = conversion_result.m_digits.substr(0, position); |
| 919 | + } |
| 920 | + } |
| 921 | + |
| 922 | + if (m_specifier.m_type == FormatSpecifier::Type::General) |
| 923 | + { |
| 924 | + const auto digits = conversion_result.m_digits.size() - |
| 925 | + static_cast<std::size_t>(!conversion_result.m_append_decimal); |
| 926 | + |
| 927 | + if (static_cast<std::size_t>(precision) > digits) |
| 928 | + { |
| 929 | + conversion_result.m_zeroes_to_append = static_cast<std::size_t>(precision) - digits; |
| 930 | + } |
| 931 | + } |
| 932 | + } |
| 933 | + |
| 934 | + if (m_specifier.m_case == FormatSpecifier::Case::Upper) |
| 935 | + { |
| 936 | + for (char *it = begin; it != to_chars_result.ptr; ++it) |
| 937 | + { |
| 938 | + *it = detail::BasicClassifier<char>::to_upper(*it); |
| 939 | + } |
| 940 | + } |
| 941 | + |
| 942 | + return conversion_result; |
| 943 | +} |
| 944 | + |
| 945 | +#else // FLY_COMPILER_SUPPORTS_FP_CHARCONV |
| 946 | + |
| 947 | +//================================================================================================== |
| 948 | +template <typename T, typename CharType> |
| 949 | +template <typename FormatContext> |
| 950 | +void Formatter<T, CharType, fly::enable_if<std::is_floating_point<T>>>::format( |
| 951 | + T value, |
697 | 952 | FormatContext &context)
|
698 | 953 | {
|
699 | 954 | static thread_local std::stringstream s_stream;
|
@@ -771,6 +1026,8 @@ void Formatter<T, CharType, fly::enable_if<std::is_floating_point<T>>>::format(
|
771 | 1026 | s_stream.str({});
|
772 | 1027 | }
|
773 | 1028 |
|
| 1029 | +#endif // FLY_COMPILER_SUPPORTS_FP_CHARCONV |
| 1030 | + |
774 | 1031 | //==================================================================================================
|
775 | 1032 | template <typename T, typename CharType>
|
776 | 1033 | Formatter<T, CharType, fly::enable_if<std::is_same<T, bool>>>::Formatter(
|
|
0 commit comments