[libc++][format] Adds formatter floating-point.

This properly implements the formatter for floating-point types.

Completes:
- P1652R1 Printf corner cases in std::format
- LWG 3250 std::format: # (alternate form) for NaN and inf
- LWG 3243 std::format and negative zeroes

Implements parts of:
- P0645 Text Formatting

Reviewed By: #libc, ldionne, vitaut

Differential Revision: https://reviews.llvm.org/D114001
This commit is contained in:
Mark de Wever 2020-12-14 17:39:15 +01:00
parent 50999e82e8
commit db2944e34b
15 changed files with 5118 additions and 70 deletions

View File

@ -0,0 +1,241 @@
//===----------------------------------------------------------------------===//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include <format>
#include <array>
#include <limits>
#include <random>
#include <string>
#include "CartesianBenchmarks.h"
#include "benchmark/benchmark.h"
// *** Localization ***
enum class LocalizationE { False, True };
struct AllLocalizations : EnumValuesAsTuple<AllLocalizations, LocalizationE, 2> {
static constexpr const char* Names[] = {"LocFalse", "LocTrue"};
};
template <LocalizationE E>
struct Localization {};
template <>
struct Localization<LocalizationE::False> {
static constexpr const char* fmt = "";
};
template <>
struct Localization<LocalizationE::True> {
static constexpr const char* fmt = "L";
};
// *** Types ***
enum class TypeE { Float, Double, LongDouble };
// TODO FMT Set to 3 after to_chars has long double suport.
struct AllTypes : EnumValuesAsTuple<AllTypes, TypeE, 2> {
static constexpr const char* Names[] = {"Float", "Double", "LongDouble"};
};
template <TypeE E>
struct Type {};
template <>
struct Type<TypeE::Float> {
using type = float;
};
template <>
struct Type<TypeE::Double> {
using type = double;
};
template <>
struct Type<TypeE::LongDouble> {
using type = long double;
};
// *** Values ***
enum class ValueE { Inf, Random };
struct AllValues : EnumValuesAsTuple<AllValues, ValueE, 2> {
static constexpr const char* Names[] = {"Inf", "Random"};
};
template <ValueE E>
struct Value {};
template <>
struct Value<ValueE::Inf> {
template <class F>
static std::array<F, 1000> make_data() {
std::array<F, 1000> result;
std::fill(result.begin(), result.end(), -std::numeric_limits<F>::infinity());
return result;
}
};
template <>
struct Value<ValueE::Random> {
template <class F>
static std::array<F, 1000> make_data() {
std::random_device seed;
std::mt19937 generator(seed());
std::uniform_int_distribution<std::conditional_t<sizeof(F) == sizeof(uint32_t), uint32_t, uint64_t>> distribution;
std::array<F, 1000> result;
std::generate(result.begin(), result.end(), [&] {
while (true) {
auto result = std::bit_cast<F>(distribution(generator));
if (std::isfinite(result))
return result;
}
});
return result;
}
};
// *** Display Type ***
enum class DisplayTypeE {
Default,
Hex,
Scientific,
Fixed,
General,
};
struct AllDisplayTypes : EnumValuesAsTuple<AllDisplayTypes, DisplayTypeE, 5> {
static constexpr const char* Names[] = {"DisplayDefault", "DisplayHex", "DisplayScientific", "DisplayFixed",
"DisplayGeneral"};
};
template <DisplayTypeE E>
struct DisplayType {};
template <>
struct DisplayType<DisplayTypeE::Default> {
static constexpr const char* fmt = "";
};
template <>
struct DisplayType<DisplayTypeE::Hex> {
static constexpr const char* fmt = "a";
};
template <>
struct DisplayType<DisplayTypeE::Scientific> {
static constexpr const char* fmt = "e";
};
template <>
struct DisplayType<DisplayTypeE::Fixed> {
static constexpr const char* fmt = "f";
};
template <>
struct DisplayType<DisplayTypeE::General> {
static constexpr const char* fmt = "g";
};
// *** Alignment ***
enum class AlignmentE { None, Left, Center, Right, ZeroPadding };
struct AllAlignments : EnumValuesAsTuple<AllAlignments, AlignmentE, 5> {
static constexpr const char* Names[] = {"AlignNone", "AlignmentLeft", "AlignmentCenter", "AlignmentRight",
"ZeroPadding"};
};
template <AlignmentE E>
struct Alignment {};
template <>
struct Alignment<AlignmentE::None> {
static constexpr const char* fmt = "";
};
template <>
struct Alignment<AlignmentE::Left> {
// Width > PrecisionE::Huge
static constexpr const char* fmt = "0<17500";
};
template <>
struct Alignment<AlignmentE::Center> {
// Width > PrecisionE::Huge
static constexpr const char* fmt = "0^17500";
};
template <>
struct Alignment<AlignmentE::Right> {
// Width > PrecisionE::Huge
static constexpr const char* fmt = "0>17500";
};
template <>
struct Alignment<AlignmentE::ZeroPadding> {
// Width > PrecisionE::Huge
static constexpr const char* fmt = "017500";
};
enum class PrecisionE { None, Zero, Small, Huge };
struct AllPrecisions : EnumValuesAsTuple<AllPrecisions, PrecisionE, 4> {
static constexpr const char* Names[] = {"PrecNone", "PrecZero", "PrecSmall", "PrecHuge"};
};
template <PrecisionE E>
struct Precision {};
template <>
struct Precision<PrecisionE::None> {
static constexpr const char* fmt = "";
};
template <>
struct Precision<PrecisionE::Zero> {
static constexpr const char* fmt = ".0";
};
template <>
struct Precision<PrecisionE::Small> {
static constexpr const char* fmt = ".10";
};
template <>
struct Precision<PrecisionE::Huge> {
// The maximum precision for a minimal sub normal long double is ±0x1p-16494.
// This value is always larger than that value forcing the trailing zero path
// to be executed.
static constexpr const char* fmt = ".17000";
};
template <class L, class DT, class T, class V, class A, class P>
struct FloatingPoint {
using F = typename Type<T::value>::type;
void run(benchmark::State& state) const {
std::array<F, 1000> data{Value<V::value>::template make_data<F>()};
std::array<char, 20'000> output;
std::string fmt{std::string("{:") + Alignment<A::value>::fmt + Precision<P::value>::fmt +
Localization<L::value>::fmt + DisplayType<DT::value>::fmt + "}"};
while (state.KeepRunningBatch(1000))
for (F value : data)
benchmark::DoNotOptimize(std::format_to(output.begin(), fmt, value));
}
std::string name() const {
return "FloatingPoint" + L::name() + DT::name() + T::name() + V::name() + A::name() + P::name();
}
};
int main(int argc, char** argv) {
benchmark::Initialize(&argc, argv);
if (benchmark::ReportUnrecognizedArguments(argc, argv))
return 1;
makeCartesianProductBenchmark<FloatingPoint, AllLocalizations, AllDisplayTypes, AllTypes, AllValues, AllAlignments,
AllPrecisions>();
benchmark::RunSpecifiedBenchmarks();
}

View File

@ -203,10 +203,10 @@
"`3237 <https://wg21.link/LWG3237>`__","LWG 3038 and 3190 have inconsistent PRs","Prague","|Complete|","14.0"
"`3238 <https://wg21.link/LWG3238>`__","Insufficiently-defined behavior of ``std::function``\ deduction guides","Prague","",""
"`3242 <https://wg21.link/LWG3242>`__","``std::format``\ : missing rules for ``arg-id``\ in ``width``\ and ``precision``\ ","Prague","|Complete|","Clang 14","|format|"
"`3243 <https://wg21.link/LWG3243>`__","``std::format``\ and negative zeroes","Prague","","","|format|"
"`3243 <https://wg21.link/LWG3243>`__","``std::format``\ and negative zeroes","Prague","|Complete|","14.0","|format|"
"`3247 <https://wg21.link/LWG3247>`__","``ranges::iter_move``\ should perform ADL-only lookup of ``iter_move``\ ","Prague","","","|ranges|"
"`3248 <https://wg21.link/LWG3248>`__","``std::format``\ ``#b``\ , ``#B``\ , ``#o``\ , ``#x``\ , and ``#X``\ presentation types misformat negative numbers","Prague","|Complete|","14.0","|format|"
"`3250 <https://wg21.link/LWG3250>`__","``std::format``\ : ``#``\ (alternate form) for NaN and inf","Prague","","","|format|"
"`3250 <https://wg21.link/LWG3250>`__","``std::format``\ : ``#``\ (alternate form) for NaN and inf","Prague","|Complete|","14.0","|format|"
"`3251 <https://wg21.link/LWG3251>`__","Are ``std::format``\ alignment specifiers applied to string arguments?","Prague","","","|format|"
"`3252 <https://wg21.link/LWG3252>`__","Parse locale's aware modifiers for commands are not consistent with POSIX spec","Prague","","","|chrono|"
"`3254 <https://wg21.link/LWG3254>`__","Strike ``stop_token``\ 's ``operator!=``\ ","Prague","",""

Can't render this file because it has a wrong number of fields in line 2.

View File

@ -129,7 +129,7 @@
"`P1644R0 <https://wg21.link/P1644R0>`__","LWG","Add wait/notify to atomic<shared_ptr>","Cologne","",""
"`P1650R0 <https://wg21.link/P1650R0>`__","LWG","Output std::chrono::days with 'd' suffix","Cologne","",""
"`P1651R0 <https://wg21.link/P1651R0>`__","LWG","bind_front should not unwrap reference_wrapper","Cologne","|Complete|","13.0"
"`P1652R1 <https://wg21.link/P1652R1>`__","LWG","Printf corner cases in std::format","Cologne","|In Progress|",""
"`P1652R1 <https://wg21.link/P1652R1>`__","LWG","Printf corner cases in std::format","Cologne","|Complete|","14.0"
"`P1661R1 <https://wg21.link/P1661R1>`__","LWG","Remove dedicated precalculated hash lookup interface","Cologne","|Nothing To Do|",""
"`P1754R1 <https://wg21.link/P1754R1>`__","LWG","Rename concepts to standard_case for C++20, while we still can","Cologne","|In Progress|",""
"","","","","",""

1 Paper # Group Paper Name Meeting Status First released version
129 `P1644R0 <https://wg21.link/P1644R0>`__ LWG Add wait/notify to atomic<shared_ptr> Cologne
130 `P1650R0 <https://wg21.link/P1650R0>`__ LWG Output std::chrono::days with 'd' suffix Cologne
131 `P1651R0 <https://wg21.link/P1651R0>`__ LWG bind_front should not unwrap reference_wrapper Cologne |Complete| 13.0
132 `P1652R1 <https://wg21.link/P1652R1>`__ LWG Printf corner cases in std::format Cologne |In Progress| |Complete| 14.0
133 `P1661R1 <https://wg21.link/P1661R1>`__ LWG Remove dedicated precalculated hash lookup interface Cologne |Nothing To Do|
134 `P1754R1 <https://wg21.link/P1754R1>`__ LWG Rename concepts to standard_case for C++20, while we still can Cologne |In Progress|
135

View File

@ -178,6 +178,7 @@ set(files
__format/formatter.h
__format/formatter_bool.h
__format/formatter_char.h
__format/formatter_floating_point.h
__format/formatter_integer.h
__format/formatter_integral.h
__format/formatter_string.h

View File

@ -190,6 +190,34 @@ __write(output_iterator<const _CharT&> auto __out_it, const _CharT* __first,
return _VSTD::fill_n(_VSTD::move(__out_it), __padding.__after, __fill);
}
/**
* @overload
*
* Writes additional zero's for the precision before the exponent.
* This is used when the precision requested in the format string is larger
* than the maximum precision of the floating-point type. These precision
* digits are always 0.
*
* @param __exponent The location of the exponent character.
* @param __num_trailing_zeros The number of 0's to write before the exponent
* character.
*/
template <class _CharT, class _Fill>
_LIBCPP_HIDE_FROM_ABI auto __write(output_iterator<const _CharT&> auto __out_it, const _CharT* __first,
const _CharT* __last, size_t __size, size_t __width, _Fill __fill,
__format_spec::_Flags::_Alignment __alignment, const _CharT* __exponent,
size_t __num_trailing_zeros) -> decltype(__out_it) {
_LIBCPP_ASSERT(__first <= __last, "Not a valid range");
_LIBCPP_ASSERT(__num_trailing_zeros > 0, "The overload not writing trailing zeros should have been used");
__padding_size_result __padding = __padding_size(__size + __num_trailing_zeros, __width, __alignment);
__out_it = _VSTD::fill_n(_VSTD::move(__out_it), __padding.__before, __fill);
__out_it = _VSTD::copy(__first, __exponent, _VSTD::move(__out_it));
__out_it = _VSTD::fill_n(_VSTD::move(__out_it), __num_trailing_zeros, _CharT('0'));
__out_it = _VSTD::copy(__exponent, __last, _VSTD::move(__out_it));
return _VSTD::fill_n(_VSTD::move(__out_it), __padding.__after, __fill);
}
/**
* @overload
*

View File

@ -0,0 +1,717 @@
// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef _LIBCPP___FORMAT_FORMATTER_FLOATING_POINT_H
#define _LIBCPP___FORMAT_FORMATTER_FLOATING_POINT_H
#include <__algorithm/copy.h>
#include <__algorithm/copy_n.h>
#include <__algorithm/fill_n.h>
#include <__algorithm/find.h>
#include <__algorithm/min.h>
#include <__algorithm/rotate.h>
#include <__algorithm/transform.h>
#include <__concepts/arithmetic.h>
#include <__config>
#include <__debug>
#include <__format/format_error.h>
#include <__format/format_fwd.h>
#include <__format/format_string.h>
#include <__format/formatter.h>
#include <__format/formatter_integral.h>
#include <__format/parser_std_format_spec.h>
#include <__utility/move.h>
#include <charconv>
#include <cmath>
#ifndef _LIBCPP_HAS_NO_LOCALIZATION
# include <locale>
#endif
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif
_LIBCPP_PUSH_MACROS
#include <__undef_macros>
_LIBCPP_BEGIN_NAMESPACE_STD
#if _LIBCPP_STD_VER > 17
// TODO FMT Remove this once we require compilers with proper C++20 support.
// If the compiler has no concepts support, the format header will be disabled.
// Without concepts support enable_if needs to be used and that too much effort
// to support compilers with partial C++20 support.
# if !defined(_LIBCPP_HAS_NO_CONCEPTS)
namespace __format_spec {
template <floating_point _Tp>
_LIBCPP_HIDE_FROM_ABI char* __to_buffer(char* __first, char* __last, _Tp __value) {
to_chars_result __r = _VSTD::to_chars(__first, __last, __value);
_LIBCPP_ASSERT(__r.ec == errc(0), "Internal buffer too small");
return __r.ptr;
}
template <floating_point _Tp>
_LIBCPP_HIDE_FROM_ABI char* __to_buffer(char* __first, char* __last, _Tp __value, chars_format __fmt) {
to_chars_result __r = _VSTD::to_chars(__first, __last, __value, __fmt);
_LIBCPP_ASSERT(__r.ec == errc(0), "Internal buffer too small");
return __r.ptr;
}
template <floating_point _Tp>
_LIBCPP_HIDE_FROM_ABI char* __to_buffer(char* __first, char* __last, _Tp __value, chars_format __fmt, int __precision) {
to_chars_result __r = _VSTD::to_chars(__first, __last, __value, __fmt, __precision);
_LIBCPP_ASSERT(__r.ec == errc(0), "Internal buffer too small");
return __r.ptr;
}
// https://en.cppreference.com/w/cpp/language/types#cite_note-1
// float min subnormal: +/-0x1p-149 max: +/- 3.402,823,4 10^38
// double min subnormal: +/-0x1p-1074 max +/- 1.797,693,134,862,315,7 10^308
// long double (x86) min subnormal: +/-0x1p-16446 max: +/- 1.189,731,495,357,231,765,021 10^4932
//
// The maximum number of digits required for the integral part is based on the
// maximum's value power of 10. Every power of 10 requires one additional
// decimal digit.
// The maximum number of digits required for the fractional part is based on
// the minimal subnormal hexadecimal output's power of 10. Every division of a
// fraction's binary 1 by 2, requires one additional decimal digit.
//
// The maximum size of a formatted value depends on the selected output format.
// Ignoring the fact the format string can request a precision larger than the
// values maximum required, these values are:
//
// sign 1 code unit
// __max_integral
// radix point 1 code unit
// __max_fractional
// exponent character 1 code unit
// sign 1 code unit
// __max_fractional_value
// -----------------------------------
// total 4 code units extra required.
//
// TODO FMT Optimize the storage to avoid storing digits that are known to be zero.
// https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/
// TODO FMT Add long double specialization when to_chars has proper long double support.
template <class _Tp>
struct __traits;
template <floating_point _Fp>
static constexpr size_t __float_buffer_size(int __precision) {
using _Traits = __traits<_Fp>;
return 4 + _Traits::__max_integral + __precision + _Traits::__max_fractional_value;
}
template <>
struct __traits<float> {
static constexpr int __max_integral = 38;
static constexpr int __max_fractional = 149;
static constexpr int __max_fractional_value = 3;
static constexpr size_t __stack_buffer_size = 256;
static constexpr int __hex_precision_digits = 3;
};
template <>
struct __traits<double> {
static constexpr int __max_integral = 308;
static constexpr int __max_fractional = 1074;
static constexpr int __max_fractional_value = 4;
static constexpr size_t __stack_buffer_size = 1024;
static constexpr int __hex_precision_digits = 4;
};
/// Helper class to store the conversion buffer.
///
/// Depending on the maxium size required for a value, the buffer is allocated
/// on the stack or the heap.
template <floating_point _Fp>
class _LIBCPP_TEMPLATE_VIS __float_buffer {
using _Traits = __traits<_Fp>;
public:
// TODO FMT Improve this constructor to do a better estimate.
// When using a scientific formatting with a precision of 6 a stack buffer
// will always suffice. At the moment that isn't important since floats and
// doubles use a stack buffer, unless the precision used in the format string
// is large.
// When supporting long doubles the __max_integral part becomes 4932 which
// may be too much for some platforms. For these cases a better estimate is
// required.
explicit _LIBCPP_HIDE_FROM_ABI __float_buffer(int __precision)
: __precision_(__precision != -1 ? __precision : _Traits::__max_fractional) {
// When the precision is larger than _Traits::__max_fractional the digits in
// the range (_Traits::__max_fractional, precision] will contain the value
// zero. There's no need to request to_chars to write these zeros:
// - When the value is large a temporary heap buffer needs to be allocated.
// - When to_chars writes the values they need to be "copied" to the output:
// - char: std::fill on the output iterator is faster than std::copy.
// - wchar_t: same argument as char, but additional std::copy won't work.
// The input is always a char buffer, so every char in the buffer needs
// to be converted from a char to a wchar_t.
if (__precision_ > _Traits::__max_fractional) {
__num_trailing_zeros_ = __precision_ - _Traits::__max_fractional;
__precision_ = _Traits::__max_fractional;
}
__size_ = __format_spec::__float_buffer_size<_Fp>(__precision_);
if (__size_ > _Traits::__stack_buffer_size)
// The allocated buffer's contents don't need initialization.
__begin_ = allocator<char>{}.allocate(__size_);
else
__begin_ = __buffer_;
}
_LIBCPP_HIDE_FROM_ABI ~__float_buffer() {
if (__size_ > _Traits::__stack_buffer_size)
allocator<char>{}.deallocate(__begin_, __size_);
}
_LIBCPP_HIDE_FROM_ABI __float_buffer(const __float_buffer&) = delete;
_LIBCPP_HIDE_FROM_ABI __float_buffer& operator=(const __float_buffer&) = delete;
_LIBCPP_HIDE_FROM_ABI char* begin() const { return __begin_; }
_LIBCPP_HIDE_FROM_ABI char* end() const { return __begin_ + __size_; }
_LIBCPP_HIDE_FROM_ABI int __precision() const { return __precision_; }
_LIBCPP_HIDE_FROM_ABI int __num_trailing_zeros() const { return __num_trailing_zeros_; }
_LIBCPP_HIDE_FROM_ABI void __remove_trailing_zeros() { __num_trailing_zeros_ = 0; }
private:
int __precision_;
int __num_trailing_zeros_{0};
size_t __size_;
char* __begin_;
char __buffer_[_Traits::__stack_buffer_size];
};
struct __float_result {
/// Points at the beginning of the integral part in the buffer.
///
/// When there's no sign character this points at the start of the buffer.
char* __integral;
/// Points at the radix point, when not present it's the same as \ref __last.
char* __radix_point;
/// Points at the exponent character, when not present it's the same as \ref __last.
char* __exponent;
/// Points beyond the last written element in the buffer.
char* __last;
};
/// Finds the position of the exponent character 'e' at the end of the buffer.
///
/// Assuming there is an exponent the input will terminate with
/// eSdd and eSdddd (S = sign, d = digit)
///
/// \returns a pointer to the exponent or __last when not found.
constexpr inline _LIBCPP_HIDE_FROM_ABI char* __find_exponent(char* __first, char* __last) {
ptrdiff_t __size = __last - __first;
if (__size > 4) {
__first = __last - _VSTD::min(__size, ptrdiff_t(6));
for (; __first != __last - 3; ++__first) {
if (*__first == 'e')
return __first;
}
}
return __last;
}
template <class _Fp, class _Tp>
_LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_default(const __float_buffer<_Fp>& __buffer, _Tp __value,
char* __integral) {
__float_result __result;
__result.__integral = __integral;
__result.__last = __format_spec::__to_buffer(__integral, __buffer.end(), __value);
__result.__exponent = __format_spec::__find_exponent(__result.__integral, __result.__last);
// Constrains:
// - There's at least one decimal digit before the radix point.
// - The radix point, when present, is placed before the exponent.
__result.__radix_point = _VSTD::find(__result.__integral + 1, __result.__exponent, '.');
// When the radix point isn't found its position is the exponent instead of
// __result.__last.
if (__result.__radix_point == __result.__exponent)
__result.__radix_point = __result.__last;
// clang-format off
_LIBCPP_ASSERT((__result.__integral != __result.__last) &&
(__result.__radix_point == __result.__last || *__result.__radix_point == '.') &&
(__result.__exponent == __result.__last || *__result.__exponent == 'e'),
"Post-condition failure.");
// clang-format on
return __result;
}
template <class _Fp, class _Tp>
_LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_hexadecimal_lower_case(const __float_buffer<_Fp>& __buffer,
_Tp __value, int __precision,
char* __integral) {
__float_result __result;
__result.__integral = __integral;
if (__precision == -1)
__result.__last = __format_spec::__to_buffer(__integral, __buffer.end(), __value, chars_format::hex);
else
__result.__last = __format_spec::__to_buffer(__integral, __buffer.end(), __value, chars_format::hex, __precision);
// H = one or more hex-digits
// S = sign
// D = one or more decimal-digits
// When the fractional part is zero and no precision the output is 0p+0
// else the output is 0.HpSD
// So testing the second position can differentiate between these two cases.
char* __first = __integral + 1;
if (*__first == '.') {
__result.__radix_point = __first;
// One digit is the minimum
// 0.hpSd
// ^-- last
// ^---- integral = end of search
// ^-------- start of search
// 0123456
//
// Four digits is the maximum
// 0.hpSdddd
// ^-- last
// ^---- integral = end of search
// ^-------- start of search
// 0123456789
static_assert(__traits<_Fp>::__hex_precision_digits <= 4, "Guard against possible underflow.");
char* __last = __result.__last - 2;
__first = __last - __traits<_Fp>::__hex_precision_digits;
__result.__exponent = _VSTD::find(__first, __last, 'p');
} else {
__result.__radix_point = __result.__last;
__result.__exponent = __first;
}
// clang-format off
_LIBCPP_ASSERT((__result.__integral != __result.__last) &&
(__result.__radix_point == __result.__last || *__result.__radix_point == '.') &&
(__result.__exponent != __result.__last && *__result.__exponent == 'p'),
"Post-condition failure.");
// clang-format on
return __result;
}
template <class _Fp, class _Tp>
_LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_hexadecimal_upper_case(const __float_buffer<_Fp>& __buffer,
_Tp __value, int __precision,
char* __integral) {
__float_result __result =
__format_spec::__format_buffer_hexadecimal_lower_case(__buffer, __value, __precision, __integral);
_VSTD::transform(__result.__integral, __result.__exponent, __result.__integral, __hex_to_upper);
*__result.__exponent = 'P';
return __result;
}
template <class _Fp, class _Tp>
_LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_scientific_lower_case(const __float_buffer<_Fp>& __buffer,
_Tp __value, int __precision,
char* __integral) {
__float_result __result;
__result.__integral = __integral;
__result.__last =
__format_spec::__to_buffer(__integral, __buffer.end(), __value, chars_format::scientific, __precision);
char* __first = __integral + 1;
_LIBCPP_ASSERT(__first != __result.__last, "No exponent present");
if (*__first == '.') {
__result.__radix_point = __first;
__result.__exponent = __format_spec::__find_exponent(__first + 1, __result.__last);
} else {
__result.__radix_point = __result.__last;
__result.__exponent = __first;
}
// clang-format off
_LIBCPP_ASSERT((__result.__integral != __result.__last) &&
(__result.__radix_point == __result.__last || *__result.__radix_point == '.') &&
(__result.__exponent != __result.__last && *__result.__exponent == 'e'),
"Post-condition failure.");
// clang-format on
return __result;
}
template <class _Fp, class _Tp>
_LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_scientific_upper_case(const __float_buffer<_Fp>& __buffer,
_Tp __value, int __precision,
char* __integral) {
__float_result __result =
__format_spec::__format_buffer_scientific_lower_case(__buffer, __value, __precision, __integral);
*__result.__exponent = 'E';
return __result;
}
template <class _Fp, class _Tp>
_LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_fixed(const __float_buffer<_Fp>& __buffer, _Tp __value,
int __precision, char* __integral) {
__float_result __result;
__result.__integral = __integral;
__result.__last = __format_spec::__to_buffer(__integral, __buffer.end(), __value, chars_format::fixed, __precision);
// When there's no precision there's no radix point.
// Else the radix point is placed at __precision + 1 from the end.
// By converting __precision to a bool the subtraction can be done
// unconditionally.
__result.__radix_point = __result.__last - (__precision + bool(__precision));
__result.__exponent = __result.__last;
// clang-format off
_LIBCPP_ASSERT((__result.__integral != __result.__last) &&
(__result.__radix_point == __result.__last || *__result.__radix_point == '.') &&
(__result.__exponent == __result.__last),
"Post-condition failure.");
// clang-format on
return __result;
}
template <class _Fp, class _Tp>
_LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_general_lower_case(__float_buffer<_Fp>& __buffer, _Tp __value,
int __precision, char* __integral) {
__buffer.__remove_trailing_zeros();
__float_result __result;
__result.__integral = __integral;
__result.__last = __format_spec::__to_buffer(__integral, __buffer.end(), __value, chars_format::general, __precision);
char* __first = __integral + 1;
if (__first == __result.__last) {
__result.__radix_point = __result.__last;
__result.__exponent = __result.__last;
} else {
__result.__exponent = __format_spec::__find_exponent(__first, __result.__last);
if (__result.__exponent != __result.__last)
// In scientific mode if there's a radix point it will always be after
// the first digit. (This is the position __first points at).
__result.__radix_point = *__first == '.' ? __first : __result.__last;
else {
// In fixed mode the algorithm truncates trailing spaces and possibly the
// radix point. There's no good guess for the position of the radix point
// therefore scan the output after the first digit.
__result.__radix_point = _VSTD::find(__first, __result.__last, '.');
}
}
// clang-format off
_LIBCPP_ASSERT((__result.__integral != __result.__last) &&
(__result.__radix_point == __result.__last || *__result.__radix_point == '.') &&
(__result.__exponent == __result.__last || *__result.__exponent == 'e'),
"Post-condition failure.");
// clang-format on
return __result;
}
template <class _Fp, class _Tp>
_LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_general_upper_case(__float_buffer<_Fp>& __buffer, _Tp __value,
int __precision, char* __integral) {
__float_result __result =
__format_spec::__format_buffer_general_lower_case(__buffer, __value, __precision, __integral);
if (__result.__exponent != __result.__last)
*__result.__exponent = 'E';
return __result;
}
# ifndef _LIBCPP_HAS_NO_LOCALIZATION
template <class _OutIt, class _Fp, class _CharT>
_LIBCPP_HIDE_FROM_ABI _OutIt __format_locale_specific_form(_OutIt __out_it, const __float_buffer<_Fp>& __buffer,
const __float_result& __result, _VSTD::locale __loc,
size_t __width, _Flags::_Alignment __alignment,
_CharT __fill) {
const auto& __np = use_facet<numpunct<_CharT>>(__loc);
string __grouping = __np.grouping();
char* __first = __result.__integral;
// When no radix point or exponent are present __last will be __result.__last.
char* __last = _VSTD::min(__result.__radix_point, __result.__exponent);
ptrdiff_t __digits = __last - __first;
if (!__grouping.empty()) {
if (__digits <= __grouping[0])
__grouping.clear();
else
__grouping = __determine_grouping(__digits, __grouping);
}
size_t __size = __result.__last - __buffer.begin() + // Formatted string
__buffer.__num_trailing_zeros() + // Not yet rendered zeros
__grouping.size() - // Grouping contains one
!__grouping.empty(); // additional character
__formatter::__padding_size_result __padding = {0, 0};
bool __zero_padding = __alignment == _Flags::_Alignment::__default;
if (__size < __width) {
if (__zero_padding) {
__alignment = _Flags::_Alignment::__right;
__fill = _CharT('0');
}
__padding = __formatter::__padding_size(__size, __width, __alignment);
}
// sign and (zero padding or alignment)
if (__zero_padding && __first != __buffer.begin())
*__out_it++ = *__buffer.begin();
__out_it = _VSTD::fill_n(_VSTD::move(__out_it), __padding.__before, __fill);
if (!__zero_padding && __first != __buffer.begin())
*__out_it++ = *__buffer.begin();
// integral part
if (__grouping.empty()) {
__out_it = _VSTD::copy_n(__first, __digits, _VSTD::move(__out_it));
} else {
auto __r = __grouping.rbegin();
auto __e = __grouping.rend() - 1;
_CharT __sep = __np.thousands_sep();
// The output is divided in small groups of numbers to write:
// - A group before the first separator.
// - A separator and a group, repeated for the number of separators.
// - A group after the last separator.
// This loop achieves that process by testing the termination condition
// midway in the loop.
while (true) {
__out_it = _VSTD::copy_n(__first, *__r, _VSTD::move(__out_it));
__first += *__r;
if (__r == __e)
break;
++__r;
*__out_it++ = __sep;
}
}
// fractional part
if (__result.__radix_point != __result.__last) {
*__out_it++ = __np.decimal_point();
__out_it = _VSTD::copy(__result.__radix_point + 1, __result.__exponent, _VSTD::move(__out_it));
__out_it = _VSTD::fill_n(_VSTD::move(__out_it), __buffer.__num_trailing_zeros(), _CharT('0'));
}
// exponent
if (__result.__exponent != __result.__last)
__out_it = _VSTD::copy(__result.__exponent, __result.__last, _VSTD::move(__out_it));
// alignment
return _VSTD::fill_n(_VSTD::move(__out_it), __padding.__after, __fill);
}
# endif // _LIBCPP_HAS_NO_LOCALIZATION
template <__formatter::__char_type _CharT>
class _LIBCPP_TEMPLATE_VIS __formatter_floating_point : public __parser_floating_point<_CharT> {
public:
template <floating_point _Tp>
_LIBCPP_HIDE_FROM_ABI auto format(_Tp __value, auto& __ctx) -> decltype(__ctx.out()) {
if (this->__width_needs_substitution())
this->__substitute_width_arg_id(__ctx.arg(this->__width));
bool __negative = _VSTD::signbit(__value);
if (!_VSTD::isfinite(__value)) [[unlikely]]
return __format_non_finite(__ctx.out(), __negative, _VSTD::isnan(__value));
bool __has_precision = this->__has_precision_field();
if (this->__precision_needs_substitution())
this->__substitute_precision_arg_id(__ctx.arg(this->__precision));
// Depending on the std-format-spec string the sign and the value
// might not be outputted together:
// - zero-padding may insert additional '0' characters.
// Therefore the value is processed as a non negative value.
// The function @ref __insert_sign will insert a '-' when the value was
// negative.
if (__negative)
__value = _VSTD::copysign(__value, +1.0);
// TODO FMT _Fp should just be _Tp when to_chars has proper long double support.
using _Fp = conditional_t<same_as<_Tp, long double>, double, _Tp>;
// Force the type of the precision to avoid -1 to become an unsigned value.
__float_buffer<_Fp> __buffer(__has_precision ? int(this->__precision) : -1);
__float_result __result = __format_buffer(__buffer, __value, __negative, __has_precision);
if (this->__alternate_form && __result.__radix_point == __result.__last) {
*__result.__last++ = '.';
// When there is an exponent the point needs to be moved before the
// exponent. When there's no exponent the rotate does nothing. Since
// rotate tests whether the operation is a nop, call it unconditionally.
_VSTD::rotate(__result.__exponent, __result.__last - 1, __result.__last);
__result.__radix_point = __result.__exponent;
// The radix point is always placed before the exponent.
// - No exponent needs to point to the new last.
// - An exponent needs to move one position to the right.
// So it's safe to increment the value unconditionally.
++__result.__exponent;
}
# ifndef _LIBCPP_HAS_NO_LOCALIZATION
if (this->__locale_specific_form)
return __format_spec::__format_locale_specific_form(__ctx.out(), __buffer, __result, __ctx.locale(),
this->__width, this->__alignment, this->__fill);
# endif
ptrdiff_t __size = __result.__last - __buffer.begin();
int __num_trailing_zeros = __buffer.__num_trailing_zeros();
if (__size + __num_trailing_zeros >= this->__width) {
if (__num_trailing_zeros && __result.__exponent != __result.__last)
// Insert trailing zeros before exponent character.
return _VSTD::copy(__result.__exponent, __result.__last,
_VSTD::fill_n(_VSTD::copy(__buffer.begin(), __result.__exponent, __ctx.out()),
__num_trailing_zeros, _CharT('0')));
return _VSTD::fill_n(_VSTD::copy(__buffer.begin(), __result.__last, __ctx.out()), __num_trailing_zeros,
_CharT('0'));
}
auto __out_it = __ctx.out();
char* __first = __buffer.begin();
if (this->__alignment == _Flags::_Alignment::__default) {
// When there is a sign output it before the padding. Note the __size
// doesn't need any adjustment, regardless whether the sign is written
// here or in __formatter::__write.
if (__first != __result.__integral)
*__out_it++ = *__first++;
// After the sign is written, zero padding is the same a right alignment
// with '0'.
this->__alignment = _Flags::_Alignment::__right;
this->__fill = _CharT('0');
}
if (__num_trailing_zeros)
return __formatter::__write(_VSTD::move(__out_it), __first, __result.__last, __size, this->__width, this->__fill,
this->__alignment, __result.__exponent, __num_trailing_zeros);
return __formatter::__write(_VSTD::move(__out_it), __first, __result.__last, __size, this->__width, this->__fill,
this->__alignment);
}
private:
template <class _OutIt>
_LIBCPP_HIDE_FROM_ABI _OutIt __format_non_finite(_OutIt __out_it, bool __negative, bool __isnan) {
char __buffer[4];
char* __last = __insert_sign(__buffer, __negative, this->__sign);
// to_char can return inf, infinity, nan, and nan(n-char-sequence).
// The format library requires inf and nan.
// All in one expression to avoid dangling references.
__last = _VSTD::copy_n(&("infnanINFNAN"[6 * (this->__type == _Flags::_Type::__float_hexadecimal_upper_case ||
this->__type == _Flags::_Type::__scientific_upper_case ||
this->__type == _Flags::_Type::__fixed_upper_case ||
this->__type == _Flags::_Type::__general_upper_case) +
3 * __isnan]),
3, __last);
// [format.string.std]/13
// A zero (0) character preceding the width field pads the field with
// leading zeros (following any indication of sign or base) to the field
// width, except when applied to an infinity or NaN.
if (this->__alignment == _Flags::_Alignment::__default)
this->__alignment = _Flags::_Alignment::__right;
ptrdiff_t __size = __last - __buffer;
if (__size >= this->__width)
return _VSTD::copy_n(__buffer, __size, _VSTD::move(__out_it));
return __formatter::__write(_VSTD::move(__out_it), __buffer, __last, __size, this->__width, this->__fill,
this->__alignment);
}
/// Fills the buffer with the data based on the requested formatting.
///
/// This function, when needed, turns the characters to upper case and
/// determines the "interesting" locations which are returned to the caller.
///
/// This means the caller never has to convert the contents of the buffer to
/// upper case or search for radix points and the location of the exponent.
/// This gives a bit of overhead. The original code didn't do that, but due
/// to the number of possible additional work needed to turn this number to
/// the proper output the code was littered with tests for upper cases and
/// searches for radix points and exponents.
/// - When a precision larger than the type's precision is selected
/// additional zero characters need to be written before the exponent.
/// - alternate form needs to add a radix point when not present.
/// - localization needs to do grouping in the integral part.
template <class _Fp, class _Tp>
// TODO FMT _Fp should just be _Tp when to_chars has proper long double support.
_LIBCPP_HIDE_FROM_ABI __float_result __format_buffer(__float_buffer<_Fp>& __buffer, _Tp __value, bool __negative,
bool __has_precision) {
char* __first = __insert_sign(__buffer.begin(), __negative, this->__sign);
switch (this->__type) {
case _Flags::_Type::__default:
return __format_spec::__format_buffer_default(__buffer, __value, __first);
case _Flags::_Type::__float_hexadecimal_lower_case:
return __format_spec::__format_buffer_hexadecimal_lower_case(
__buffer, __value, __has_precision ? __buffer.__precision() : -1, __first);
case _Flags::_Type::__float_hexadecimal_upper_case:
return __format_spec::__format_buffer_hexadecimal_upper_case(
__buffer, __value, __has_precision ? __buffer.__precision() : -1, __first);
case _Flags::_Type::__scientific_lower_case:
return __format_spec::__format_buffer_scientific_lower_case(__buffer, __value, __buffer.__precision(), __first);
case _Flags::_Type::__scientific_upper_case:
return __format_spec::__format_buffer_scientific_upper_case(__buffer, __value, __buffer.__precision(), __first);
case _Flags::_Type::__fixed_lower_case:
case _Flags::_Type::__fixed_upper_case:
return __format_spec::__format_buffer_fixed(__buffer, __value, __buffer.__precision(), __first);
case _Flags::_Type::__general_lower_case:
return __format_spec::__format_buffer_general_lower_case(__buffer, __value, __buffer.__precision(), __first);
case _Flags::_Type::__general_upper_case:
return __format_spec::__format_buffer_general_upper_case(__buffer, __value, __buffer.__precision(), __first);
default:
_LIBCPP_ASSERT(false, "The parser should have validated the type");
_LIBCPP_UNREACHABLE();
}
}
};
} //namespace __format_spec
template <__formatter::__char_type _CharT>
struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<float, _CharT>
: public __format_spec::__formatter_floating_point<_CharT> {};
template <__formatter::__char_type _CharT>
struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<double, _CharT>
: public __format_spec::__formatter_floating_point<_CharT> {};
template <__formatter::__char_type _CharT>
struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<long double, _CharT>
: public __format_spec::__formatter_floating_point<_CharT> {};
# endif // !defined(_LIBCPP_HAS_NO_CONCEPTS)
#endif //_LIBCPP_STD_VER > 17
_LIBCPP_END_NAMESPACE_STD
_LIBCPP_POP_MACROS
#endif // _LIBCPP___FORMAT_FORMATTER_FLOATING_POINT_H

View File

@ -82,7 +82,7 @@ _LIBCPP_BEGIN_NAMESPACE_STD
namespace __format_spec {
/** Wrapper around @ref to_chars, returning the output pointer. */
template <class _Tp>
template <integral _Tp>
_LIBCPP_HIDE_FROM_ABI char* __to_buffer(char* __first, char* __last,
_Tp __value, int __base) {
// TODO FMT Evaluate code overhead due to not calling the internal function

View File

@ -475,6 +475,21 @@ __parse_type(const _CharT* __begin, _Flags& __flags) {
return ++__begin;
}
/**
* Process the parsed alignment and zero-padding state of arithmetic types.
*
* [format.string.std]/13
* If the 0 character and an align option both appear, the 0 character is
* ignored.
*
* For the formatter a @ref __default alignment means zero-padding.
*/
_LIBCPP_HIDE_FROM_ABI constexpr void __process_arithmetic_alignment(_Flags& __flags) {
__flags.__zero_padding &= __flags.__alignment == _Flags::_Alignment::__default;
if (!__flags.__zero_padding && __flags.__alignment == _Flags::_Alignment::__default)
__flags.__alignment = _Flags::_Alignment::__right;
}
/**
* The parser for the std-format-spec.
*
@ -648,23 +663,9 @@ protected:
return __begin;
}
/**
* Handles the post-parsing updates for the integer types.
*
* Updates the zero-padding and alignment for integer types.
*
* [format.string.std]/13
* If the 0 character and an align option both appear, the 0 character is
* ignored.
*
* For the formatter a @ref __default alignment means zero-padding. Update
* the alignment based on parsed format string.
*/
/** Handles the post-parsing updates for the integer types. */
_LIBCPP_HIDE_FROM_ABI constexpr void __handle_integer() noexcept {
this->__zero_padding &= this->__alignment == _Flags::_Alignment::__default;
if (!this->__zero_padding &&
this->__alignment == _Flags::_Alignment::__default)
this->__alignment = _Flags::_Alignment::__right;
__process_arithmetic_alignment(static_cast<_Flags&>(*this));
}
/**
@ -701,7 +702,130 @@ protected:
}
};
// TODO FMT Add a parser for floating-point values.
/**
* The parser for the std-format-spec.
*
* This implements the parser for the floating-point types.
*
* See @ref __parser_string.
*/
template <class _CharT>
class _LIBCPP_TEMPLATE_VIS __parser_floating_point
: public __parser_width, // provides __width(|as_arg)
public __parser_precision, // provides __precision(|as_arg)
public __parser_fill_align<_CharT>, // provides __fill and uses __flags
public _Flags // provides __flags
{
public:
using char_type = _CharT;
/**
* The low-level std-format-spec parse function.
*
* @pre __begin points at the beginning of the std-format-spec. This means
* directly after the ':'.
* @pre The std-format-spec parses the entire input, or the first unmatched
* character is a '}'.
*
* @returns The iterator pointing at the last parsed character.
*/
_LIBCPP_HIDE_FROM_ABI constexpr auto parse(auto& __parse_ctx)
-> decltype(__parse_ctx.begin()) {
auto __it = __parse(__parse_ctx);
__process_arithmetic_alignment(static_cast<_Flags&>(*this));
__process_display_type();
return __it;
}
protected:
/**
* The low-level std-format-spec parse function.
*
* @pre __begin points at the beginning of the std-format-spec. This means
* directly after the ':'.
* @pre The std-format-spec parses the entire input, or the first unmatched
* character is a '}'.
*
* @returns The iterator pointing at the last parsed character.
*/
_LIBCPP_HIDE_FROM_ABI constexpr auto __parse(auto& __parse_ctx)
-> decltype(__parse_ctx.begin()) {
auto __begin = __parse_ctx.begin();
auto __end = __parse_ctx.end();
if (__begin == __end)
return __begin;
__begin = __parser_fill_align<_CharT>::__parse(__begin, __end,
static_cast<_Flags&>(*this));
if (__begin == __end)
return __begin;
__begin = __parse_sign(__begin, static_cast<_Flags&>(*this));
if (__begin == __end)
return __begin;
__begin = __parse_alternate_form(__begin, static_cast<_Flags&>(*this));
if (__begin == __end)
return __begin;
__begin = __parse_zero_padding(__begin, static_cast<_Flags&>(*this));
if (__begin == __end)
return __begin;
__begin = __parser_width::__parse(__begin, __end, __parse_ctx);
if (__begin == __end)
return __begin;
__begin = __parser_precision::__parse(__begin, __end, __parse_ctx);
if (__begin == __end)
return __begin;
__begin =
__parse_locale_specific_form(__begin, static_cast<_Flags&>(*this));
if (__begin == __end)
return __begin;
__begin = __parse_type(__begin, static_cast<_Flags&>(*this));
if (__begin != __end && *__begin != _CharT('}'))
__throw_format_error(
"The format-spec should consume the input or end with a '}'");
return __begin;
}
/** Processes the parsed std-format-spec based on the parsed display type. */
_LIBCPP_HIDE_FROM_ABI constexpr void __process_display_type() {
switch (this->__type) {
case _Flags::_Type::__default:
// When no precision specified then it keeps default since that
// formatting differs from the other types.
if (this->__has_precision_field())
this->__type = _Flags::_Type::__general_lower_case;
break;
case _Flags::_Type::__float_hexadecimal_lower_case:
case _Flags::_Type::__float_hexadecimal_upper_case:
// Precision specific behavior will be handled later.
break;
case _Flags::_Type::__scientific_lower_case:
case _Flags::_Type::__scientific_upper_case:
case _Flags::_Type::__fixed_lower_case:
case _Flags::_Type::__fixed_upper_case:
case _Flags::_Type::__general_lower_case:
case _Flags::_Type::__general_upper_case:
if (!this->__has_precision_field()) {
// Set the default precision for the call to to_chars.
this->__precision = 6;
this->__precision_as_arg = false;
}
break;
default:
__throw_format_error("The format-spec type has a type not supported for "
"a floating-point argument");
}
}
};
// TODO FMT Add a parser for pointer values.
/** Helper struct returned from @ref __get_string_alignment. */

View File

@ -277,6 +277,7 @@ namespace std {
#include <__format/formatter.h>
#include <__format/formatter_bool.h>
#include <__format/formatter_char.h>
#include <__format/formatter_floating_point.h>
#include <__format/formatter_integer.h>
#include <__format/formatter_string.h>
#include <__format/parser_std_format_spec.h>

View File

@ -503,6 +503,7 @@ module std [system] {
module formatter { private header "__format/formatter.h" }
module formatter_bool { private header "__format/formatter_bool.h" }
module formatter_char { private header "__format/formatter_char.h" }
module formatter_floating_point { private header "__format/formatter_floating_point.h" }
module formatter_integer { private header "__format/formatter_integer.h" }
module formatter_integral { private header "__format/formatter_integral.h" }
module formatter_string { private header "__format/formatter_string.h" }

View File

@ -0,0 +1,15 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// REQUIRES: modules-build
// WARNING: This test was generated by 'generate_private_header_tests.py'
// and should not be edited manually.
// expected-error@*:* {{use of private header from outside its module: '__format/formatter_floating_point.h'}}
#include <__format/formatter_floating_point.h>

View File

@ -0,0 +1,353 @@
//===----------------------------------------------------------------------===//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-no-concepts
// UNSUPPORTED: libcpp-has-no-incomplete-format
// <format>
// Tests the parsing of the format string as specified in [format.string.std].
// It validates whether the std-format-spec is valid for a floating-point type.
#include <format>
#include <cassert>
#ifndef _LIBCPP_HAS_NO_LOCALIZATION
# include <iostream>
#endif
#include "concepts_precision.h"
#include "test_macros.h"
#include "make_string.h"
#include "test_exception.h"
#define CSTR(S) MAKE_CSTRING(CharT, S)
using namespace std::__format_spec;
template <class CharT>
using Parser = __parser_floating_point<CharT>;
template <class CharT>
struct Expected {
CharT fill = CharT(' ');
_Flags::_Alignment alignment = _Flags::_Alignment::__right;
_Flags::_Sign sign = _Flags::_Sign::__default;
bool alternate_form = false;
bool zero_padding = false;
uint32_t width = 0;
bool width_as_arg = false;
uint32_t precision = std::__format::__number_max;
bool precision_as_arg = true;
bool locale_specific_form = false;
_Flags::_Type type = _Flags::_Type::__default;
};
template <class CharT>
constexpr void test(Expected<CharT> expected, size_t size, std::basic_string_view<CharT> fmt) {
// Initialize parser with sufficient arguments to avoid the parsing to fail
// due to insufficient arguments.
std::basic_format_parse_context<CharT> parse_ctx(fmt, std::__format::__number_max);
auto begin = parse_ctx.begin();
auto end = parse_ctx.end();
Parser<CharT> parser;
auto it = parser.parse(parse_ctx);
assert(begin == parse_ctx.begin());
assert(end == parse_ctx.end());
assert(begin + size == it);
assert(parser.__fill == expected.fill);
assert(parser.__alignment == expected.alignment);
assert(parser.__sign == expected.sign);
assert(parser.__alternate_form == expected.alternate_form);
assert(parser.__zero_padding == expected.zero_padding);
assert(parser.__width == expected.width);
assert(parser.__width_as_arg == expected.width_as_arg);
assert(parser.__precision == expected.precision);
assert(parser.__precision_as_arg == expected.precision_as_arg);
assert(parser.__locale_specific_form == expected.locale_specific_form);
assert(parser.__type == expected.type);
}
template <class CharT>
constexpr void test(Expected<CharT> expected, size_t size, const CharT* f) {
// The format-spec is valid if completely consumed or terminates at a '}'.
// The valid inputs all end with a '}'. The test is executed twice:
// - first with the terminating '}',
// - second consuming the entire input.
std::basic_string_view<CharT> fmt{f};
assert(fmt.back() == CharT('}') && "Pre-condition failure");
test(expected, size, fmt);
fmt.remove_suffix(1);
test(expected, size, fmt);
}
template <class CharT>
constexpr void test() {
Parser<CharT> parser;
assert(parser.__fill == CharT(' '));
assert(parser.__alignment == _Flags::_Alignment::__default);
assert(parser.__sign == _Flags::_Sign::__default);
assert(parser.__alternate_form == false);
assert(parser.__zero_padding == false);
assert(parser.__width == 0);
assert(parser.__width_as_arg == false);
assert(parser.__precision == std::__format::__number_max);
assert(parser.__precision_as_arg == true);
assert(parser.__locale_specific_form == false);
assert(parser.__type == _Flags::_Type::__default);
// Depending on whether or not a precision is specified the results differ.
// Table 65: Meaning of type options for floating-point types[tab:format.type.float]
test({}, 0, CSTR("}"));
test({.precision = 0, .precision_as_arg = false, .type = _Flags::_Type::__general_lower_case}, 2, CSTR(".0}"));
test({.precision = 1, .precision_as_arg = true, .type = _Flags::_Type::__general_lower_case}, 4, CSTR(".{1}}"));
test({.type = _Flags::_Type::__float_hexadecimal_lower_case}, 1, CSTR("a}"));
test({.type = _Flags::_Type::__float_hexadecimal_upper_case}, 1, CSTR("A}"));
test({.precision = 6, .precision_as_arg = false, .type = _Flags::_Type::__scientific_lower_case}, 1, CSTR("e}"));
test({.precision = 0, .precision_as_arg = false, .type = _Flags::_Type::__scientific_lower_case}, 3, CSTR(".0e}"));
test({.precision = 1, .precision_as_arg = true, .type = _Flags::_Type::__scientific_lower_case}, 5, CSTR(".{1}e}"));
test({.precision = 6, .precision_as_arg = false, .type = _Flags::_Type::__scientific_upper_case}, 1, CSTR("E}"));
test({.precision = 0, .precision_as_arg = false, .type = _Flags::_Type::__scientific_upper_case}, 3, CSTR(".0E}"));
test({.precision = 1, .precision_as_arg = true, .type = _Flags::_Type::__scientific_upper_case}, 5, CSTR(".{1}E}"));
test({.precision = 6, .precision_as_arg = false, .type = _Flags::_Type::__fixed_lower_case}, 1, CSTR("f}"));
test({.precision = 0, .precision_as_arg = false, .type = _Flags::_Type::__fixed_lower_case}, 3, CSTR(".0f}"));
test({.precision = 1, .precision_as_arg = true, .type = _Flags::_Type::__fixed_lower_case}, 5, CSTR(".{1}f}"));
test({.precision = 6, .precision_as_arg = false, .type = _Flags::_Type::__fixed_upper_case}, 1, CSTR("F}"));
test({.precision = 0, .precision_as_arg = false, .type = _Flags::_Type::__fixed_upper_case}, 3, CSTR(".0F}"));
test({.precision = 1, .precision_as_arg = true, .type = _Flags::_Type::__fixed_upper_case}, 5, CSTR(".{1}F}"));
test({.precision = 6, .precision_as_arg = false, .type = _Flags::_Type::__general_lower_case}, 1, CSTR("g}"));
test({.precision = 0, .precision_as_arg = false, .type = _Flags::_Type::__general_lower_case}, 3, CSTR(".0g}"));
test({.precision = 1, .precision_as_arg = true, .type = _Flags::_Type::__general_lower_case}, 5, CSTR(".{1}g}"));
test({.precision = 6, .precision_as_arg = false, .type = _Flags::_Type::__general_upper_case}, 1, CSTR("G}"));
test({.precision = 0, .precision_as_arg = false, .type = _Flags::_Type::__general_upper_case}, 3, CSTR(".0G}"));
test({.precision = 1, .precision_as_arg = true, .type = _Flags::_Type::__general_upper_case}, 5, CSTR(".{1}G}"));
// *** Align-fill ***
test({.alignment = _Flags::_Alignment::__left}, 1, CSTR("<}"));
test({.alignment = _Flags::_Alignment::__center}, 1, "^}");
test({.alignment = _Flags::_Alignment::__right}, 1, ">}");
test({.fill = CharT('L'), .alignment = _Flags::_Alignment::__left}, 2, CSTR("L<}"));
test({.fill = CharT('#'), .alignment = _Flags::_Alignment::__center}, 2, CSTR("#^}"));
test({.fill = CharT('0'), .alignment = _Flags::_Alignment::__right}, 2, CSTR("0>}"));
test_exception<Parser<CharT>>("The format-spec fill field contains an invalid character", CSTR("{<"));
test_exception<Parser<CharT>>("The format-spec fill field contains an invalid character", CSTR("}<"));
// *** Sign ***
test({.sign = _Flags::_Sign::__minus}, 1, CSTR("-}"));
test({.sign = _Flags::_Sign::__plus}, 1, CSTR("+}"));
test({.sign = _Flags::_Sign::__space}, 1, CSTR(" }"));
// *** Alternate form ***
test({.alternate_form = true}, 1, CSTR("#}"));
// *** Zero padding ***
// TODO FMT What to do with zero-padding without a width?
// [format.string.std]/13
// A zero (0) character preceding the width field pads the field with
// leading zeros (following any indication of sign or base) to the field
// width, except when applied to an infinity or NaN.
// Obviously it makes no sense, but should it be allowed or is it a format
// error?
test({.alignment = _Flags::_Alignment::__default, .zero_padding = true}, 1, CSTR("0}"));
test({.alignment = _Flags::_Alignment::__left, .zero_padding = false}, 2, CSTR("<0}"));
test({.alignment = _Flags::_Alignment::__center, .zero_padding = false}, 2, CSTR("^0}"));
test({.alignment = _Flags::_Alignment::__right, .zero_padding = false}, 2, CSTR(">0}"));
// *** Width ***
test({.width = 0, .width_as_arg = false}, 0, CSTR("}"));
test({.width = 1, .width_as_arg = false}, 1, CSTR("1}"));
test({.width = 10, .width_as_arg = false}, 2, CSTR("10}"));
test({.width = 1000, .width_as_arg = false}, 4, CSTR("1000}"));
test({.width = 1000000, .width_as_arg = false}, 7, CSTR("1000000}"));
test({.width = 0, .width_as_arg = true}, 2, CSTR("{}}"));
test({.width = 0, .width_as_arg = true}, 3, CSTR("{0}}"));
test({.width = 1, .width_as_arg = true}, 3, CSTR("{1}}"));
test_exception<Parser<CharT>>("A format-spec width field shouldn't have a leading zero", CSTR("00"));
static_assert(std::__format::__number_max == 2'147'483'647, "Update the assert and the test.");
test({.width = 2'147'483'647, .width_as_arg = false}, 10, CSTR("2147483647}"));
test_exception<Parser<CharT>>("The numeric value of the format-spec is too large", CSTR("2147483648"));
test_exception<Parser<CharT>>("The numeric value of the format-spec is too large", CSTR("5000000000"));
test_exception<Parser<CharT>>("The numeric value of the format-spec is too large", CSTR("10000000000"));
test_exception<Parser<CharT>>("End of input while parsing format-spec arg-id", CSTR("{"));
test_exception<Parser<CharT>>("Invalid arg-id", CSTR("{0"));
test_exception<Parser<CharT>>("The arg-id of the format-spec starts with an invalid character", CSTR("{a"));
test_exception<Parser<CharT>>("Invalid arg-id", CSTR("{1"));
test_exception<Parser<CharT>>("Invalid arg-id", CSTR("{9"));
test_exception<Parser<CharT>>("Invalid arg-id", CSTR("{9:"));
test_exception<Parser<CharT>>("Invalid arg-id", CSTR("{9a"));
static_assert(std::__format::__number_max == 2'147'483'647, "Update the assert and the test.");
// Note the static_assert tests whether the arg-id is valid.
// Therefore the following should be true arg-id < __format::__number_max.
test({.width = 2'147'483'646, .width_as_arg = true}, 12, CSTR("{2147483646}}"));
test_exception<Parser<CharT>>("The numeric value of the format-spec is too large", CSTR("{2147483648}"));
test_exception<Parser<CharT>>("The numeric value of the format-spec is too large", CSTR("{5000000000}"));
test_exception<Parser<CharT>>("The numeric value of the format-spec is too large", CSTR("{10000000000}"));
// *** Precision ***
test({.precision = 0, .precision_as_arg = false, .type = _Flags::_Type::__general_lower_case}, 2, CSTR(".0}"));
test({.precision = 1, .precision_as_arg = false, .type = _Flags::_Type::__general_lower_case}, 2, CSTR(".1}"));
test({.precision = 10, .precision_as_arg = false, .type = _Flags::_Type::__general_lower_case}, 3, CSTR(".10}"));
test({.precision = 1000, .precision_as_arg = false, .type = _Flags::_Type::__general_lower_case}, 5, CSTR(".1000}"));
test({.precision = 1000000, .precision_as_arg = false, .type = _Flags::_Type::__general_lower_case}, 8,
CSTR(".1000000}"));
test({.precision = 0, .precision_as_arg = true, .type = _Flags::_Type::__general_lower_case}, 3, CSTR(".{}}"));
test({.precision = 0, .precision_as_arg = true, .type = _Flags::_Type::__general_lower_case}, 4, CSTR(".{0}}"));
test({.precision = 1, .precision_as_arg = true, .type = _Flags::_Type::__general_lower_case}, 4, CSTR(".{1}}"));
test_exception<Parser<CharT>>("The format-spec precision field doesn't contain a value or arg-id", CSTR(".a"));
test_exception<Parser<CharT>>("The format-spec precision field doesn't contain a value or arg-id", CSTR(".:"));
static_assert(std::__format::__number_max == 2'147'483'647, "Update the assert and the test.");
test({.precision = 2'147'483'647, .precision_as_arg = false, .type = _Flags::_Type::__general_lower_case}, 11,
CSTR(".2147483647}"));
test_exception<Parser<CharT>>("The numeric value of the format-spec is too large", CSTR(".2147483648"));
test_exception<Parser<CharT>>("The numeric value of the format-spec is too large", CSTR(".5000000000"));
test_exception<Parser<CharT>>("The numeric value of the format-spec is too large", CSTR(".10000000000"));
test_exception<Parser<CharT>>("End of input while parsing format-spec arg-id", CSTR(".{"));
test_exception<Parser<CharT>>("Invalid arg-id", CSTR(".{0"));
test_exception<Parser<CharT>>("The arg-id of the format-spec starts with an invalid character", CSTR(".{a"));
test_exception<Parser<CharT>>("Invalid arg-id", CSTR(".{1"));
test_exception<Parser<CharT>>("Invalid arg-id", CSTR(".{9"));
test_exception<Parser<CharT>>("Invalid arg-id", CSTR(".{9:"));
test_exception<Parser<CharT>>("Invalid arg-id", CSTR(".{9a"));
static_assert(std::__format::__number_max == 2'147'483'647, "Update the assert and the test.");
// Note the static_assert tests whether the arg-id is valid.
// Therefore the following should be true arg-id < __format::__number_max.
test({.precision = 2'147'483'646, .precision_as_arg = true, .type = _Flags::_Type::__general_lower_case}, 13,
CSTR(".{2147483646}}"));
test_exception<Parser<CharT>>("The numeric value of the format-spec is too large", CSTR(".{2147483648}"));
test_exception<Parser<CharT>>("The numeric value of the format-spec is too large", CSTR(".{5000000000}"));
test_exception<Parser<CharT>>("The numeric value of the format-spec is too large", CSTR(".{10000000000}"));
// *** Width & Precision ***
test({.width = 1,
.width_as_arg = false,
.precision = 0,
.precision_as_arg = false,
.type = _Flags::_Type::__general_lower_case},
3, CSTR("1.0}"));
test({.width = 0,
.width_as_arg = true,
.precision = 1,
.precision_as_arg = true,
.type = _Flags::_Type::__general_lower_case},
5, CSTR("{}.{}}"));
test({.width = 10,
.width_as_arg = true,
.precision = 9,
.precision_as_arg = true,
.type = _Flags::_Type::__general_lower_case},
8, CSTR("{10}.{9}}"));
// *** Locale-specific form ***
test({.locale_specific_form = true}, 1, CSTR("L}"));
// *** Type ***
{
const char* unsuported_type = "The format-spec type has a type not supported for a floating-point argument";
const char* not_a_type = "The format-spec should consume the input or end with a '}'";
test({.type = _Flags::_Type::__float_hexadecimal_upper_case}, 1, CSTR("A}"));
test_exception<Parser<CharT>>(unsuported_type, CSTR("B}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("C}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("D}"));
test({.precision = 6, .precision_as_arg = false, .type = _Flags::_Type::__scientific_upper_case}, 1, CSTR("E}"));
test({.precision = 6, .precision_as_arg = false, .type = _Flags::_Type::__fixed_upper_case}, 1, CSTR("F}"));
test({.precision = 6, .precision_as_arg = false, .type = _Flags::_Type::__general_upper_case}, 1, CSTR("G}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("H}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("I}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("J}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("K}"));
test({.locale_specific_form = true}, 1, CSTR("L}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("M}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("N}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("O}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("P}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("Q}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("R}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("S}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("T}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("U}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("V}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("W}"));
test_exception<Parser<CharT>>(unsuported_type, CSTR("X}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("Y}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("Z}"));
test({.type = _Flags::_Type::__float_hexadecimal_lower_case}, 1, CSTR("a}"));
test_exception<Parser<CharT>>(unsuported_type, CSTR("b}"));
test_exception<Parser<CharT>>(unsuported_type, CSTR("c}"));
test_exception<Parser<CharT>>(unsuported_type, CSTR("d}"));
test({.precision = 6, .precision_as_arg = false, .type = _Flags::_Type::__scientific_lower_case}, 1, CSTR("e}"));
test({.precision = 6, .precision_as_arg = false, .type = _Flags::_Type::__fixed_lower_case}, 1, CSTR("f}"));
test({.precision = 6, .precision_as_arg = false, .type = _Flags::_Type::__general_lower_case}, 1, CSTR("g}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("h}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("i}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("j}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("k}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("l}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("m}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("n}"));
test_exception<Parser<CharT>>(unsuported_type, CSTR("o}"));
test_exception<Parser<CharT>>(unsuported_type, CSTR("p}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("q}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("r}"));
test_exception<Parser<CharT>>(unsuported_type, CSTR("s}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("t}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("u}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("v}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("w}"));
test_exception<Parser<CharT>>(unsuported_type, CSTR("x}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("y}"));
test_exception<Parser<CharT>>(not_a_type, CSTR("z}"));
}
// **** General ***
test_exception<Parser<CharT>>("The format-spec should consume the input or end with a '}'", CSTR("ss"));
}
constexpr bool test() {
test<char>();
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
test<wchar_t>();
#endif
return true;
}
int main(int, char**) {
#if !defined(_WIN32) && !defined(_AIX)
// Make sure the parsers match the expectations. The layout of the
// subobjects is chosen to minimize the size required.
static_assert(sizeof(Parser<char>) == 3 * sizeof(uint32_t));
# ifndef TEST_HAS_NO_WIDE_CHARACTERS
static_assert(sizeof(Parser<wchar_t>) == (sizeof(wchar_t) <= 2 ? 3 * sizeof(uint32_t) : 4 * sizeof(uint32_t)));
# endif
#endif
test();
static_assert(test());
return 0;
}

View File

@ -8,7 +8,6 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-no-concepts
// UNSUPPORTED: libcpp-has-no-incomplete-format
// UNSUPPORTED: LIBCXX-DEBUG-FIXME
// <format>
@ -25,11 +24,14 @@
// - double
// - long double
// TODO FMT Enable after floating-point support has been enabled
#if 0
#include <format>
#include <array>
#include <cassert>
#include <cmath>
#include <charconv>
#include <concepts>
#include <string>
#include <type_traits>
#include "test_macros.h"
@ -37,9 +39,8 @@
#define STR(S) MAKE_STRING(CharT, S)
template <class StringViewT, class ArithmeticT>
void test(StringViewT fmt, ArithmeticT arg) {
using CharT = typename StringViewT::value_type;
template <class CharT, class ArithmeticT>
void test(std::basic_string_view<CharT> fmt, ArithmeticT arg, std::basic_string<CharT> expected) {
auto parse_ctx = std::basic_format_parse_context<CharT>(fmt);
std::formatter<ArithmeticT, CharT> formatter;
static_assert(std::semiregular<decltype(formatter)>);
@ -51,15 +52,19 @@ void test(StringViewT fmt, ArithmeticT arg) {
auto out = std::back_inserter(result);
using FormatCtxT = std::basic_format_context<decltype(out), CharT>;
auto format_ctx = std::__format_context_create<decltype(out), CharT>(
out, std::make_format_args<FormatCtxT>(arg));
auto format_ctx = std::__format_context_create<decltype(out), CharT>(out, std::make_format_args<FormatCtxT>(arg));
formatter.format(arg, format_ctx);
std::string expected = std::to_string(arg);
assert(result == std::basic_string<CharT>(expected.begin(), expected.end()));
if (expected.empty()) {
std::array<char, 128> buffer;
expected.append(buffer.begin(), std::to_chars(buffer.begin(), buffer.end(), arg).ptr);
}
assert(result == expected);
}
template <class StringT, class ArithmeticT>
void test_termination_condition(StringT f, ArithmeticT arg) {
void test_termination_condition(StringT f, ArithmeticT arg, StringT expected = {}) {
// The format-spec is valid if completely consumed or terminates at a '}'.
// The valid inputs all end with a '}'. The test is executed twice:
// - first with the terminating '}',
@ -68,40 +73,398 @@ void test_termination_condition(StringT f, ArithmeticT arg) {
std::basic_string_view<CharT> fmt{f};
assert(fmt.back() == CharT('}') && "Pre-condition failure");
test(fmt, arg);
test(fmt, arg, expected);
fmt.remove_suffix(1);
test(fmt, arg);
test(fmt, arg, expected);
}
template <class CharT, class ArithmeticT>
void test_hex_lower_case_precision(ArithmeticT value) {
std::array<char, 25'000> buffer;
char* end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::hex, 20'000).ptr;
test_termination_condition(STR(".20000a}"), value, std::basic_string<CharT>{buffer.begin(), end});
size_t size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000a}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000a}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000a}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000a}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#ifndef _LIBCPP_HAS_NO_LOCALIZATION
end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::hex, 20'000).ptr;
test_termination_condition(STR(".20000La}"), value, std::basic_string<CharT>{buffer.begin(), end});
size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000La}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000La}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000La}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000La}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#endif
}
template <class CharT, class ArithmeticT>
void test_hex_upper_case_precision(ArithmeticT value) {
std::array<char, 25'000> buffer;
char* end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::hex, 20'000).ptr;
std::transform(buffer.begin(), end, buffer.begin(), [](char c) { return std::toupper(c); });
test_termination_condition(STR(".20000A}"), value, std::basic_string<CharT>{buffer.begin(), end});
size_t size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000A}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000A}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000A}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000A}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#ifndef _LIBCPP_HAS_NO_LOCALIZATION
end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::hex, 20'000).ptr;
std::transform(buffer.begin(), end, buffer.begin(), [](char c) { return std::toupper(c); });
test_termination_condition(STR(".20000LA}"), value, std::basic_string<CharT>{buffer.begin(), end});
size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000LA}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000LA}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000LA}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000LA}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#endif
}
template <class CharT, class ArithmeticT>
void test_scientific_lower_case_precision(ArithmeticT value) {
std::array<char, 25'000> buffer;
char* end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::scientific, 20'000).ptr;
test_termination_condition(STR(".20000e}"), value, std::basic_string<CharT>{buffer.begin(), end});
size_t size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000e}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000e}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000e}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000e}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#ifndef _LIBCPP_HAS_NO_LOCALIZATION
end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::scientific, 20'000).ptr;
test_termination_condition(STR(".20000Le}"), value, std::basic_string<CharT>{buffer.begin(), end});
size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000Le}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000Le}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000Le}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000Le}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#endif
}
template <class CharT, class ArithmeticT>
void test_scientific_upper_case_precision(ArithmeticT value) {
std::array<char, 25'000> buffer;
char* end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::scientific, 20'000).ptr;
std::transform(buffer.begin(), end, buffer.begin(), [](char c) { return std::toupper(c); });
test_termination_condition(STR(".20000E}"), value, std::basic_string<CharT>{buffer.begin(), end});
size_t size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000E}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000E}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000E}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000E}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#ifndef _LIBCPP_HAS_NO_LOCALIZATION
end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::scientific, 20'000).ptr;
std::transform(buffer.begin(), end, buffer.begin(), [](char c) { return std::toupper(c); });
test_termination_condition(STR(".20000LE}"), value, std::basic_string<CharT>{buffer.begin(), end});
size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000LE}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000LE}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000LE}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000LE}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#endif
}
template <class CharT, class ArithmeticT>
void test_fixed_lower_case_precision(ArithmeticT value) {
std::array<char, 25'000> buffer;
char* end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::fixed, 20'000).ptr;
test_termination_condition(STR(".20000f}"), value, std::basic_string<CharT>{buffer.begin(), end});
size_t size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000f}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000f}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000f}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000f}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#ifndef _LIBCPP_HAS_NO_LOCALIZATION
end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::fixed, 20'000).ptr;
test_termination_condition(STR(".20000Lf}"), value, std::basic_string<CharT>{buffer.begin(), end});
size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000Lf}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000Lf}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000Lf}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000Lf}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#endif
}
template <class CharT, class ArithmeticT>
void test_fixed_upper_case_precision(ArithmeticT value) {
std::array<char, 25'000> buffer;
char* end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::fixed, 20'000).ptr;
std::transform(buffer.begin(), end, buffer.begin(), [](char c) { return std::toupper(c); });
test_termination_condition(STR(".20000F}"), value, std::basic_string<CharT>{buffer.begin(), end});
size_t size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000F}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000F}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000F}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000F}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#ifndef _LIBCPP_HAS_NO_LOCALIZATION
end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::fixed, 20'000).ptr;
std::transform(buffer.begin(), end, buffer.begin(), [](char c) { return std::toupper(c); });
test_termination_condition(STR(".20000LF}"), value, std::basic_string<CharT>{buffer.begin(), end});
size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000LF}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000LF}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000LF}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000LF}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#endif
}
template <class CharT, class ArithmeticT>
void test_general_lower_case_precision(ArithmeticT value) {
std::array<char, 25'000> buffer;
char* end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::general, 20'000).ptr;
test_termination_condition(STR(".20000g}"), value, std::basic_string<CharT>{buffer.begin(), end});
size_t size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000g}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000g}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000g}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000g}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#ifndef _LIBCPP_HAS_NO_LOCALIZATION
end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::general, 20'000).ptr;
test_termination_condition(STR(".20000Lg}"), value, std::basic_string<CharT>{buffer.begin(), end});
size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000Lg}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000Lg}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000Lg}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000Lg}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#endif
}
template <class CharT, class ArithmeticT>
void test_general_upper_case_precision(ArithmeticT value) {
std::array<char, 25'000> buffer;
char* end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::general, 20'000).ptr;
std::transform(buffer.begin(), end, buffer.begin(), [](char c) { return std::toupper(c); });
test_termination_condition(STR(".20000G}"), value, std::basic_string<CharT>{buffer.begin(), end});
size_t size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000G}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000G}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000G}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000G}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#ifndef _LIBCPP_HAS_NO_LOCALIZATION
end = std::to_chars(buffer.begin(), buffer.end(), value, std::chars_format::general, 20'000).ptr;
std::transform(buffer.begin(), end, buffer.begin(), [](char c) { return std::toupper(c); });
test_termination_condition(STR(".20000LG}"), value, std::basic_string<CharT>{buffer.begin(), end});
size = buffer.end() - end;
std::fill_n(end, size, '#');
test_termination_condition(STR("#<25000.20000LG}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - (size / 2), buffer.end());
test_termination_condition(STR("#^25000.20000LG}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::rotate(buffer.begin(), buffer.end() - ((size + 1) / 2), buffer.end());
test_termination_condition(STR("#>25000.20000LG}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
std::fill_n(buffer.begin(), size, '0');
if (std::signbit(value)) {
buffer[0] = '-';
buffer[size] = '0';
}
test_termination_condition(STR("025000.20000LG}"), value, std::basic_string<CharT>{buffer.begin(), buffer.end()});
#endif
}
template <class CharT, class ArithmeticT>
void test_value(ArithmeticT value) {
test_hex_lower_case_precision<CharT>(value);
test_hex_upper_case_precision<CharT>(value);
test_scientific_lower_case_precision<CharT>(value);
test_scientific_upper_case_precision<CharT>(value);
test_fixed_lower_case_precision<CharT>(value);
test_fixed_upper_case_precision<CharT>(value);
test_general_lower_case_precision<CharT>(value);
test_general_upper_case_precision<CharT>(value);
}
template <class ArithmeticT, class CharT>
void test_special_values() {
using A = ArithmeticT;
test_value<CharT>(-std::numeric_limits<A>::max());
test_value<CharT>(A(-1.0));
test_value<CharT>(-std::numeric_limits<A>::min());
test_value<CharT>(-std::numeric_limits<A>::denorm_min());
test_value<CharT>(A(-0.0));
test_value<CharT>(A(0.0));
test_value<CharT>(std::numeric_limits<A>::denorm_min());
test_value<CharT>(A(1.0));
test_value<CharT>(std::numeric_limits<A>::min());
test_value<CharT>(std::numeric_limits<A>::max());
}
template <class ArithmeticT, class CharT>
void test_float_type() {
using A = ArithmeticT;
test_termination_condition(STR("}"), A(-std::numeric_limits<float>::max()));
test_termination_condition(STR("}"), A(-std::numeric_limits<float>::min()));
test_termination_condition(STR("}"), A(-0.0));
test_termination_condition(STR("}"), A(0.0));
test_termination_condition(STR("}"), A(std::numeric_limits<float>::min()));
test_termination_condition(STR("}"), A(std::numeric_limits<float>::max()));
if (sizeof(A) > sizeof(float)) {
test_termination_condition(STR("}"),
A(-std::numeric_limits<double>::max()));
test_termination_condition(STR("}"),
A(-std::numeric_limits<double>::min()));
test_termination_condition(STR("}"), A(-std::numeric_limits<double>::max()));
test_termination_condition(STR("}"), A(-std::numeric_limits<double>::min()));
test_termination_condition(STR("}"), A(std::numeric_limits<double>::min()));
test_termination_condition(STR("}"), A(std::numeric_limits<double>::max()));
}
if (sizeof(A) > sizeof(double)) {
test_termination_condition(STR("}"),
A(-std::numeric_limits<long double>::max()));
test_termination_condition(STR("}"),
A(-std::numeric_limits<long double>::min()));
test_termination_condition(STR("}"),
A(std::numeric_limits<long double>::min()));
test_termination_condition(STR("}"),
A(std::numeric_limits<long double>::max()));
test_termination_condition(STR("}"), A(-std::numeric_limits<long double>::max()));
test_termination_condition(STR("}"), A(-std::numeric_limits<long double>::min()));
test_termination_condition(STR("}"), A(std::numeric_limits<long double>::min()));
test_termination_condition(STR("}"), A(std::numeric_limits<long double>::max()));
}
// TODO FMT Also test with special floating point values: +/-Inf NaN.
// The results of inf and nan may differ from the result of to_chars.
test_termination_condition(STR("}"), A(-std::numeric_limits<A>::infinity()), STR("-inf"));
test_termination_condition(STR("}"), A(std::numeric_limits<A>::infinity()), STR("inf"));
A nan = std::numeric_limits<A>::quiet_NaN();
test_termination_condition(STR("}"), std::copysign(nan, -1.0), STR("-nan"));
test_termination_condition(STR("}"), nan, STR("nan"));
// TODO FMT Enable long double testing
if constexpr (!std::same_as<A, long double>)
test_special_values<A, CharT>();
}
template <class CharT>
@ -119,6 +482,3 @@ int main(int, char**) {
return 0;
}
#else
int main(int, char**) { return 0; }
#endif