[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:
parent
50999e82e8
commit
db2944e34b
|
@ -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();
|
||||
}
|
|
@ -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.
|
|
@ -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|",""
|
||||
"","","","","",""
|
||||
|
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue