diff --git a/libcxx/benchmarks/FormatItoa.h b/libcxx/benchmarks/FormatItoa.h new file mode 100644 --- /dev/null +++ b/libcxx/benchmarks/FormatItoa.h @@ -0,0 +1,97 @@ +//===----------------------------------------------------------------------===// +// 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 FORMATITOA_H +#define FORMATITOA_H + +#include <__format_itoa> + +#include "benchmark/benchmark.h" +#include "test_macros.h" + +template +struct digits; + +/* clang-format off */ +template<> struct digits { static constexpr int max = 10; }; +template<> struct digits { static constexpr int max = 10; }; +template<> struct digits { static constexpr int max = 19; }; +template<> struct digits { static constexpr int max = 20; }; +template<> struct digits<__int128_t> { static constexpr int max = 39; }; +template<> struct digits<__uint128_t> { static constexpr int max = 39; }; +/* clang-format on */ + +constexpr size_t batch_size = 10000; + +template +inline constexpr int digits_max = digits::max; + +template +constexpr std::array create_array_zero() { + std::array result; + std::fill(result.begin(), result.end(), T(0)); + return result; +} + +template +class sign_adjuster; + +// TODO FMT Use concept std::unsigned_integral +template +class sign_adjuster && + std::is_unsigned_v > > { +public: + constexpr T operator()(T value) { return value; } +}; + +// TODO FMT Use concept std::signed_integral +template +class sign_adjuster && + std::is_signed_v > > { +public: + constexpr T operator()(T value) { + change = !change; + if (change) + return -value; + return value; + } + +private: + bool change{true}; +}; +template +constexpr std::array create_array(int digits) { + constexpr auto lut = + std::__itoa::__create_lut_pow10 >(); + + assert(digits <= digits_max); + + T min = lut[digits - 1]; + T max = digits == digits_max ? std::numeric_limits::max() + : (lut[digits] - 1); + assert(min <= max); + + T step = (max - min) / T(batch_size); + T value = min; + std::array result; + sign_adjuster adjuster; + if (step) + for (auto& r : result) { + r = adjuster(value); + value += step; + } + else + for (auto& r : result) { + r = adjuster(value); + if (value == max) + value = min; + else + ++value; + } + return result; +} +#endif diff --git a/libcxx/benchmarks/format_itoa_character_width.bench.cpp b/libcxx/benchmarks/format_itoa_character_width.bench.cpp new file mode 100644 --- /dev/null +++ b/libcxx/benchmarks/format_itoa_character_width.bench.cpp @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// 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 "FormatItoa.h" + +template +static void BM_format_character_width_base_10(benchmark::State& state) { + std::array values = create_array(state.range(0)); + while (state.KeepRunningBatch(batch_size)) + for (auto value : values) + benchmark::DoNotOptimize(std::__character_width_base_10(value)); +} +BENCHMARK_TEMPLATE(BM_format_character_width_base_10, uint32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_character_width_base_10, int32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_character_width_base_10, uint64_t) + ->DenseRange(1, digits_max); +BENCHMARK_TEMPLATE(BM_format_character_width_base_10, int64_t) + ->DenseRange(1, digits_max, 1); +#ifndef _LIBCPP_HAS_NO_INT128 +BENCHMARK_TEMPLATE(BM_format_character_width_base_10, __uint128_t) + ->DenseRange(1, digits_max<__uint128_t>, 1); +BENCHMARK_TEMPLATE(BM_format_character_width_base_10, __int128_t) + ->DenseRange(1, digits_max<__int128_t>, 1); +#endif + +int main(int argc, char** argv) { + benchmark::Initialize(&argc, argv); + if (benchmark::ReportUnrecognizedArguments(argc, argv)) + return 1; + + benchmark::RunSpecifiedBenchmarks(); +} diff --git a/libcxx/benchmarks/format_itoa_to_chars_base_10.bench.cpp b/libcxx/benchmarks/format_itoa_to_chars_base_10.bench.cpp new file mode 100644 --- /dev/null +++ b/libcxx/benchmarks/format_itoa_to_chars_base_10.bench.cpp @@ -0,0 +1,121 @@ +//===----------------------------------------------------------------------===// +// 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 "FormatItoa.h" + +#include + +template +static void BM_to_chars_base_10(benchmark::State& state) { + std::array values = create_array(state.range(0)); + char buffer[128]; + while (state.KeepRunningBatch(batch_size)) + for (auto value : values) + benchmark::DoNotOptimize(std::to_chars(buffer, &buffer[128], value)); +} + +BENCHMARK_TEMPLATE(BM_to_chars_base_10, char, uint32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_to_chars_base_10, char, int32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_to_chars_base_10, char, uint64_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_to_chars_base_10, char, int64_t) + ->DenseRange(1, digits_max, 1); + +template +static void BM_format_to_chars_base_10(benchmark::State& state) { + std::array values = create_array(state.range(0)); + char buffer[128]; + while (state.KeepRunningBatch(batch_size)) + for (auto value : values) + benchmark::DoNotOptimize(std::__to_chars_base_10(buffer, value)); +} +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char, uint32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char, int32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char, uint64_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char, int64_t) + ->DenseRange(1, digits_max, 1); +#ifndef _LIBCPP_HAS_NO_INT128 +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char, __uint128_t) + ->DenseRange(1, digits_max<__uint128_t>, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char, __int128_t) + ->DenseRange(1, digits_max<__int128_t>, 1); +#endif +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, wchar_t, uint32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, wchar_t, int32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, wchar_t, uint64_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, wchar_t, int64_t) + ->DenseRange(1, digits_max, 1); +#ifndef _LIBCPP_HAS_NO_INT128 +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, wchar_t, __uint128_t) + ->DenseRange(1, digits_max<__uint128_t>, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, wchar_t, __int128_t) + ->DenseRange(1, digits_max<__int128_t>, 1); +#endif + +#ifndef _LIBCPP_NO_HAS_CHAR8_T +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char8_t, uint32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char8_t, int32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char8_t, uint64_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char8_t, int64_t) + ->DenseRange(1, digits_max, 1); +#ifndef _LIBCPP_HAS_NO_INT128 +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char8_t, __uint128_t) + ->DenseRange(1, digits_max<__uint128_t>, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char8_t, __int128_t) + ->DenseRange(1, digits_max<__int128_t>, 1); +#endif +#endif + +#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char16_t, uint32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char16_t, int32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char16_t, uint64_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char16_t, int64_t) + ->DenseRange(1, digits_max, 1); +#ifndef _LIBCPP_HAS_NO_INT128 +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char16_t, __uint128_t) + ->DenseRange(1, digits_max<__uint128_t>, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char16_t, __int128_t) + ->DenseRange(1, digits_max<__int128_t>, 1); +#endif +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char32_t, uint32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char32_t, int32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char32_t, uint64_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char32_t, int64_t) + ->DenseRange(1, digits_max, 1); +#ifndef _LIBCPP_HAS_NO_INT128 +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char32_t, __uint128_t) + ->DenseRange(1, digits_max<__uint128_t>, 1); +BENCHMARK_TEMPLATE(BM_format_to_chars_base_10, char32_t, __int128_t) + ->DenseRange(1, digits_max<__int128_t>, 1); +#endif +#endif + +int main(int argc, char** argv) { + benchmark::Initialize(&argc, argv); + if (benchmark::ReportUnrecognizedArguments(argc, argv)) + return 1; + + benchmark::RunSpecifiedBenchmarks(); +} diff --git a/libcxx/benchmarks/format_itoa_to_string.bench.cpp b/libcxx/benchmarks/format_itoa_to_string.bench.cpp new file mode 100644 --- /dev/null +++ b/libcxx/benchmarks/format_itoa_to_string.bench.cpp @@ -0,0 +1,116 @@ +//===----------------------------------------------------------------------===// +// 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 "FormatItoa.h" + +template +static void BM_to_string(benchmark::State& state) { + std::array values = create_array(state.range(0)); + while (state.KeepRunningBatch(batch_size)) + for (auto value : values) + benchmark::DoNotOptimize(std::to_string(value)); +} +BENCHMARK_TEMPLATE(BM_to_string, char, uint32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_to_string, char, int32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_to_string, char, uint64_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_to_string, char, int64_t) + ->DenseRange(1, digits_max, 1); + +template +static void BM_to_wstring(benchmark::State& state) { + std::array values = create_array(state.range(0)); + while (state.KeepRunningBatch(batch_size)) + for (auto value : values) + benchmark::DoNotOptimize(std::to_wstring(value)); +} +BENCHMARK_TEMPLATE(BM_to_wstring, wchar_t, uint32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_to_wstring, wchar_t, int32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_to_wstring, wchar_t, uint64_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_to_wstring, wchar_t, int64_t) + ->DenseRange(1, digits_max, 1); + +template +static void BM_format_to_basic_string(benchmark::State& state) { + std::array values = create_array(state.range(0)); + while (state.KeepRunningBatch(batch_size)) + for (auto value : values) + benchmark::DoNotOptimize(std::__to_basic_string(value)); +} +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char, uint32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char, int32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char, uint64_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char, int64_t) + ->DenseRange(1, digits_max, 1); +#ifndef _LIBCPP_HAS_NO_INT128 +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char, __uint128_t) + ->DenseRange(1, digits_max<__uint128_t>, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char, __int128_t) + ->DenseRange(1, digits_max<__int128_t>, 1); +#endif + +BENCHMARK_TEMPLATE(BM_format_to_basic_string, wchar_t, uint32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, wchar_t, int32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, wchar_t, uint64_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, wchar_t, int64_t) + ->DenseRange(1, digits_max, 1); +#ifndef _LIBCPP_HAS_NO_INT128 +BENCHMARK_TEMPLATE(BM_format_to_basic_string, wchar_t, __uint128_t) + ->DenseRange(1, digits_max<__uint128_t>, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, wchar_t, __int128_t) + ->DenseRange(1, digits_max<__int128_t>, 1); +#endif + +#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char16_t, uint32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char16_t, int32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char16_t, uint64_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char16_t, int64_t) + ->DenseRange(1, digits_max, 1); +#ifndef _LIBCPP_HAS_NO_INT128 +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char16_t, __uint128_t) + ->DenseRange(1, digits_max<__uint128_t>, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char16_t, __int128_t) + ->DenseRange(1, digits_max<__int128_t>, 1); +#endif +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char32_t, uint32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char32_t, int32_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char32_t, uint64_t) + ->DenseRange(1, digits_max, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char32_t, int64_t) + ->DenseRange(1, digits_max, 1); +#ifndef _LIBCPP_HAS_NO_INT128 +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char32_t, __uint128_t) + ->DenseRange(1, digits_max<__uint128_t>, 1); +BENCHMARK_TEMPLATE(BM_format_to_basic_string, char32_t, __int128_t) + ->DenseRange(1, digits_max<__int128_t>, 1); +#endif +#endif + +int main(int argc, char** argv) { + benchmark::Initialize(&argc, argv); + if (benchmark::ReportUnrecognizedArguments(argc, argv)) + return 1; + + benchmark::RunSpecifiedBenchmarks(); +} diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -6,6 +6,7 @@ __bsd_locale_fallbacks.h __debug __errc + __format_itoa __functional_03 __functional_base __functional_base_03 diff --git a/libcxx/include/__format_itoa b/libcxx/include/__format_itoa new file mode 100644 --- /dev/null +++ b/libcxx/include/__format_itoa @@ -0,0 +1,414 @@ +// -*- C++ -*- +//===------------------------ __format_itoa -------------------------------===// +// +// 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_ITOA +#define _LIBCPP___FORMAT_ITOA + +/** + * Helper header for convertion integrals to strings. + * + * This duplicates some of the effort of std::to_chars but this version has a + * slightly different goal. The goal is to offer routines to aid the format + * header. The header requires C++20 support due to using the bit header. + * + * The unit test can be found there. (libcxx/test/std/utilities/format/__itoa) + * + * The number of digits available for the various base types are: + * type | digits full | digits partial + * ------------------------------------------ + * int32_t* | 9 | 10 + * uint32_t | 9 | 10 + * int64_t* | 18 | 19 + * uint64_t | 19 | 20 + * __int128_t* | 38 | 39 + * __uint128_t | 38 | 39 + * + * *) An additional character may be required for the sign. + * + * TODO FMT Try to merge this code with the code used for std::to_chars and + * std::to_string. + */ + +#include <__config> +#include +#include +#include +#include +#include +#include + +#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 Copy the fixes from D93166 to avoid building on older Clang versions. +// +// + +/** The unsigned integral types supported for the __itoa related functions. */ +template +concept __uintX_t = + _VSTD::same_as<_Tp, uint8_t> || _VSTD::same_as<_Tp, uint16_t> || + _VSTD::same_as<_Tp, uint32_t> || _VSTD::same_as<_Tp, uint64_t> +#ifndef _LIBCPP_HAS_NO_INT128 + || _VSTD::same_as<_Tp, __uint128_t> +#endif + ; + +/** The signed integral types supported for the __itoa related functions. */ +template +concept __intX_t = + _VSTD::same_as<_Tp, int8_t> || _VSTD::same_as<_Tp, int16_t> || + _VSTD::same_as<_Tp, int32_t> || _VSTD::same_as<_Tp, int64_t> +#ifndef _LIBCPP_HAS_NO_INT128 + || _VSTD::same_as<_Tp, __int128_t> +#endif + ; + +/** + * Used to determine the noexcept state of the output functions. + * + * Several functions in this header can be noexcept iff the output operations + * don't throw. + */ +template +concept __output_iterator_noexcept = + _VSTD::is_nothrow_move_constructible_v<_OutIt>&& + _VSTD::is_nothrow_copy_constructible_v<_OutIt>&& + requires(_OutIt __out_it) { + noexcept(*__out_it); + noexcept(__out_it++); + noexcept(++__out_it); +}; + +namespace __itoa { +/** + * Types that are allowed to instantiate in this namespace. + * + * Since several of these functions are optimized for certain types do not + * allow every type in this namespace to be instantiated. The functions in + * the std namespace should cast the types to an appropriate internal type. + */ +template +concept __internal_integral = + _VSTD::same_as<_Tp, uint32_t> || _VSTD::same_as<_Tp, uint64_t> +#ifndef _LIBCPP_HAS_NO_INT128 + || _VSTD::same_as<_Tp, __uint128_t> +#endif + ; + +/** Types that should be converted to a uint32_t. */ +template +concept __integral_to_uint32_t = + _VSTD::same_as<_Tp, uint8_t> || _VSTD::same_as<_Tp, uint16_t>; + +/** Creates a lookup table with all powers of 10. */ +template <__internal_integral _Tp> +consteval auto __create_lut_pow10() { + // _VSTD::numeric_limits<_Tp>::digits10 is rounded down so add 1. + _VSTD::array<_Tp, _VSTD::numeric_limits<_Tp>::digits10 + 1> __result; + + __result[0] = 1; + for (size_t __i = 1; __i < __result.size(); ++__i) + __result[__i] = 10 * __result[__i - 1]; + return __result; +} + +/** + * Creates a lookup table with the bit width mapped to the character width. + * + * The maximum of digits required is based on the first zero bit of a value. + * + * The table for uint8_t would look like: + * _VSTD::array { + * 1, // zero bit = 0 -> 0 + * 1, // zero bit = 1 -> 1 + * 1, // zero bit = 2 -> 3 + * 1, // zero bit = 3 -> 7 + * 2, // zero bit = 4 -> 15 + * 2, // zero bit = 5 -> 31 + * 2, // zero bit = 6 -> 63 + * 3, // zero bit = 7 -> 127 + * 3, // zero bit = 8 -> 255 + * }; + * As can be seen the number of digits is numberic_limits::digits + 1. + * If an 8 bit value has its leading (7th) bit set the first zero bit would be + * bit 8. That bit isn't available in a 8 bit value, but that doesn't matter + * for this algorithm. + */ +consteval auto __create_lut_character_width_base_10() { +#ifndef _LIBCPP_HAS_NO_INT128 + using _Tp = __uint128_t; +#else + using _Tp = uint64_t; +#endif + _VSTD::array::digits + 1> __result; + + _Tp __value = 0; + _Tp __max = 10; + uint8_t __digits = 1; + + // Avoid setting the last element, it will fail due to __max overflowing. + for (size_t __i = 0; __i < __result.size() - 1; ++__i) { + if (__value >= __max) { + ++__digits; + __max *= 10; + } + __result[__i] = __digits; + __value <<= 1; + __value |= 1; + } + + // _VSTD::numeric_limits<_Tp>::digits10 is rounded down so add 1. + __result[__result.size() - 1] = _VSTD::numeric_limits<_Tp>::digits10 + 1; + return __result; +} + +template <__internal_integral _Tp> +_LIBCPP_HIDDEN constexpr int __character_width_base_10(_Tp __value) noexcept { + constexpr auto __character_width = __create_lut_character_width_base_10(); + constexpr auto __pow10 = __create_lut_pow10<_Tp>(); + if (__value == _Tp(0)) + return 1; + + int __result = __character_width[_VSTD::bit_width(__value)]; + + _LIBCPP_ASSERT(__result > 0, "No bits set should required 1 digit."); + // Using this branch free code instead of + // if (__value < __pow10[__result - 1]) --__result; + // gives better performance. + __result -= __value < __pow10[__result - 1]; + + _LIBCPP_ASSERT(__result > 0, "No bits set should required 1 digit."); + + return __result; +} + +template <__integral_to_uint32_t _Tp> +_LIBCPP_HIDDEN constexpr int __character_width_base_10(_Tp __value) noexcept { + return __character_width_base_10(static_cast(__value)); +} + +template +consteval auto __create_lut_to_buffer_digits_2() { + std::array<_CharT, 200> __result; + _CharT* __ptr = &__result[0]; + + for (int __i = 0; __i < 10; ++__i) + for (int __j = 0; __j < 10; ++__j) { + *__ptr++ = _CharT('0' + __i); + *__ptr++ = _CharT('0' + __j); + } + return __result; +} + +template +_LIBCPP_HIDDEN constexpr void +__to_buffer_digits_2_base_10(_CharT* __out_it, uint32_t __value) noexcept { + _LIBCPP_ASSERT(__value < 100, + "This function requires only 2 digits in the value."); + constexpr auto __lut = __create_lut_to_buffer_digits_2<_CharT>(); + _VSTD::copy_n(&__lut[__value * 2], 2, __out_it); +} + +template _Tp> +_LIBCPP_HIDDEN constexpr _CharT* +__to_buffer_base_10(_CharT* __out_it, _Tp __value, int __digits) noexcept { + while (__digits >= 2) { + __out_it -= 2; + __digits -= 2; + __to_buffer_digits_2_base_10(__out_it, __value % 100); + __value /= 100; + } + if (__digits) + *--__out_it = _CharT('0' + __value); + + return __out_it; +} + +template _Tp> +_LIBCPP_HIDDEN constexpr _CharT* +__to_buffer_base_10(_CharT* __out_it, _Tp __value, int __digits) noexcept { + // Process the largest fully supported number of digits to the 32 bit version. + // The odd number leads to outputting a single value but that has no + // performance impact. + constexpr int __dispatch_size = 9; + constexpr _Tp __dispatch_value = __create_lut_pow10<_Tp>()[__dispatch_size]; + while (__digits >= __dispatch_size) { + __digits -= __dispatch_size; + __out_it = __to_buffer_base_10( + __out_it, static_cast(__value % __dispatch_value), + __dispatch_size); + __value /= __dispatch_value; + } + if (__digits) + __out_it = + __to_buffer_base_10(__out_it, static_cast(__value), __digits); + + return __out_it; +} + +#ifndef _LIBCPP_HAS_NO_INT128 +template _Tp> +_LIBCPP_HIDDEN constexpr void __to_buffer_base_10(_CharT* __out_it, _Tp __value, + int __digits) noexcept { + + // Process the largest fully supported number of digits to the 64 bit version. + // The odd number leads to outputting a single value but that has no + // performance impact. + constexpr int __dispatch_size = 19; + constexpr _Tp __dispatch_value = __create_lut_pow10<_Tp>()[__dispatch_size]; + while (__digits >= __dispatch_size) { + __digits -= __dispatch_size; + __out_it = __to_buffer_base_10( + __out_it, static_cast(__value % __dispatch_value), + __dispatch_size); + __value /= __dispatch_value; + } + if (__digits) + __to_buffer_base_10(__out_it, static_cast(__value), __digits); +} +#endif + +template +_LIBCPP_HIDDEN constexpr void __to_buffer_base_10(_CharT* __out_it, _Tp __value, + int __digits) noexcept { + __to_buffer_base_10(__out_it, static_cast(__value), __digits); +} + +} // namespace __itoa + +/** + * Determines the number of characters needed to store a value as a string. + * + * The number of characters doesn't take the locale into account and stores no + * thousands separator. + * + * This version handles non-negative values and doesn't take a character for a + * sign into account. + * + * @returns The number of characters to store the @a __value. + */ +template <__uintX_t _Tp> +_LIBCPP_INLINE_VISIBILITY constexpr int +__character_width_base_10(_Tp __value) noexcept { + return __itoa::__character_width_base_10(__value); +} + +/** + * @overload for signed integral values. + * + * When the value is negative it adds one character for the sign. + */ +template <__intX_t _Tp> +_LIBCPP_INLINE_VISIBILITY constexpr int +__character_width_base_10(_Tp __value) noexcept { + if (__value < 0) + return 1 + __itoa::__character_width_base_10( + static_cast<_VSTD::make_unsigned_t<_Tp> >(-__value)); + return __itoa::__character_width_base_10( + static_cast<_VSTD::make_unsigned_t<_Tp> >(__value)); +} + +/** + * An alternative for std::to_chars. + * + * An locale-independent, non-allocating function to convert an integral to its + * text representation. Unlike std::to_chars it has no "last" argument, making + * it easier to use with std::format_to. + * + * This version is specialized for a character output iterator. This avoids + * creating a temporary stack buffer. + * + * @param ___out_it The output iterator to write the text to. + * @param __value The value to write. + * + * @returns An iterator one past the end of the last written character. + * + * @note This implements only the unsigned integral implementation. + */ +template +_LIBCPP_INLINE_VISIBILITY constexpr _CharT* +__to_chars_base_10(_CharT* __out_it, _Tp __value) noexcept { + if (__value == _Tp(0)) { + *__out_it++ = _CharT('0'); + return __out_it; + } + int __size = __character_width_base_10(__value); + __out_it += __size; + __itoa::__to_buffer_base_10(__out_it, __value, __size); + return __out_it; +} + +/** The unsigned integral implementation for generic output iterators. */ +// TODO FMT Use concept _VSTD::output_iterator<_OutIt> +template +_LIBCPP_INLINE_VISIBILITY constexpr _OutIt __to_chars_base_10(_OutIt __out_it, + _Tp __value) + noexcept(__output_iterator_noexcept<_OutIt>) { + using _CharT = typename _VSTD::iterator_traits<_OutIt>::value_type; + // _VSTD::numeric_limits<_Tp>::digits10 is rounded down so add 1 + _CharT __buffer[_VSTD::numeric_limits<_Tp>::digits10 + 1]; + auto __last = __to_chars_base_10(__buffer, __value); + return _VSTD::copy(__buffer, __last, __out_it); +} + +/** The signed integral implementation of __to_chars_base_10. */ +// TODO FMT Use concept _VSTD::output_iterator<_OutIt> +template +_LIBCPP_INLINE_VISIBILITY constexpr _OutIt __to_chars_base_10(_OutIt __out_it, + _Tp __value) + noexcept(__output_iterator_noexcept<_OutIt>) { + using _CharT = typename _VSTD::iterator_traits<_OutIt>::value_type; + if (__value < _Tp(0)) { + *__out_it++ = _CharT('-'); + __value = -__value; + } + return __to_chars_base_10(__out_it, + static_cast<_VSTD::make_unsigned_t<_Tp> >(__value)); +} + +/** + * An alternative for std::to_string and std::to_wstring. + * + * An locale-independent, function to convert an integral to std::basic_string. + * Unlike std::to_string the function is templated on the character type. + * Making it possible to return basic_string types with the character other + * than @c char or @c wchar_t. + * + * @param __value The value to convert. + * + * @returns A std::basic_string with the converted value. + * + * @throws std::bad_alloc from the std::basic_string's constructor. + */ +template +requires(__uintX_t<_Tp> || __intX_t<_Tp>) _LIBCPP_INLINE_VISIBILITY constexpr + _VSTD::basic_string<_CharT> __to_basic_string(_Tp __value) { + // _VSTD::numeric_limits<_Tp>::digits10 is rounded down so add 1 + _CharT __buffer[_VSTD::numeric_limits<_Tp>::digits10 + 2]; + auto __last = __to_chars_base_10(__buffer, __value); + return _VSTD::basic_string(__buffer, __last); +} + +#endif //_LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___FORMAT_ITOA diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -294,6 +294,7 @@ } module format { header "format" + export __format_itoa export * } module forward_list { diff --git a/libcxx/test/std/utilities/format/__itoa/__character_width_base_10.pass.cpp b/libcxx/test/std/utilities/format/__itoa/__character_width_base_10.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/__itoa/__character_width_base_10.pass.cpp @@ -0,0 +1,245 @@ +//===----------------------------------------------------------------------===// +// 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 + +// <__format_itoa> +// +// template +// requires(_VSTD::is_integral_v<_Tp>&& _VSTD::is_unsigned_v<_Tp>) +// constexpr +// int __character_width_base_10(_Tp __value) noexcept; +// template +// requires(_VSTD::is_integral_v<_Tp>&& _VSTD::is_signed_v<_Tp>) +// constexpr +// int __character_width_base_10(_Tp __value) noexcept; + +#include <__format_itoa> +#include + +#include "test_macros.h" + +template +// TODO FMT Use concept std::signed_integral +requires(std::is_integral_v&& std::is_signed_v) constexpr + void test_negative() { + { + //[-1, -9) + for (T a = 1; a < 10; ++a) { + T value = a; + value = -value; + assert(std::__character_width_base_10(value) == 2); + } + } + { // [-10, -100) + for (T a = 1; a < 10; ++a) + for (T b = 0; b < 10; ++b) { + T value = (10 * a) + b; + value = -value; + assert(std::__character_width_base_10(value) == 3); + } + } + // During constexpr evaluation the following error is triggered + // constexpr evaluation hit maximum step limit; possible infinite loop? + // Therefore only do a short test during constant evaluation. + if (std::is_constant_evaluated()) + return; + [] { // [-100, -1.000) + for (T a = 1; a < 10; ++a) + for (T b = 0; b < 10; ++b) + for (T c = 0; c < 10; ++c) { + T value = (100 * a) + (10 * b) + c; + value = -value; + assert(std::__character_width_base_10(value) == 4); + if (value == -127 && std::numeric_limits::digits == 7) + return; // Bail-out for int8_t. + } + }(); + if (std::numeric_limits::digits > 8) { + // [-1.000, -10.000) + for (T a = 1; a < 10; ++a) + for (T b = 0; b < 10; ++b) + for (T c = 0; c < 10; ++c) + for (T d = 0; d < 10; ++d) { + T value = (1000 * a) + (100 * b) + (10 * c) + d; + value = -value; + assert(std::__character_width_base_10(value) == 5); + } + } + if (std::numeric_limits::digits > 8) { + [] { // [-10.000, -100.000) + for (T a = 1; a < 10; ++a) + for (T b = 0; b < 10; ++b) + for (T c = 0; c < 10; ++c) + for (T d = 0; d < 10; ++d) + for (T e = 0; e < 10; ++e) { + T value = (10000 * a) + (1000 * b) + (100 * c) + (10 * d) + e; + value = -value; + assert(std::__character_width_base_10(value) == 6); + if (value == -32767 && std::numeric_limits::digits == 15) + return; // Bail-out for int16_t. + } + }(); + } + if (std::numeric_limits::digits > 16) { + T multiplier = -10; + for (int i = 6; i < 40; ++i, multiplier *= 10) + for (T a = 1; a < 10; ++a) + for (T b = 0; b < 10; ++b) + for (T c = 0; c < 10; ++c) + for (T d = 0; d < 10; ++d) + for (T e = 0; e < 10; ++e) { + T value = (10000 * a) + (1000 * b) + (100 * c) + (10 * d) + e; + // Validate whether the whether the maximum for a type is + // reached. Instead of testing against a huge number, just test + // the value before multiplying. Use the number of digits to + // "get" the real limit. + if (value == 21475 && i == 10 && + std::numeric_limits::digits == 31) + return; // Bail-out for int32_t. + if (value == 92234 && i == 19 && + std::numeric_limits::digits == 63) + return; // Bail-out for int64_t. + if (value == 17015 && i == 39 && + std::numeric_limits::digits == 127) + return; // Bail-out for __int128_t. + + value *= multiplier; + assert(std::__character_width_base_10(value) == i + 1); + } + } +} + +template +constexpr void test() { + ASSERT_NOEXCEPT(std::__character_width_base_10(T(0))); + + // TODO FMT Use concept std::signed_integral + if constexpr (std::is_integral_v && std::is_signed_v) + test_negative(); + { // [0, 10) + for (T a = 0; a < 10; ++a) { + T value = a; + assert(std::__character_width_base_10(value) == 1); + } + } + { // [10, 100) + for (T a = 1; a < 10; ++a) + for (T b = 0; b < 10; ++b) { + T value = (10 * a) + b; + assert(std::__character_width_base_10(value) == 2); + } + } + // During constexpr evaluation the following error is triggered + // constexpr evaluation hit maximum step limit; possible infinite loop? + // Therefore only do a short test during constant evaluation. + if (std::is_constant_evaluated()) + return; + [] { // [100, 1.000) + for (T a = 1; a < 10; ++a) + for (T b = 0; b < 10; ++b) + for (T c = 0; c < 10; ++c) { + T value = (100 * a) + (10 * b) + c; + assert(std::__character_width_base_10(value) == 3); + + if (value == 127 && std::numeric_limits::digits == 7) + return; // Bail-out for int8_t. + if (value == 255 && std::numeric_limits::digits == 8) + return; // Bail-out for uint8_t. + } + }(); + if (std::numeric_limits::digits > 8) { + // [1.000, 10.000) + for (T a = 1; a < 10; ++a) + for (T b = 0; b < 10; ++b) + for (T c = 0; c < 10; ++c) + for (T d = 0; d < 10; ++d) { + T value = (1000 * a) + (100 * b) + (10 * c) + d; + assert(std::__character_width_base_10(value) == 4); + } + } + if (std::numeric_limits::digits > 8) { + [] { // [10.000, 100.000) + for (T a = 1; a < 10; ++a) + for (T b = 0; b < 10; ++b) + for (T c = 0; c < 10; ++c) + for (T d = 0; d < 10; ++d) + for (T e = 0; e < 10; ++e) { + T value = (10000 * a) + (1000 * b) + (100 * c) + (10 * d) + e; + assert(std::__character_width_base_10(value) == 5); + if (value == 32767 && std::numeric_limits::digits == 15) + return; // Bail-out for int16_t. + if (value == 65535 && std::numeric_limits::digits == 16) + return; // Bail-out for unt16_t. + } + }(); + } + if (std::numeric_limits::digits > 16) { + T multiplier = 10; + for (int i = 6; i < 40; ++i, multiplier *= 10) + for (T a = 1; a < 10; ++a) + for (T b = 0; b < 10; ++b) + for (T c = 0; c < 10; ++c) + for (T d = 0; d < 10; ++d) + for (T e = 0; e < 10; ++e) { + T value = (10000 * a) + (1000 * b) + (100 * c) + (10 * d) + e; + // Validate whether the whether the maximum for a type is + // reached. Instead of testing against a huge number, just test + // the value before multiplying. Use the number of digits to + // "get" the real limit. + if (value == 21475 && i == 10 && + std::numeric_limits::digits == 31) + return; // Bail-out for int32_t. + if (value == 42950 && i == 10 && + std::numeric_limits::digits == 32) + return; // Bail-out for unt32_t. + if (value == 92234 && i == 19 && + std::numeric_limits::digits == 63) + return; // Bail-out for int64_t. + if (value == 18447 && i == 20 && + std::numeric_limits::digits == 64) + return; // Bail-out for uint64_t. + if (value == 17015 && i == 39 && + std::numeric_limits::digits == 127) + return; // Bail-out for __int128_t. + if (value == 34029 && i == 39 && + std::numeric_limits::digits == 128) + return; // Bail-out for __uint128_t. + + value *= multiplier; + assert(std::__character_width_base_10(value) == i); + } + } +} + +constexpr bool test() { + test(); + test(); + test(); + test(); +#ifndef _LIBCPP_HAS_NO_INT128 + test<__uint128_t>(); +#endif + + test(); + test(); + test(); + test(); +#ifndef _LIBCPP_HAS_NO_INT128 + test<__int128_t>(); +#endif + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/utilities/format/__itoa/__to_basic_string.pass.cpp b/libcxx/test/std/utilities/format/__itoa/__to_basic_string.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/__itoa/__to_basic_string.pass.cpp @@ -0,0 +1,335 @@ +//===----------------------------------------------------------------------===// +// 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 + +// <__format_itoa> + +// template +// requires(_VSTD::is_integral_v<_Tp>&& _VSTD::is_unsigned_v<_Tp>) constexpr +// template +// std::basic_string<_CharT> __to_basic_string(_Tp __value); +// requires(_VSTD::is_integral_v<_Tp>&& _VSTD::is_signed_v<_Tp>) constexpr +// std::basic_string<_CharT> __to_basic_string(_Tp __value); + +#include <__format_itoa> +#include + +#include "test_macros.h" + +template +// TODO FMT Use concept std::signed_integral +requires(std::is_integral_v&& std::is_signed_v) constexpr + void test_negative() { + // Set all characthers to '0'. The 5 starting at the second character will + // change during the tests, the otherw will remain the same. + std::array expected; + std::fill(expected.begin(), expected.end(), CharT('0')); + expected[0] = CharT('-'); + + for (T a = 1; a < 10; ++a) { //[-1, -9) + expected[1] = CharT('0' + a); + T value = a; + value = -value; + auto str = std::__to_basic_string(value); + assert(str.size() == 2); + assert((std::equal(str.begin(), str.end(), expected.begin()))); + } + for (T a = 1; a < 10; ++a) { // [-10, -100) + expected[1] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[2] = CharT('0' + b); + T value = (10 * a) + b; + value = -value; + auto str = std::__to_basic_string(value); + assert(str.size() == 3); + assert((std::equal(str.begin(), str.end(), expected.begin()))); + } + } + // During constexpr evaluation the following error is triggered + // constexpr evaluation hit maximum step limit; possible infinite loop? + // Therefore only do a short test during constant evaluation. + if (std::is_constant_evaluated()) + return; + + for (T a = 1; a < 10; ++a) { // [-100, -1.000) + expected[1] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[2] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[3] = CharT('0' + c); + T value = (100 * a) + (10 * b) + c; + value = -value; + auto str = std::__to_basic_string(value); + assert(str.size() == 4); + assert((std::equal(str.begin(), str.end(), expected.begin()))); + if (value == -127 && std::numeric_limits::digits == 7) + return; // Bail-out for int8_t. + } + } + } + for (T a = 1; a < 10; ++a) { // [-1.000, -10.000) + expected[1] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[2] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[3] = CharT('0' + c); + for (T d = 0; d < 10; ++d) { + expected[4] = CharT('0' + d); + T value = (1000 * a) + (100 * b) + (10 * c) + d; + value = -value; + auto str = std::__to_basic_string(value); + assert(str.size() == 5); + assert((std::equal(str.begin(), str.end(), expected.begin()))); + if (value == -127 && std::numeric_limits::digits == 7) + return; // Bail-out for int8_t. + } + } + } + } + for (T a = 1; a < 10; ++a) { // [-10.000, -100.000) + expected[1] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[2] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[3] = CharT('0' + c); + for (T d = 0; d < 10; ++d) { + expected[4] = CharT('0' + d); + for (T e = 0; e < 10; ++e) { + expected[5] = CharT('0' + e); + T value = (10000 * a) + (1000 * b) + (100 * c) + (10 * d) + e; + value = -value; + auto str = std::__to_basic_string(value); + assert(str.size() == 6); + assert((std::equal(str.begin(), str.end(), expected.begin()))); + if (value == -32767 && std::numeric_limits::digits == 15) + return; // Bail-out for int16_t. + } + } + } + } + } + T multiplier = -10; + for (size_t i = 6; i < 40; ++i, multiplier *= 10) { + for (T a = 1; a < 10; ++a) { + expected[1] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[2] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[3] = CharT('0' + c); + for (T d = 0; d < 10; ++d) { + expected[4] = CharT('0' + d); + for (T e = 0; e < 10; ++e) { + expected[5] = CharT('0' + e); + T value = (10000 * a) + (1000 * b) + (100 * c) + (10 * d) + e; + // Validate whether the whether the maximum for a type is + // reached. Instead of testing against a huge number, just test + // the value before multiplying. Use the number of digits to + // "get" the real limit. + if (value == 21475 && i == 10 && + std::numeric_limits::digits == 31) + return; // Bail-out for int32_t. + if (value == 92234 && i == 19 && + std::numeric_limits::digits == 63) + return; // Bail-out for int64_t. + if (value == 17015 && i == 39 && + std::numeric_limits::digits == 127) + return; // Bail-out for __int128_t. + + value *= multiplier; + auto str = std::__to_basic_string(value); + assert(str.size() == i + 1); + assert((std::equal(str.begin(), str.end(), expected.begin()))); + } + } + } + } + } + } +} + +template +constexpr void test() { + // TODO FMT Use concept std::signed_integral + if constexpr (std::is_integral_v && std::is_signed_v) + test_negative(); + + // Set all characthers to '0', the first 5 will change during the tests the + // others will remain '0'. + std::array expected; + std::fill(expected.begin(), expected.end(), CharT('0')); + + for (T a = 0; a < 10; ++a) { // [0, 10) + expected[0] = CharT('0' + a); + T value = a; + auto str = std::__to_basic_string(value); + assert(str.size() == 1); + assert((std::equal(str.begin(), str.end(), expected.begin()))); + } + for (T a = 1; a < 10; ++a) { // [10, 100) + expected[0] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[1] = CharT('0' + b); + T value = (10 * a) + b; + auto str = std::__to_basic_string(value); + assert(str.size() == 2); + assert((std::equal(str.begin(), str.end(), expected.begin()))); + } + } + + // During constexpr evaluation the following error is triggered + // constexpr evaluation hit maximum step limit; possible infinite loop? + // Therefore only do a short test during constant evaluation. + if (std::is_constant_evaluated()) + return; + + for (T a = 1; a < 10; ++a) { // [100, 1.000) + expected[0] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[1] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[2] = CharT('0' + c); + T value = (100 * a) + (10 * b) + c; + auto str = std::__to_basic_string(value); + assert(str.size() == 3); + assert((std::equal(str.begin(), str.end(), expected.begin()))); + if (value == 127 && std::numeric_limits::digits == 7) + return; // Bail-out for int8_t. + if (value == 255 && std::numeric_limits::digits == 8) + return; // Bail-out for uint8_t. + } + } + } + for (T a = 1; a < 10; ++a) { // [1.000, 10.000) + expected[0] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[1] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[2] = CharT('0' + c); + for (T d = 0; d < 10; ++d) { + expected[3] = CharT('0' + d); + T value = (1000 * a) + (100 * b) + (10 * c) + d; + auto str = std::__to_basic_string(value); + assert(str.size() == 4); + assert((std::equal(str.begin(), str.end(), expected.begin()))); + } + } + } + } + for (T a = 1; a < 10; ++a) { // [10.000, 100.000) + expected[0] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[1] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[2] = CharT('0' + c); + for (T d = 0; d < 10; ++d) { + expected[3] = CharT('0' + d); + for (T e = 0; e < 10; ++e) { + expected[4] = CharT('0' + e); + T value = (10000 * a) + (1000 * b) + (100 * c) + (10 * d) + e; + auto str = std::__to_basic_string(value); + assert(str.size() == 5); + assert((std::equal(str.begin(), str.end(), expected.begin()))); + if (value == 32767 && std::numeric_limits::digits == 15) + return; // Bail-out for int16_t. + if (value == 65535 && std::numeric_limits::digits == 16) + return; // Bail-out for unt16_t. + } + } + } + } + } + T multiplier = 10; + for (size_t i = 6; i < 40; ++i, multiplier *= 10) { + for (T a = 1; a < 10; ++a) { + expected[0] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[1] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[2] = CharT('0' + c); + for (T d = 0; d < 10; ++d) { + expected[3] = CharT('0' + d); + for (T e = 0; e < 10; ++e) { + expected[4] = CharT('0' + e); + T value = (10000 * a) + (1000 * b) + (100 * c) + (10 * d) + e; + // Validate whether the whether the maximum for a type is + // reached. Instead of testing against a huge number, just test + // the value before multiplying. Use the number of digits to + // "get" the real limit. + if (value == 21475 && i == 10 && + std::numeric_limits::digits == 31) + return; // Bail-out for int32_t. + if (value == 42950 && i == 10 && + std::numeric_limits::digits == 32) + return; // Bail-out for unt32_t. + if (value == 92234 && i == 19 && + std::numeric_limits::digits == 63) + return; // Bail-out for int64_t. + if (value == 18447 && i == 20 && + std::numeric_limits::digits == 64) + return; // Bail-out for uint64_t. + if (value == 17015 && i == 39 && + std::numeric_limits::digits == 127) + return; // Bail-out for __int128_t. + if (value == 34029 && i == 39 && + std::numeric_limits::digits == 128) + return; // Bail-out for __uint128_t. + + value *= multiplier; + auto str = std::__to_basic_string(value); + assert(str.size() == i); + assert((std::equal(str.begin(), str.end(), expected.begin()))); + } + } + } + } + } + } +} + +template +constexpr void test() { + test(); + test(); + test(); + test(); +#ifndef _LIBCPP_HAS_NO_INT128 + test(); +#endif + + test(); + test(); + test(); + test(); +#ifndef _LIBCPP_HAS_NO_INT128 + test(); +#endif +} + +constexpr bool test() { + test(); + test(); +#ifndef _LIBCPP_NO_HAS_CHAR8_T + test(); +#endif +#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS + test(); + test(); +#endif + + return true; +} + +int main(int, char**) { + test(); + // TODO FMT + // Uncomment the line below when std::basic_string can be used as constexpr. + //static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/utilities/format/__itoa/__to_chars_base_10.pass.cpp b/libcxx/test/std/utilities/format/__itoa/__to_chars_base_10.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/__itoa/__to_chars_base_10.pass.cpp @@ -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 + +// <__format_itoa> +// +// template +// requires(_VSTD::is_integral_v<_Tp>&& _VSTD::is_unsigned_v<_Tp>) +// constexpr _OutIt +// __to_chars_base_10(_OutIt __out_it, _Tp __value) noexcept +// template +// requires(_VSTD::is_integral_v<_Tp>&& _VSTD::is_signed_v<_Tp>) +// constexpr _OutIt +// __to_chars_base_10(_OutIt __out_it, _Tp __value) noexcept + +#include <__format_itoa> +#include + +#include "test_macros.h" + +template +// TODO FMT Use concept std::signed_integral +requires(std::is_integral_v&& std::is_signed_v) constexpr + void test_negative() { + + CharT output[128]; + // Set all characthers to '0'. The 5 starting at the second character will + // change during the tests, the otherw will remain the same. + std::array expected; + std::fill(expected.begin(), expected.end(), CharT('0')); + expected[0] = CharT('-'); + + for (T a = 1; a < 10; ++a) { //[-1, -9) + expected[1] = CharT('0' + a); + T value = a; + value = -value; + auto it = std::__to_chars_base_10(output, value); + assert(it == output + 2); + assert((std::equal(output, it, expected.begin()))); + } + for (T a = 1; a < 10; ++a) { // [-10, -100) + expected[1] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[2] = CharT('0' + b); + T value = (10 * a) + b; + value = -value; + auto it = std::__to_chars_base_10(output, value); + assert(it == output + 3); + assert((std::equal(output, it, expected.begin()))); + } + } + // During constexpr evaluation the following error is triggered + // constexpr evaluation hit maximum step limit; possible infinite loop? + // Therefore only do a short test during constant evaluation. + if (std::is_constant_evaluated()) + return; + + for (T a = 1; a < 10; ++a) { // [-100, -1.000) + expected[1] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[2] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[3] = CharT('0' + c); + T value = (100 * a) + (10 * b) + c; + value = -value; + auto it = std::__to_chars_base_10(output, value); + assert(it == output + 4); + assert((std::equal(output, it, expected.begin()))); + if (value == -127 && std::numeric_limits::digits == 7) + return; // Bail-out for int8_t. + } + } + } + for (T a = 1; a < 10; ++a) { // [-1.000, -10.000) + expected[1] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[2] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[3] = CharT('0' + c); + for (T d = 0; d < 10; ++d) { + expected[4] = CharT('0' + d); + T value = (1000 * a) + (100 * b) + (10 * c) + d; + value = -value; + auto it = std::__to_chars_base_10(output, value); + assert(it == output + 5); + assert((std::equal(output, it, expected.begin()))); + if (value == -127 && std::numeric_limits::digits == 7) + return; // Bail-out for int8_t. + } + } + } + } + for (T a = 1; a < 10; ++a) { // [-10.000, -100.000) + expected[1] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[2] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[3] = CharT('0' + c); + for (T d = 0; d < 10; ++d) { + expected[4] = CharT('0' + d); + for (T e = 0; e < 10; ++e) { + expected[5] = CharT('0' + e); + T value = (10000 * a) + (1000 * b) + (100 * c) + (10 * d) + e; + value = -value; + auto it = std::__to_chars_base_10(output, value); + assert(it == output + 6); + assert((std::equal(output, it, expected.begin()))); + if (value == -32767 && std::numeric_limits::digits == 15) + return; // Bail-out for int16_t. + } + } + } + } + } + T multiplier = -10; + for (size_t i = 6; i < 40; ++i, multiplier *= 10) { + for (T a = 1; a < 10; ++a) { + expected[1] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[2] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[3] = CharT('0' + c); + for (T d = 0; d < 10; ++d) { + expected[4] = CharT('0' + d); + for (T e = 0; e < 10; ++e) { + expected[5] = CharT('0' + e); + T value = (10000 * a) + (1000 * b) + (100 * c) + (10 * d) + e; + // Validate whether the whether the maximum for a type is + // reached. Instead of testing against a huge number, just test + // the value before multiplying. Use the number of digits to + // "get" the real limit. + if (value == 21475 && i == 10 && + std::numeric_limits::digits == 31) + return; // Bail-out for int32_t. + if (value == 92234 && i == 19 && + std::numeric_limits::digits == 63) + return; // Bail-out for int64_t. + if (value == 17015 && i == 39 && + std::numeric_limits::digits == 127) + return; // Bail-out for __int128_t. + + value *= multiplier; + auto it = std::__to_chars_base_10(output, value); + assert(it == output + i + 1); + assert((std::equal(output, it, expected.begin()))); + } + } + } + } + } + } +} + +template +constexpr void test() { + + { + CharT* ptr = nullptr; + T value = 0; + ASSERT_NOEXCEPT((std::__to_chars_base_10(ptr, value))); + + // TODO FMT Remove if statement once std::string can be constexpr. + if (!std::is_constant_evaluated()) { + std::basic_string str; + ASSERT_NOT_NOEXCEPT( + (std::__to_chars_base_10(std::back_inserter(str), value))); + } + } + + // TODO FMT Use concept std::signed_integral + if constexpr (std::is_integral_v && std::is_signed_v) + test_negative(); + + CharT output[128]; + // Set all characthers to '0', the first 5 will change during the tests the + // others will remain '0'. + std::array expected; + std::fill(expected.begin(), expected.end(), CharT('0')); + + for (T a = 0; a < 10; ++a) { // [0, 10) + expected[0] = CharT('0' + a); + T value = a; + auto it = std::__to_chars_base_10(output, value); + assert(it == output + 1); + assert((std::equal(output, it, expected.begin()))); + } + for (T a = 1; a < 10; ++a) { // [10, 100) + expected[0] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[1] = CharT('0' + b); + T value = (10 * a) + b; + auto it = std::__to_chars_base_10(output, value); + assert(it == output + 2); + assert((std::equal(output, it, expected.begin()))); + } + } + + // During constexpr evaluation the following error is triggered + // constexpr evaluation hit maximum step limit; possible infinite loop? + // Therefore only do a short test during constant evaluation. + if (std::is_constant_evaluated()) + return; + + for (T a = 1; a < 10; ++a) { // [100, 1.000) + expected[0] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[1] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[2] = CharT('0' + c); + T value = (100 * a) + (10 * b) + c; + auto it = std::__to_chars_base_10(output, value); + assert(it == output + 3); + assert((std::equal(output, it, expected.begin()))); + if (value == 127 && std::numeric_limits::digits == 7) + return; // Bail-out for int8_t. + if (value == 255 && std::numeric_limits::digits == 8) + return; // Bail-out for uint8_t. + } + } + } + for (T a = 1; a < 10; ++a) { // [1.000, 10.000) + expected[0] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[1] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[2] = CharT('0' + c); + for (T d = 0; d < 10; ++d) { + expected[3] = CharT('0' + d); + T value = (1000 * a) + (100 * b) + (10 * c) + d; + auto it = std::__to_chars_base_10(output, value); + assert(it == output + 4); + assert((std::equal(output, it, expected.begin()))); + } + } + } + } + for (T a = 1; a < 10; ++a) { // [10.000, 100.000) + expected[0] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[1] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[2] = CharT('0' + c); + for (T d = 0; d < 10; ++d) { + expected[3] = CharT('0' + d); + for (T e = 0; e < 10; ++e) { + expected[4] = CharT('0' + e); + T value = (10000 * a) + (1000 * b) + (100 * c) + (10 * d) + e; + auto it = std::__to_chars_base_10(output, value); + assert(it == output + 5); + assert((std::equal(output, it, expected.begin()))); + if (value == 32767 && std::numeric_limits::digits == 15) + return; // Bail-out for int16_t. + if (value == 65535 && std::numeric_limits::digits == 16) + return; // Bail-out for unt16_t. + } + } + } + } + } + T multiplier = 10; + for (size_t i = 6; i < 40; ++i, multiplier *= 10) { + for (T a = 1; a < 10; ++a) { + expected[0] = CharT('0' + a); + for (T b = 0; b < 10; ++b) { + expected[1] = CharT('0' + b); + for (T c = 0; c < 10; ++c) { + expected[2] = CharT('0' + c); + for (T d = 0; d < 10; ++d) { + expected[3] = CharT('0' + d); + for (T e = 0; e < 10; ++e) { + expected[4] = CharT('0' + e); + T value = (10000 * a) + (1000 * b) + (100 * c) + (10 * d) + e; + // Validate whether the whether the maximum for a type is + // reached. Instead of testing against a huge number, just test + // the value before multiplying. Use the number of digits to + // "get" the real limit. + if (value == 21475 && i == 10 && + std::numeric_limits::digits == 31) + return; // Bail-out for int32_t. + if (value == 42950 && i == 10 && + std::numeric_limits::digits == 32) + return; // Bail-out for unt32_t. + if (value == 92234 && i == 19 && + std::numeric_limits::digits == 63) + return; // Bail-out for int64_t. + if (value == 18447 && i == 20 && + std::numeric_limits::digits == 64) + return; // Bail-out for uint64_t. + if (value == 17015 && i == 39 && + std::numeric_limits::digits == 127) + return; // Bail-out for __int128_t. + if (value == 34029 && i == 39 && + std::numeric_limits::digits == 128) + return; // Bail-out for __uint128_t. + + value *= multiplier; + auto it = std::__to_chars_base_10(output, value); + assert(it == output + i); + assert(std::equal(output, it, expected.begin())); + } + } + } + } + } + } +} + +template +constexpr void test() { + test(); + test(); + test(); + test(); +#ifndef _LIBCPP_HAS_NO_INT128 + test(); +#endif + + test(); + test(); + test(); + test(); +#ifndef _LIBCPP_HAS_NO_INT128 + test(); +#endif +} + +constexpr bool test() { + test(); + test(); +#ifndef _LIBCPP_NO_HAS_CHAR8_T + test(); +#endif +#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS + test(); + test(); +#endif + + return true; +} + +int main(int, char**) { + + test(); + static_assert(test()); + + return 0; +}