From 384ebd28528bef0a2082099ff41933c72e2afd1b Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 27 Jul 2024 02:32:45 +0900 Subject: [PATCH 001/387] add string_view lite. --- CMakeLists.txt | 1 + README.md | 1 + src/nonstd/string_view.hpp | 1709 ++++++++++++++++++++++++++++++++++++ src/tiny-string.cc | 64 ++ 4 files changed, 1775 insertions(+) create mode 100644 src/nonstd/string_view.hpp create mode 100644 src/tiny-string.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 18497200..ed1fbdbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -388,6 +388,7 @@ set(TINYUSDZ_SOURCES ${PROJECT_SOURCE_DIR}/src/value-pprint.cc ${PROJECT_SOURCE_DIR}/src/value-types.cc ${PROJECT_SOURCE_DIR}/src/tiny-format.cc + ${PROJECT_SOURCE_DIR}/src/tiny-string.cc ${PROJECT_SOURCE_DIR}/src/io-util.cc ${PROJECT_SOURCE_DIR}/src/image-loader.cc ${PROJECT_SOURCE_DIR}/src/image-writer.cc diff --git a/README.md b/README.md index 88f00d9e..77739681 100644 --- a/README.md +++ b/README.md @@ -511,6 +511,7 @@ Some helper code is licensed under MIT license. * SDL2 : zlib license. https://www.libsdl.org/index.php * optional-lite: BSL 1.0 license. https://github.com/martinmoene/optional-lite * expected-lite: BSL 1.0 license. https://github.com/martinmoene/expected-lite +* string-view-lite: BSL 1.0 license. https://github.com/martinmoene/string-view-lite * mapbox/earcut.hpp: ISC license. https://github.com/mapbox/earcut.hpp * par_shapes.h generate parametric surfaces and other simple shapes: MIT license https://github.com/prideout/par * MaterialX: Apache 2.0 license. https://github.com/AcademySoftwareFoundation/MaterialX diff --git a/src/nonstd/string_view.hpp b/src/nonstd/string_view.hpp new file mode 100644 index 00000000..9c9159b1 --- /dev/null +++ b/src/nonstd/string_view.hpp @@ -0,0 +1,1709 @@ +// Copyright 2017-2020 by Martin Moene +// +// string-view lite, a C++17-like string_view for C++98 and later. +// For more information see https://github.com/martinmoene/string-view-lite +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#ifndef NONSTD_SV_LITE_H_INCLUDED +#define NONSTD_SV_LITE_H_INCLUDED + +#define string_view_lite_MAJOR 1 +#define string_view_lite_MINOR 8 +#define string_view_lite_PATCH 0 + +#define string_view_lite_VERSION nssv_STRINGIFY(string_view_lite_MAJOR) "." nssv_STRINGIFY(string_view_lite_MINOR) "." nssv_STRINGIFY(string_view_lite_PATCH) + +#define nssv_STRINGIFY( x ) nssv_STRINGIFY_( x ) +#define nssv_STRINGIFY_( x ) #x + +// string-view lite configuration: + +#define nssv_STRING_VIEW_DEFAULT 0 +#define nssv_STRING_VIEW_NONSTD 1 +#define nssv_STRING_VIEW_STD 2 + +// tweak header support: + +#ifdef __has_include +# if __has_include() +# include +# endif +#define nssv_HAVE_TWEAK_HEADER 1 +#else +#define nssv_HAVE_TWEAK_HEADER 0 +//# pragma message("string_view.hpp: Note: Tweak header not supported.") +#endif + +// string_view selection and configuration: + +#if !defined( nssv_CONFIG_SELECT_STRING_VIEW ) +# define nssv_CONFIG_SELECT_STRING_VIEW ( nssv_HAVE_STD_STRING_VIEW ? nssv_STRING_VIEW_STD : nssv_STRING_VIEW_NONSTD ) +#endif + +#ifndef nssv_CONFIG_STD_SV_OPERATOR +# define nssv_CONFIG_STD_SV_OPERATOR 0 +#endif + +#ifndef nssv_CONFIG_USR_SV_OPERATOR +# define nssv_CONFIG_USR_SV_OPERATOR 1 +#endif + +#ifdef nssv_CONFIG_CONVERSION_STD_STRING +# define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS nssv_CONFIG_CONVERSION_STD_STRING +# define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS nssv_CONFIG_CONVERSION_STD_STRING +#endif + +#ifndef nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS +# define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS 1 +#endif + +#ifndef nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS +# define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS 1 +#endif + +#ifndef nssv_CONFIG_NO_STREAM_INSERTION +# define nssv_CONFIG_NO_STREAM_INSERTION 0 +#endif + +#ifndef nssv_CONFIG_CONSTEXPR11_STD_SEARCH +# define nssv_CONFIG_CONSTEXPR11_STD_SEARCH 1 +#endif + +// Control presence of exception handling (try and auto discover): + +#ifndef nssv_CONFIG_NO_EXCEPTIONS +# if defined(_MSC_VER) +# include // for _HAS_EXCEPTIONS +# endif +# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS) +# define nssv_CONFIG_NO_EXCEPTIONS 0 +# else +# define nssv_CONFIG_NO_EXCEPTIONS 1 +# endif +#endif + +// C++ language version detection (C++23 is speculative): +// Note: VC14.0/1900 (VS2015) lacks too much from C++14. + +#ifndef nssv_CPLUSPLUS +# if defined(_MSVC_LANG ) && !defined(__clang__) +# define nssv_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) +# else +# define nssv_CPLUSPLUS __cplusplus +# endif +#endif + +#define nssv_CPP98_OR_GREATER ( nssv_CPLUSPLUS >= 199711L ) +#define nssv_CPP11_OR_GREATER ( nssv_CPLUSPLUS >= 201103L ) +#define nssv_CPP11_OR_GREATER_ ( nssv_CPLUSPLUS >= 201103L ) +#define nssv_CPP14_OR_GREATER ( nssv_CPLUSPLUS >= 201402L ) +#define nssv_CPP17_OR_GREATER ( nssv_CPLUSPLUS >= 201703L ) +#define nssv_CPP20_OR_GREATER ( nssv_CPLUSPLUS >= 202002L ) +#define nssv_CPP23_OR_GREATER ( nssv_CPLUSPLUS >= 202300L ) + +// use C++17 std::string_view if available and requested: + +#if nssv_CPP17_OR_GREATER && defined(__has_include ) +# if __has_include( ) +# define nssv_HAVE_STD_STRING_VIEW 1 +# else +# define nssv_HAVE_STD_STRING_VIEW 0 +# endif +#else +# define nssv_HAVE_STD_STRING_VIEW 0 +#endif + +#define nssv_USES_STD_STRING_VIEW ( (nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_STD) || ((nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_DEFAULT) && nssv_HAVE_STD_STRING_VIEW) ) + +#define nssv_HAVE_STARTS_WITH ( nssv_CPP20_OR_GREATER || !nssv_USES_STD_STRING_VIEW ) +#define nssv_HAVE_ENDS_WITH nssv_HAVE_STARTS_WITH + +// +// Use C++17 std::string_view: +// + +#if nssv_USES_STD_STRING_VIEW + +#include + +// Extensions for std::string: + +#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS + +#include + +namespace nonstd { + +template< class CharT, class Traits, class Allocator = std::allocator > +std::basic_string +to_string( std::basic_string_view v, Allocator const & a = Allocator() ) +{ + return std::basic_string( v.begin(), v.end(), a ); +} + +template< class CharT, class Traits, class Allocator > +std::basic_string_view +to_string_view( std::basic_string const & s ) +{ + return std::basic_string_view( s.data(), s.size() ); +} + +// Literal operators sv and _sv: + +#if nssv_CONFIG_STD_SV_OPERATOR + +using namespace std::literals::string_view_literals; + +#endif + +#if nssv_CONFIG_USR_SV_OPERATOR + +inline namespace literals { +inline namespace string_view_literals { + + +constexpr std::string_view operator "" _sv( const char* str, size_t len ) noexcept // (1) +{ + return std::string_view{ str, len }; +} + +constexpr std::u16string_view operator "" _sv( const char16_t* str, size_t len ) noexcept // (2) +{ + return std::u16string_view{ str, len }; +} + +constexpr std::u32string_view operator "" _sv( const char32_t* str, size_t len ) noexcept // (3) +{ + return std::u32string_view{ str, len }; +} + +constexpr std::wstring_view operator "" _sv( const wchar_t* str, size_t len ) noexcept // (4) +{ + return std::wstring_view{ str, len }; +} + +}} // namespace literals::string_view_literals + +#endif // nssv_CONFIG_USR_SV_OPERATOR + +} // namespace nonstd + +#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS + +namespace nonstd { + +using std::string_view; +using std::wstring_view; +using std::u16string_view; +using std::u32string_view; +using std::basic_string_view; + +// literal "sv" and "_sv", see above + +using std::operator==; +using std::operator!=; +using std::operator<; +using std::operator<=; +using std::operator>; +using std::operator>=; + +using std::operator<<; + +} // namespace nonstd + +#else // nssv_HAVE_STD_STRING_VIEW + +// +// Before C++17: use string_view lite: +// + +// Compiler versions: +// +// MSVC++ 6.0 _MSC_VER == 1200 nssv_COMPILER_MSVC_VERSION == 60 (Visual Studio 6.0) +// MSVC++ 7.0 _MSC_VER == 1300 nssv_COMPILER_MSVC_VERSION == 70 (Visual Studio .NET 2002) +// MSVC++ 7.1 _MSC_VER == 1310 nssv_COMPILER_MSVC_VERSION == 71 (Visual Studio .NET 2003) +// MSVC++ 8.0 _MSC_VER == 1400 nssv_COMPILER_MSVC_VERSION == 80 (Visual Studio 2005) +// MSVC++ 9.0 _MSC_VER == 1500 nssv_COMPILER_MSVC_VERSION == 90 (Visual Studio 2008) +// MSVC++ 10.0 _MSC_VER == 1600 nssv_COMPILER_MSVC_VERSION == 100 (Visual Studio 2010) +// MSVC++ 11.0 _MSC_VER == 1700 nssv_COMPILER_MSVC_VERSION == 110 (Visual Studio 2012) +// MSVC++ 12.0 _MSC_VER == 1800 nssv_COMPILER_MSVC_VERSION == 120 (Visual Studio 2013) +// MSVC++ 14.0 _MSC_VER == 1900 nssv_COMPILER_MSVC_VERSION == 140 (Visual Studio 2015) +// MSVC++ 14.1 _MSC_VER >= 1910 nssv_COMPILER_MSVC_VERSION == 141 (Visual Studio 2017) +// MSVC++ 14.2 _MSC_VER >= 1920 nssv_COMPILER_MSVC_VERSION == 142 (Visual Studio 2019) + +#if defined(_MSC_VER ) && !defined(__clang__) +# define nssv_COMPILER_MSVC_VER (_MSC_VER ) +# define nssv_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) ) +#else +# define nssv_COMPILER_MSVC_VER 0 +# define nssv_COMPILER_MSVC_VERSION 0 +#endif + +#define nssv_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * (major) + (minor) ) + (patch) ) + +#if defined( __apple_build_version__ ) +# define nssv_COMPILER_APPLECLANG_VERSION nssv_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) +# define nssv_COMPILER_CLANG_VERSION 0 +#elif defined( __clang__ ) +# define nssv_COMPILER_APPLECLANG_VERSION 0 +# define nssv_COMPILER_CLANG_VERSION nssv_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) +#else +# define nssv_COMPILER_APPLECLANG_VERSION 0 +# define nssv_COMPILER_CLANG_VERSION 0 +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# define nssv_COMPILER_GNUC_VERSION nssv_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#else +# define nssv_COMPILER_GNUC_VERSION 0 +#endif + +// half-open range [lo..hi): +#define nssv_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) + +// Presence of language and library features: + +#ifdef _HAS_CPP0X +# define nssv_HAS_CPP0X _HAS_CPP0X +#else +# define nssv_HAS_CPP0X 0 +#endif + +// Unless defined otherwise below, consider VC14 as C++11 for string-view-lite: + +#if nssv_COMPILER_MSVC_VER >= 1900 +# undef nssv_CPP11_OR_GREATER +# define nssv_CPP11_OR_GREATER 1 +#endif + +#define nssv_CPP11_90 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1500) +#define nssv_CPP11_100 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1600) +#define nssv_CPP11_110 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1700) +#define nssv_CPP11_120 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1800) +#define nssv_CPP11_140 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1900) +#define nssv_CPP11_141 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1910) + +#define nssv_CPP14_000 (nssv_CPP14_OR_GREATER) +#define nssv_CPP17_000 (nssv_CPP17_OR_GREATER) + +// Presence of C++11 language features: + +#define nssv_HAVE_CONSTEXPR_11 nssv_CPP11_140 +#define nssv_HAVE_EXPLICIT_CONVERSION nssv_CPP11_140 +#define nssv_HAVE_INLINE_NAMESPACE nssv_CPP11_140 +#define nssv_HAVE_IS_DEFAULT nssv_CPP11_140 +#define nssv_HAVE_IS_DELETE nssv_CPP11_140 +#define nssv_HAVE_NOEXCEPT nssv_CPP11_140 +#define nssv_HAVE_NULLPTR nssv_CPP11_100 +#define nssv_HAVE_REF_QUALIFIER nssv_CPP11_140 +#define nssv_HAVE_UNICODE_LITERALS nssv_CPP11_140 +#define nssv_HAVE_USER_DEFINED_LITERALS nssv_CPP11_140 +#define nssv_HAVE_WCHAR16_T nssv_CPP11_100 +#define nssv_HAVE_WCHAR32_T nssv_CPP11_100 + +#if ! ( ( nssv_CPP11_OR_GREATER && nssv_COMPILER_CLANG_VERSION ) || nssv_BETWEEN( nssv_COMPILER_CLANG_VERSION, 300, 400 ) ) +# define nssv_HAVE_STD_DEFINED_LITERALS nssv_CPP11_140 +#else +# define nssv_HAVE_STD_DEFINED_LITERALS 0 +#endif + +// Presence of C++14 language features: + +#define nssv_HAVE_CONSTEXPR_14 nssv_CPP14_000 + +// Presence of C++17 language features: + +#define nssv_HAVE_NODISCARD nssv_CPP17_000 + +// Presence of C++ library features: + +#define nssv_HAVE_STD_HASH nssv_CPP11_120 + +// Presence of compiler intrinsics: + +// Providing char-type specializations for compare() and length() that +// use compiler intrinsics can improve compile- and run-time performance. +// +// The challenge is in using the right combinations of builtin availability +// and its constexpr-ness. +// +// | compiler | __builtin_memcmp (constexpr) | memcmp (constexpr) | +// |----------|------------------------------|---------------------| +// | clang | 4.0 (>= 4.0 ) | any (? ) | +// | clang-a | 9.0 (>= 9.0 ) | any (? ) | +// | gcc | any (constexpr) | any (? ) | +// | msvc | >= 14.2 C++17 (>= 14.2 ) | any (? ) | + +#define nssv_HAVE_BUILTIN_VER ( (nssv_CPP17_000 && nssv_COMPILER_MSVC_VERSION >= 142) || nssv_COMPILER_GNUC_VERSION > 0 || nssv_COMPILER_CLANG_VERSION >= 400 || nssv_COMPILER_APPLECLANG_VERSION >= 900 ) +#define nssv_HAVE_BUILTIN_CE ( nssv_HAVE_BUILTIN_VER ) + +#define nssv_HAVE_BUILTIN_MEMCMP ( (nssv_HAVE_CONSTEXPR_14 && nssv_HAVE_BUILTIN_CE) || !nssv_HAVE_CONSTEXPR_14 ) +#define nssv_HAVE_BUILTIN_STRLEN ( (nssv_HAVE_CONSTEXPR_11 && nssv_HAVE_BUILTIN_CE) || !nssv_HAVE_CONSTEXPR_11 ) + +#ifdef __has_builtin +# define nssv_HAVE_BUILTIN( x ) __has_builtin( x ) +#else +# define nssv_HAVE_BUILTIN( x ) 0 +#endif + +#if nssv_HAVE_BUILTIN(__builtin_memcmp) || nssv_HAVE_BUILTIN_VER +# define nssv_BUILTIN_MEMCMP __builtin_memcmp +#else +# define nssv_BUILTIN_MEMCMP memcmp +#endif + +#if nssv_HAVE_BUILTIN(__builtin_strlen) || nssv_HAVE_BUILTIN_VER +# define nssv_BUILTIN_STRLEN __builtin_strlen +#else +# define nssv_BUILTIN_STRLEN strlen +#endif + +// C++ feature usage: + +#if nssv_HAVE_CONSTEXPR_11 +# define nssv_constexpr constexpr +#else +# define nssv_constexpr /*constexpr*/ +#endif + +#if nssv_HAVE_CONSTEXPR_14 +# define nssv_constexpr14 constexpr +#else +# define nssv_constexpr14 /*constexpr*/ +#endif + +#if nssv_HAVE_EXPLICIT_CONVERSION +# define nssv_explicit explicit +#else +# define nssv_explicit /*explicit*/ +#endif + +#if nssv_HAVE_INLINE_NAMESPACE +# define nssv_inline_ns inline +#else +# define nssv_inline_ns /*inline*/ +#endif + +#if nssv_HAVE_NOEXCEPT +# define nssv_noexcept noexcept +#else +# define nssv_noexcept /*noexcept*/ +#endif + +//#if nssv_HAVE_REF_QUALIFIER +//# define nssv_ref_qual & +//# define nssv_refref_qual && +//#else +//# define nssv_ref_qual /*&*/ +//# define nssv_refref_qual /*&&*/ +//#endif + +#if nssv_HAVE_NULLPTR +# define nssv_nullptr nullptr +#else +# define nssv_nullptr NULL +#endif + +#if nssv_HAVE_NODISCARD +# define nssv_nodiscard [[nodiscard]] +#else +# define nssv_nodiscard /*[[nodiscard]]*/ +#endif + +// Additional includes: + +#include +#include +#include +#include +#include // std::char_traits<> + +#if ! nssv_CONFIG_NO_STREAM_INSERTION +# include +#endif + +#if ! nssv_CONFIG_NO_EXCEPTIONS +# include +#endif + +#if nssv_CPP11_OR_GREATER +# include +#endif + +// Clang, GNUC, MSVC warning suppression macros: + +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wreserved-user-defined-literal" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wuser-defined-literals" +#elif nssv_COMPILER_GNUC_VERSION >= 480 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wliteral-suffix" +#endif // __clang__ + +#if nssv_COMPILER_MSVC_VERSION >= 140 +# define nssv_SUPPRESS_MSGSL_WARNING(expr) [[gsl::suppress(expr)]] +# define nssv_SUPPRESS_MSVC_WARNING(code, descr) __pragma(warning(suppress: code) ) +# define nssv_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable: codes)) +#else +# define nssv_SUPPRESS_MSGSL_WARNING(expr) +# define nssv_SUPPRESS_MSVC_WARNING(code, descr) +# define nssv_DISABLE_MSVC_WARNINGS(codes) +#endif + +#if defined(__clang__) +# define nssv_RESTORE_WARNINGS() _Pragma("clang diagnostic pop") +#elif nssv_COMPILER_GNUC_VERSION >= 480 +# define nssv_RESTORE_WARNINGS() _Pragma("GCC diagnostic pop") +#elif nssv_COMPILER_MSVC_VERSION >= 140 +# define nssv_RESTORE_WARNINGS() __pragma(warning(pop )) +#else +# define nssv_RESTORE_WARNINGS() +#endif + +// Suppress the following MSVC (GSL) warnings: +// - C4455, non-gsl : 'operator ""sv': literal suffix identifiers that do not +// start with an underscore are reserved +// - C26472, gsl::t.1 : don't use a static_cast for arithmetic conversions; +// use brace initialization, gsl::narrow_cast or gsl::narow +// - C26481: gsl::b.1 : don't use pointer arithmetic. Use span instead + +nssv_DISABLE_MSVC_WARNINGS( 4455 26481 26472 ) +//nssv_DISABLE_CLANG_WARNINGS( "-Wuser-defined-literals" ) +//nssv_DISABLE_GNUC_WARNINGS( -Wliteral-suffix ) + +namespace nonstd { namespace sv_lite { + +// +// basic_string_view declaration: +// + +template +< + class CharT, + class Traits = std::char_traits +> +class basic_string_view; + +namespace detail { + +// support constexpr comparison in C++14; +// for C++17 and later, use provided traits: + +template< typename CharT > +inline nssv_constexpr14 int compare( CharT const * s1, CharT const * s2, std::size_t count ) +{ + while ( count-- != 0 ) + { + if ( *s1 < *s2 ) return -1; + if ( *s1 > *s2 ) return +1; + ++s1; ++s2; + } + return 0; +} + +#if nssv_HAVE_BUILTIN_MEMCMP + +// specialization of compare() for char, see also generic compare() above: + +inline nssv_constexpr14 int compare( char const * s1, char const * s2, std::size_t count ) +{ + return nssv_BUILTIN_MEMCMP( s1, s2, count ); +} + +#endif + +#if nssv_HAVE_BUILTIN_STRLEN + +// specialization of length() for char, see also generic length() further below: + +inline nssv_constexpr std::size_t length( char const * s ) +{ + return nssv_BUILTIN_STRLEN( s ); +} + +#endif + +#if defined(__OPTIMIZE__) + +// gcc, clang provide __OPTIMIZE__ +// Expect tail call optimization to make length() non-recursive: + +template< typename CharT > +inline nssv_constexpr std::size_t length( CharT * s, std::size_t result = 0 ) +{ + return *s == '\0' ? result : length( s + 1, result + 1 ); +} + +#else // OPTIMIZE + +// non-recursive: + +template< typename CharT > +inline nssv_constexpr14 std::size_t length( CharT * s ) +{ + std::size_t result = 0; + while ( *s++ != '\0' ) + { + ++result; + } + return result; +} + +#endif // OPTIMIZE + +#if nssv_CPP11_OR_GREATER && ! nssv_CPP17_OR_GREATER +#if defined(__OPTIMIZE__) + +// gcc, clang provide __OPTIMIZE__ +// Expect tail call optimization to make search() non-recursive: + +template< class CharT, class Traits = std::char_traits > +constexpr const CharT* search( basic_string_view haystack, basic_string_view needle ) +{ + return haystack.starts_with( needle ) ? haystack.begin() : + haystack.empty() ? haystack.end() : search( haystack.substr(1), needle ); +} + +#else // OPTIMIZE + +// non-recursive: + +#if nssv_CONFIG_CONSTEXPR11_STD_SEARCH + +template< class CharT, class Traits = std::char_traits > +constexpr const CharT* search( basic_string_view haystack, basic_string_view needle ) +{ + return std::search( haystack.begin(), haystack.end(), needle.begin(), needle.end() ); +} + +#else // nssv_CONFIG_CONSTEXPR11_STD_SEARCH + +template< class CharT, class Traits = std::char_traits > +nssv_constexpr14 const CharT* search( basic_string_view haystack, basic_string_view needle ) +{ + while ( needle.size() <= haystack.size() ) + { + if ( haystack.starts_with(needle) ) + { + return haystack.cbegin(); + } + haystack = basic_string_view{ haystack.begin() + 1, haystack.size() - 1U }; + } + return haystack.cend(); +} +#endif // nssv_CONFIG_CONSTEXPR11_STD_SEARCH + +#endif // OPTIMIZE +#endif // nssv_CPP11_OR_GREATER && ! nssv_CPP17_OR_GREATER + +} // namespace detail + +// +// basic_string_view: +// + +template +< + class CharT, + class Traits /* = std::char_traits */ +> +class basic_string_view +{ +public: + // Member types: + + typedef Traits traits_type; + typedef CharT value_type; + + typedef CharT * pointer; + typedef CharT const * const_pointer; + typedef CharT & reference; + typedef CharT const & const_reference; + + typedef const_pointer iterator; + typedef const_pointer const_iterator; + typedef std::reverse_iterator< const_iterator > reverse_iterator; + typedef std::reverse_iterator< const_iterator > const_reverse_iterator; + + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + // 24.4.2.1 Construction and assignment: + + nssv_constexpr basic_string_view() nssv_noexcept + : data_( nssv_nullptr ) + , size_( 0 ) + {} + +#if nssv_CPP11_OR_GREATER + nssv_constexpr basic_string_view( basic_string_view const & other ) nssv_noexcept = default; +#else + nssv_constexpr basic_string_view( basic_string_view const & other ) nssv_noexcept + : data_( other.data_) + , size_( other.size_) + {} +#endif + + nssv_constexpr basic_string_view( CharT const * s, size_type count ) nssv_noexcept // non-standard noexcept + : data_( s ) + , size_( count ) + {} + + nssv_constexpr basic_string_view( CharT const * s) nssv_noexcept // non-standard noexcept + : data_( s ) +#if nssv_CPP17_OR_GREATER + , size_( Traits::length(s) ) +#elif nssv_CPP11_OR_GREATER + , size_( detail::length(s) ) +#else + , size_( Traits::length(s) ) +#endif + {} + +#if nssv_HAVE_NULLPTR +# if nssv_HAVE_IS_DELETE + nssv_constexpr basic_string_view( std::nullptr_t ) nssv_noexcept = delete; +# else + private: nssv_constexpr basic_string_view( std::nullptr_t ) nssv_noexcept; public: +# endif +#endif + + // Assignment: + +#if nssv_CPP11_OR_GREATER + nssv_constexpr14 basic_string_view & operator=( basic_string_view const & other ) nssv_noexcept = default; +#else + nssv_constexpr14 basic_string_view & operator=( basic_string_view const & other ) nssv_noexcept + { + data_ = other.data_; + size_ = other.size_; + return *this; + } +#endif + + // 24.4.2.2 Iterator support: + + nssv_constexpr const_iterator begin() const nssv_noexcept { return data_; } + nssv_constexpr const_iterator end() const nssv_noexcept { return data_ + size_; } + + nssv_constexpr const_iterator cbegin() const nssv_noexcept { return begin(); } + nssv_constexpr const_iterator cend() const nssv_noexcept { return end(); } + + nssv_constexpr const_reverse_iterator rbegin() const nssv_noexcept { return const_reverse_iterator( end() ); } + nssv_constexpr const_reverse_iterator rend() const nssv_noexcept { return const_reverse_iterator( begin() ); } + + nssv_constexpr const_reverse_iterator crbegin() const nssv_noexcept { return rbegin(); } + nssv_constexpr const_reverse_iterator crend() const nssv_noexcept { return rend(); } + + // 24.4.2.3 Capacity: + + nssv_constexpr size_type size() const nssv_noexcept { return size_; } + nssv_constexpr size_type length() const nssv_noexcept { return size_; } + nssv_constexpr size_type max_size() const nssv_noexcept { return (std::numeric_limits< size_type >::max)(); } + + // since C++20 + nssv_nodiscard nssv_constexpr bool empty() const nssv_noexcept + { + return 0 == size_; + } + + // 24.4.2.4 Element access: + + nssv_constexpr const_reference operator[]( size_type pos ) const + { + return data_at( pos ); + } + + nssv_constexpr14 const_reference at( size_type pos ) const + { +#if nssv_CONFIG_NO_EXCEPTIONS + assert( pos < size() ); +#else + if ( pos >= size() ) + { + throw std::out_of_range("nonstd::string_view::at()"); + } +#endif + return data_at( pos ); + } + + nssv_constexpr const_reference front() const { return data_at( 0 ); } + nssv_constexpr const_reference back() const { return data_at( size() - 1 ); } + + nssv_constexpr const_pointer data() const nssv_noexcept { return data_; } + + // 24.4.2.5 Modifiers: + + nssv_constexpr14 void remove_prefix( size_type n ) + { + assert( n <= size() ); + data_ += n; + size_ -= n; + } + + nssv_constexpr14 void remove_suffix( size_type n ) + { + assert( n <= size() ); + size_ -= n; + } + + nssv_constexpr14 void swap( basic_string_view & other ) nssv_noexcept + { + const basic_string_view tmp(other); + other = *this; + *this = tmp; + } + + // 24.4.2.6 String operations: + + size_type copy( CharT * dest, size_type n, size_type pos = 0 ) const + { +#if nssv_CONFIG_NO_EXCEPTIONS + assert( pos <= size() ); +#else + if ( pos > size() ) + { + throw std::out_of_range("nonstd::string_view::copy()"); + } +#endif + const size_type rlen = (std::min)( n, size() - pos ); + + (void) Traits::copy( dest, data() + pos, rlen ); + + return rlen; + } + + nssv_constexpr14 basic_string_view substr( size_type pos = 0, size_type n = npos ) const + { +#if nssv_CONFIG_NO_EXCEPTIONS + assert( pos <= size() ); +#else + if ( pos > size() ) + { + throw std::out_of_range("nonstd::string_view::substr()"); + } +#endif + return basic_string_view( data() + pos, (std::min)( n, size() - pos ) ); + } + + // compare(), 6x: + + nssv_constexpr14 int compare( basic_string_view other ) const nssv_noexcept // (1) + { +#if nssv_CPP17_OR_GREATER + if ( const int result = Traits::compare( data(), other.data(), (std::min)( size(), other.size() ) ) ) +#else + if ( const int result = detail::compare( data(), other.data(), (std::min)( size(), other.size() ) ) ) +#endif + { + return result; + } + + return size() == other.size() ? 0 : size() < other.size() ? -1 : 1; + } + + nssv_constexpr int compare( size_type pos1, size_type n1, basic_string_view other ) const // (2) + { + return substr( pos1, n1 ).compare( other ); + } + + nssv_constexpr int compare( size_type pos1, size_type n1, basic_string_view other, size_type pos2, size_type n2 ) const // (3) + { + return substr( pos1, n1 ).compare( other.substr( pos2, n2 ) ); + } + + nssv_constexpr int compare( CharT const * s ) const // (4) + { + return compare( basic_string_view( s ) ); + } + + nssv_constexpr int compare( size_type pos1, size_type n1, CharT const * s ) const // (5) + { + return substr( pos1, n1 ).compare( basic_string_view( s ) ); + } + + nssv_constexpr int compare( size_type pos1, size_type n1, CharT const * s, size_type n2 ) const // (6) + { + return substr( pos1, n1 ).compare( basic_string_view( s, n2 ) ); + } + + // 24.4.2.7 Searching: + + // starts_with(), 3x, since C++20: + + nssv_constexpr bool starts_with( basic_string_view v ) const nssv_noexcept // (1) + { + return size() >= v.size() && compare( 0, v.size(), v ) == 0; + } + + nssv_constexpr bool starts_with( CharT c ) const nssv_noexcept // (2) + { + return starts_with( basic_string_view( &c, 1 ) ); + } + + nssv_constexpr bool starts_with( CharT const * s ) const // (3) + { + return starts_with( basic_string_view( s ) ); + } + + // ends_with(), 3x, since C++20: + + nssv_constexpr bool ends_with( basic_string_view v ) const nssv_noexcept // (1) + { + return size() >= v.size() && compare( size() - v.size(), npos, v ) == 0; + } + + nssv_constexpr bool ends_with( CharT c ) const nssv_noexcept // (2) + { + return ends_with( basic_string_view( &c, 1 ) ); + } + + nssv_constexpr bool ends_with( CharT const * s ) const // (3) + { + return ends_with( basic_string_view( s ) ); + } + + // find(), 4x: + + nssv_constexpr14 size_type find( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1) + { + return assert( v.size() == 0 || v.data() != nssv_nullptr ) + , pos >= size() + ? npos : to_pos( +#if nssv_CPP11_OR_GREATER && ! nssv_CPP17_OR_GREATER + detail::search( substr(pos), v ) +#else + std::search( cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq ) +#endif + ); + } + + nssv_constexpr size_type find( CharT c, size_type pos = 0 ) const nssv_noexcept // (2) + { + return find( basic_string_view( &c, 1 ), pos ); + } + + nssv_constexpr size_type find( CharT const * s, size_type pos, size_type n ) const // (3) + { + return find( basic_string_view( s, n ), pos ); + } + + nssv_constexpr size_type find( CharT const * s, size_type pos = 0 ) const // (4) + { + return find( basic_string_view( s ), pos ); + } + + // rfind(), 4x: + + nssv_constexpr14 size_type rfind( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1) + { + if ( size() < v.size() ) + { + return npos; + } + + if ( v.empty() ) + { + return (std::min)( size(), pos ); + } + + const_iterator last = cbegin() + (std::min)( size() - v.size(), pos ) + v.size(); + const_iterator result = std::find_end( cbegin(), last, v.cbegin(), v.cend(), Traits::eq ); + + return result != last ? size_type( result - cbegin() ) : npos; + } + + nssv_constexpr14 size_type rfind( CharT c, size_type pos = npos ) const nssv_noexcept // (2) + { + return rfind( basic_string_view( &c, 1 ), pos ); + } + + nssv_constexpr14 size_type rfind( CharT const * s, size_type pos, size_type n ) const // (3) + { + return rfind( basic_string_view( s, n ), pos ); + } + + nssv_constexpr14 size_type rfind( CharT const * s, size_type pos = npos ) const // (4) + { + return rfind( basic_string_view( s ), pos ); + } + + // find_first_of(), 4x: + + nssv_constexpr size_type find_first_of( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1) + { + return pos >= size() + ? npos + : to_pos( std::find_first_of( cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq ) ); + } + + nssv_constexpr size_type find_first_of( CharT c, size_type pos = 0 ) const nssv_noexcept // (2) + { + return find_first_of( basic_string_view( &c, 1 ), pos ); + } + + nssv_constexpr size_type find_first_of( CharT const * s, size_type pos, size_type n ) const // (3) + { + return find_first_of( basic_string_view( s, n ), pos ); + } + + nssv_constexpr size_type find_first_of( CharT const * s, size_type pos = 0 ) const // (4) + { + return find_first_of( basic_string_view( s ), pos ); + } + + // find_last_of(), 4x: + + nssv_constexpr size_type find_last_of( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1) + { + return empty() + ? npos + : pos >= size() + ? find_last_of( v, size() - 1 ) + : to_pos( std::find_first_of( const_reverse_iterator( cbegin() + pos + 1 ), crend(), v.cbegin(), v.cend(), Traits::eq ) ); + } + + nssv_constexpr size_type find_last_of( CharT c, size_type pos = npos ) const nssv_noexcept // (2) + { + return find_last_of( basic_string_view( &c, 1 ), pos ); + } + + nssv_constexpr size_type find_last_of( CharT const * s, size_type pos, size_type count ) const // (3) + { + return find_last_of( basic_string_view( s, count ), pos ); + } + + nssv_constexpr size_type find_last_of( CharT const * s, size_type pos = npos ) const // (4) + { + return find_last_of( basic_string_view( s ), pos ); + } + + // find_first_not_of(), 4x: + + nssv_constexpr size_type find_first_not_of( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1) + { + return pos >= size() + ? npos + : to_pos( std::find_if( cbegin() + pos, cend(), not_in_view( v ) ) ); + } + + nssv_constexpr size_type find_first_not_of( CharT c, size_type pos = 0 ) const nssv_noexcept // (2) + { + return find_first_not_of( basic_string_view( &c, 1 ), pos ); + } + + nssv_constexpr size_type find_first_not_of( CharT const * s, size_type pos, size_type count ) const // (3) + { + return find_first_not_of( basic_string_view( s, count ), pos ); + } + + nssv_constexpr size_type find_first_not_of( CharT const * s, size_type pos = 0 ) const // (4) + { + return find_first_not_of( basic_string_view( s ), pos ); + } + + // find_last_not_of(), 4x: + + nssv_constexpr size_type find_last_not_of( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1) + { + return empty() + ? npos + : pos >= size() + ? find_last_not_of( v, size() - 1 ) + : to_pos( std::find_if( const_reverse_iterator( cbegin() + pos + 1 ), crend(), not_in_view( v ) ) ); + } + + nssv_constexpr size_type find_last_not_of( CharT c, size_type pos = npos ) const nssv_noexcept // (2) + { + return find_last_not_of( basic_string_view( &c, 1 ), pos ); + } + + nssv_constexpr size_type find_last_not_of( CharT const * s, size_type pos, size_type count ) const // (3) + { + return find_last_not_of( basic_string_view( s, count ), pos ); + } + + nssv_constexpr size_type find_last_not_of( CharT const * s, size_type pos = npos ) const // (4) + { + return find_last_not_of( basic_string_view( s ), pos ); + } + + // Constants: + +#if nssv_CPP17_OR_GREATER + static nssv_constexpr size_type npos = size_type(-1); +#elif nssv_CPP11_OR_GREATER + enum : size_type { npos = size_type(-1) }; +#else + enum { npos = size_type(-1) }; +#endif + +private: + struct not_in_view + { + const basic_string_view v; + + nssv_constexpr explicit not_in_view( basic_string_view v_ ) : v( v_ ) {} + + nssv_constexpr bool operator()( CharT c ) const + { + return npos == v.find_first_of( c ); + } + }; + + nssv_constexpr size_type to_pos( const_iterator it ) const + { + return it == cend() ? npos : size_type( it - cbegin() ); + } + + nssv_constexpr size_type to_pos( const_reverse_iterator it ) const + { + return it == crend() ? npos : size_type( crend() - it - 1 ); + } + + nssv_constexpr const_reference data_at( size_type pos ) const + { +#if nssv_BETWEEN( nssv_COMPILER_GNUC_VERSION, 1, 500 ) + return data_[pos]; +#else + return assert( pos < size() ), data_[pos]; +#endif + } + +private: + const_pointer data_; + size_type size_; + +public: +#if nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS + + template< class Allocator > + basic_string_view( std::basic_string const & s ) nssv_noexcept + : data_( s.data() ) + , size_( s.size() ) + {} + +#if nssv_HAVE_EXPLICIT_CONVERSION + + template< class Allocator > + explicit operator std::basic_string() const + { + return to_string( Allocator() ); + } + +#endif // nssv_HAVE_EXPLICIT_CONVERSION + +#if nssv_CPP11_OR_GREATER + + template< class Allocator = std::allocator > + std::basic_string + to_string( Allocator const & a = Allocator() ) const + { + return std::basic_string( begin(), end(), a ); + } + +#else + + std::basic_string + to_string() const + { + return std::basic_string( begin(), end() ); + } + + template< class Allocator > + std::basic_string + to_string( Allocator const & a ) const + { + return std::basic_string( begin(), end(), a ); + } + +#endif // nssv_CPP11_OR_GREATER + +#endif // nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS +}; + +// +// Non-member functions: +// + +// 24.4.3 Non-member comparison functions: +// lexicographically compare two string views (function template): + +template< class CharT, class Traits > +nssv_constexpr bool operator== ( + basic_string_view lhs, + basic_string_view rhs ) nssv_noexcept +{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } + +template< class CharT, class Traits > +nssv_constexpr bool operator!= ( + basic_string_view lhs, + basic_string_view rhs ) nssv_noexcept +{ return !( lhs == rhs ); } + +template< class CharT, class Traits > +nssv_constexpr bool operator< ( + basic_string_view lhs, + basic_string_view rhs ) nssv_noexcept +{ return lhs.compare( rhs ) < 0; } + +template< class CharT, class Traits > +nssv_constexpr bool operator<= ( + basic_string_view lhs, + basic_string_view rhs ) nssv_noexcept +{ return lhs.compare( rhs ) <= 0; } + +template< class CharT, class Traits > +nssv_constexpr bool operator> ( + basic_string_view lhs, + basic_string_view rhs ) nssv_noexcept +{ return lhs.compare( rhs ) > 0; } + +template< class CharT, class Traits > +nssv_constexpr bool operator>= ( + basic_string_view lhs, + basic_string_view rhs ) nssv_noexcept +{ return lhs.compare( rhs ) >= 0; } + +// Let S be basic_string_view, and sv be an instance of S. +// Implementations shall provide sufficient additional overloads marked +// constexpr and noexcept so that an object t with an implicit conversion +// to S can be compared according to Table 67. + +#if ! nssv_CPP11_OR_GREATER || nssv_BETWEEN( nssv_COMPILER_MSVC_VERSION, 100, 141 ) + +// accommodate for older compilers: + +// == + +template< class CharT, class Traits> +nssv_constexpr bool operator==( + basic_string_view lhs, + CharT const * rhs ) nssv_noexcept +{ return lhs.size() == detail::length( rhs ) && lhs.compare( rhs ) == 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator==( + CharT const * lhs, + basic_string_view rhs ) nssv_noexcept +{ return detail::length( lhs ) == rhs.size() && rhs.compare( lhs ) == 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator==( + basic_string_view lhs, + std::basic_string rhs ) nssv_noexcept +{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator==( + std::basic_string rhs, + basic_string_view lhs ) nssv_noexcept +{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } + +// != + +template< class CharT, class Traits> +nssv_constexpr bool operator!=( + basic_string_view lhs, + CharT const * rhs ) nssv_noexcept +{ return !( lhs == rhs ); } + +template< class CharT, class Traits> +nssv_constexpr bool operator!=( + CharT const * lhs, + basic_string_view rhs ) nssv_noexcept +{ return !( lhs == rhs ); } + +template< class CharT, class Traits> +nssv_constexpr bool operator!=( + basic_string_view lhs, + std::basic_string rhs ) nssv_noexcept +{ return !( lhs == rhs ); } + +template< class CharT, class Traits> +nssv_constexpr bool operator!=( + std::basic_string rhs, + basic_string_view lhs ) nssv_noexcept +{ return !( lhs == rhs ); } + +// < + +template< class CharT, class Traits> +nssv_constexpr bool operator<( + basic_string_view lhs, + CharT const * rhs ) nssv_noexcept +{ return lhs.compare( rhs ) < 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator<( + CharT const * lhs, + basic_string_view rhs ) nssv_noexcept +{ return rhs.compare( lhs ) > 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator<( + basic_string_view lhs, + std::basic_string rhs ) nssv_noexcept +{ return lhs.compare( rhs ) < 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator<( + std::basic_string rhs, + basic_string_view lhs ) nssv_noexcept +{ return rhs.compare( lhs ) > 0; } + +// <= + +template< class CharT, class Traits> +nssv_constexpr bool operator<=( + basic_string_view lhs, + CharT const * rhs ) nssv_noexcept +{ return lhs.compare( rhs ) <= 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator<=( + CharT const * lhs, + basic_string_view rhs ) nssv_noexcept +{ return rhs.compare( lhs ) >= 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator<=( + basic_string_view lhs, + std::basic_string rhs ) nssv_noexcept +{ return lhs.compare( rhs ) <= 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator<=( + std::basic_string rhs, + basic_string_view lhs ) nssv_noexcept +{ return rhs.compare( lhs ) >= 0; } + +// > + +template< class CharT, class Traits> +nssv_constexpr bool operator>( + basic_string_view lhs, + CharT const * rhs ) nssv_noexcept +{ return lhs.compare( rhs ) > 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator>( + CharT const * lhs, + basic_string_view rhs ) nssv_noexcept +{ return rhs.compare( lhs ) < 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator>( + basic_string_view lhs, + std::basic_string rhs ) nssv_noexcept +{ return lhs.compare( rhs ) > 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator>( + std::basic_string rhs, + basic_string_view lhs ) nssv_noexcept +{ return rhs.compare( lhs ) < 0; } + +// >= + +template< class CharT, class Traits> +nssv_constexpr bool operator>=( + basic_string_view lhs, + CharT const * rhs ) nssv_noexcept +{ return lhs.compare( rhs ) >= 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator>=( + CharT const * lhs, + basic_string_view rhs ) nssv_noexcept +{ return rhs.compare( lhs ) <= 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator>=( + basic_string_view lhs, + std::basic_string rhs ) nssv_noexcept +{ return lhs.compare( rhs ) >= 0; } + +template< class CharT, class Traits> +nssv_constexpr bool operator>=( + std::basic_string rhs, + basic_string_view lhs ) nssv_noexcept +{ return rhs.compare( lhs ) <= 0; } + +#else // newer compilers: + +#define nssv_BASIC_STRING_VIEW_I(T,U) typename std::decay< basic_string_view >::type + +#if defined(_MSC_VER) // issue 40 +# define nssv_MSVC_ORDER(x) , int=x +#else +# define nssv_MSVC_ORDER(x) /*, int=x*/ +#endif + +// == + +template< class CharT, class Traits nssv_MSVC_ORDER(1) > +nssv_constexpr bool operator==( + basic_string_view lhs, + nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs ) nssv_noexcept +{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } + +template< class CharT, class Traits nssv_MSVC_ORDER(2) > +nssv_constexpr bool operator==( + nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, + basic_string_view rhs ) nssv_noexcept +{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } + +// != + +template< class CharT, class Traits nssv_MSVC_ORDER(1) > +nssv_constexpr bool operator!= ( + basic_string_view < CharT, Traits > lhs, + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept +{ return !( lhs == rhs ); } + +template< class CharT, class Traits nssv_MSVC_ORDER(2) > +nssv_constexpr bool operator!= ( + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, + basic_string_view < CharT, Traits > rhs ) nssv_noexcept +{ return !( lhs == rhs ); } + +// < + +template< class CharT, class Traits nssv_MSVC_ORDER(1) > +nssv_constexpr bool operator< ( + basic_string_view < CharT, Traits > lhs, + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept +{ return lhs.compare( rhs ) < 0; } + +template< class CharT, class Traits nssv_MSVC_ORDER(2) > +nssv_constexpr bool operator< ( + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, + basic_string_view < CharT, Traits > rhs ) nssv_noexcept +{ return lhs.compare( rhs ) < 0; } + +// <= + +template< class CharT, class Traits nssv_MSVC_ORDER(1) > +nssv_constexpr bool operator<= ( + basic_string_view < CharT, Traits > lhs, + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept +{ return lhs.compare( rhs ) <= 0; } + +template< class CharT, class Traits nssv_MSVC_ORDER(2) > +nssv_constexpr bool operator<= ( + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, + basic_string_view < CharT, Traits > rhs ) nssv_noexcept +{ return lhs.compare( rhs ) <= 0; } + +// > + +template< class CharT, class Traits nssv_MSVC_ORDER(1) > +nssv_constexpr bool operator> ( + basic_string_view < CharT, Traits > lhs, + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept +{ return lhs.compare( rhs ) > 0; } + +template< class CharT, class Traits nssv_MSVC_ORDER(2) > +nssv_constexpr bool operator> ( + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, + basic_string_view < CharT, Traits > rhs ) nssv_noexcept +{ return lhs.compare( rhs ) > 0; } + +// >= + +template< class CharT, class Traits nssv_MSVC_ORDER(1) > +nssv_constexpr bool operator>= ( + basic_string_view < CharT, Traits > lhs, + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept +{ return lhs.compare( rhs ) >= 0; } + +template< class CharT, class Traits nssv_MSVC_ORDER(2) > +nssv_constexpr bool operator>= ( + nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, + basic_string_view < CharT, Traits > rhs ) nssv_noexcept +{ return lhs.compare( rhs ) >= 0; } + +#undef nssv_MSVC_ORDER +#undef nssv_BASIC_STRING_VIEW_I + +#endif // compiler-dependent approach to comparisons + +// 24.4.4 Inserters and extractors: + +#if ! nssv_CONFIG_NO_STREAM_INSERTION + +namespace detail { + +template< class Stream > +void write_padding( Stream & os, std::streamsize n ) +{ + for ( std::streamsize i = 0; i < n; ++i ) + os.rdbuf()->sputc( os.fill() ); +} + +template< class Stream, class View > +Stream & write_to_stream( Stream & os, View const & sv ) +{ + typename Stream::sentry sentry( os ); + + if ( !sentry ) + return os; + + const std::streamsize length = static_cast( sv.length() ); + + // Whether, and how, to pad: + const bool pad = ( length < os.width() ); + const bool left_pad = pad && ( os.flags() & std::ios_base::adjustfield ) == std::ios_base::right; + + if ( left_pad ) + write_padding( os, os.width() - length ); + + // Write span characters: + os.rdbuf()->sputn( sv.begin(), length ); + + if ( pad && !left_pad ) + write_padding( os, os.width() - length ); + + // Reset output stream width: + os.width( 0 ); + + return os; +} + +} // namespace detail + +template< class CharT, class Traits > +std::basic_ostream & +operator<<( + std::basic_ostream& os, + basic_string_view sv ) +{ + return detail::write_to_stream( os, sv ); +} + +#endif // nssv_CONFIG_NO_STREAM_INSERTION + +// Several typedefs for common character types are provided: + +typedef basic_string_view string_view; +typedef basic_string_view wstring_view; +#if nssv_HAVE_WCHAR16_T +typedef basic_string_view u16string_view; +typedef basic_string_view u32string_view; +#endif + +}} // namespace nonstd::sv_lite + +// +// 24.4.6 Suffix for basic_string_view literals: +// + +#if nssv_HAVE_USER_DEFINED_LITERALS + +namespace nonstd { +nssv_inline_ns namespace literals { +nssv_inline_ns namespace string_view_literals { + +#if nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS + +nssv_constexpr nonstd::sv_lite::string_view operator "" sv( const char* str, size_t len ) nssv_noexcept // (1) +{ + return nonstd::sv_lite::string_view{ str, len }; +} + +nssv_constexpr nonstd::sv_lite::u16string_view operator "" sv( const char16_t* str, size_t len ) nssv_noexcept // (2) +{ + return nonstd::sv_lite::u16string_view{ str, len }; +} + +nssv_constexpr nonstd::sv_lite::u32string_view operator "" sv( const char32_t* str, size_t len ) nssv_noexcept // (3) +{ + return nonstd::sv_lite::u32string_view{ str, len }; +} + +nssv_constexpr nonstd::sv_lite::wstring_view operator "" sv( const wchar_t* str, size_t len ) nssv_noexcept // (4) +{ + return nonstd::sv_lite::wstring_view{ str, len }; +} + +#endif // nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS + +#if nssv_CONFIG_USR_SV_OPERATOR + +nssv_constexpr nonstd::sv_lite::string_view operator "" _sv( const char* str, size_t len ) nssv_noexcept // (1) +{ + return nonstd::sv_lite::string_view{ str, len }; +} + +nssv_constexpr nonstd::sv_lite::u16string_view operator "" _sv( const char16_t* str, size_t len ) nssv_noexcept // (2) +{ + return nonstd::sv_lite::u16string_view{ str, len }; +} + +nssv_constexpr nonstd::sv_lite::u32string_view operator "" _sv( const char32_t* str, size_t len ) nssv_noexcept // (3) +{ + return nonstd::sv_lite::u32string_view{ str, len }; +} + +nssv_constexpr nonstd::sv_lite::wstring_view operator "" _sv( const wchar_t* str, size_t len ) nssv_noexcept // (4) +{ + return nonstd::sv_lite::wstring_view{ str, len }; +} + +#endif // nssv_CONFIG_USR_SV_OPERATOR + +}}} // namespace nonstd::literals::string_view_literals + +#endif + +// +// Extensions for std::string: +// + +#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS + +namespace nonstd { +namespace sv_lite { + +// Exclude MSVC 14 (19.00): it yields ambiguous to_string(): + +#if nssv_CPP11_OR_GREATER && nssv_COMPILER_MSVC_VERSION != 140 + +template< class CharT, class Traits, class Allocator = std::allocator > +std::basic_string +to_string( basic_string_view v, Allocator const & a = Allocator() ) +{ + return std::basic_string( v.begin(), v.end(), a ); +} + +#else + +template< class CharT, class Traits > +std::basic_string +to_string( basic_string_view v ) +{ + return std::basic_string( v.begin(), v.end() ); +} + +template< class CharT, class Traits, class Allocator > +std::basic_string +to_string( basic_string_view v, Allocator const & a ) +{ + return std::basic_string( v.begin(), v.end(), a ); +} + +#endif // nssv_CPP11_OR_GREATER + +template< class CharT, class Traits, class Allocator > +basic_string_view +to_string_view( std::basic_string const & s ) +{ + return basic_string_view( s.data(), s.size() ); +} + +}} // namespace nonstd::sv_lite + +#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS + +// +// make types and algorithms available in namespace nonstd: +// + +namespace nonstd { + +using sv_lite::basic_string_view; +using sv_lite::string_view; +using sv_lite::wstring_view; + +#if nssv_HAVE_WCHAR16_T +using sv_lite::u16string_view; +#endif +#if nssv_HAVE_WCHAR32_T +using sv_lite::u32string_view; +#endif + +// literal "sv" + +using sv_lite::operator==; +using sv_lite::operator!=; +using sv_lite::operator<; +using sv_lite::operator<=; +using sv_lite::operator>; +using sv_lite::operator>=; + +#if ! nssv_CONFIG_NO_STREAM_INSERTION +using sv_lite::operator<<; +#endif + +#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS +using sv_lite::to_string; +using sv_lite::to_string_view; +#endif + +} // namespace nonstd + +// 24.4.5 Hash support (C++11): + +// Note: The hash value of a string view object is equal to the hash value of +// the corresponding string object. + +#if nssv_HAVE_STD_HASH + +#include + +namespace std { + +template<> +struct hash< nonstd::string_view > +{ +public: + std::size_t operator()( nonstd::string_view v ) const nssv_noexcept + { + return std::hash()( std::string( v.data(), v.size() ) ); + } +}; + +template<> +struct hash< nonstd::wstring_view > +{ +public: + std::size_t operator()( nonstd::wstring_view v ) const nssv_noexcept + { + return std::hash()( std::wstring( v.data(), v.size() ) ); + } +}; + +template<> +struct hash< nonstd::u16string_view > +{ +public: + std::size_t operator()( nonstd::u16string_view v ) const nssv_noexcept + { + return std::hash()( std::u16string( v.data(), v.size() ) ); + } +}; + +template<> +struct hash< nonstd::u32string_view > +{ +public: + std::size_t operator()( nonstd::u32string_view v ) const nssv_noexcept + { + return std::hash()( std::u32string( v.data(), v.size() ) ); + } +}; + +} // namespace std + +#endif // nssv_HAVE_STD_HASH + +nssv_RESTORE_WARNINGS() + +#endif // nssv_HAVE_STD_STRING_VIEW +#endif // NONSTD_SV_LITE_H_INCLUDED diff --git a/src/tiny-string.cc b/src/tiny-string.cc new file mode 100644 index 00000000..4c59804d --- /dev/null +++ b/src/tiny-string.cc @@ -0,0 +1,64 @@ +#include "tiny-string.hh" + +#if defined(TINYUSDZ_USE_THREAD) +#include +#include +#include +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#define nssv_CONFIG_USR_SV_OPERATOR 0 + +// TODO(syoyo): Use C++17 std::string_view when compiled with C++-17 compiler + +// clang and gcc +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) + +#ifdef nsel_CONFIG_NO_EXCEPTIONS +#undef nsel_CONFIG_NO_EXCEPTIONS +#endif +#ifdef nssv_CONFIG_NO_EXCEPTIONS +#undef nssv_CONFIG_NO_EXCEPTIONS +#endif + +#define nsel_CONFIG_NO_EXCEPTIONS 0 +#define nssv_CONFIG_NO_EXCEPTIONS 0 +#else +// -fno-exceptions +#if !defined(nsel_CONFIG_NO_EXCEPTIONS) +#define nsel_CONFIG_NO_EXCEPTIONS 1 +#endif + +#define nssv_CONFIG_NO_EXCEPTIONS 1 +#endif +#include "nonstd/string_view.hpp" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace tinyusdz { + +namespace str { + +bool print_float_array(std::vector &v, + std::string &dst, const char delimiter) { + + + // TODO + (void)v; + (void)dst; + (void)delimiter; + + return false; +} + + +} + + +} // namespace tinyusdz From ce42beee049e67b7117ce604be8cec4353e383a6 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 2 Aug 2024 18:39:25 +0900 Subject: [PATCH 002/387] add missing tiny-string.hh --- src/tiny-string.cc | 4 ++++ src/tiny-string.hh | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/tiny-string.hh diff --git a/src/tiny-string.cc b/src/tiny-string.cc index 4c59804d..32a1b7b5 100644 --- a/src/tiny-string.cc +++ b/src/tiny-string.cc @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: MIT +// Copyright 2024-Present Light Transport Entertainment Inc. #include "tiny-string.hh" #if defined(TINYUSDZ_USE_THREAD) @@ -11,6 +13,8 @@ #pragma clang diagnostic ignored "-Weverything" #endif +#include "external/fast_float/include/fast_float/fast_float.h" + #define nssv_CONFIG_USR_SV_OPERATOR 0 // TODO(syoyo): Use C++17 std::string_view when compiled with C++-17 compiler diff --git a/src/tiny-string.hh b/src/tiny-string.hh new file mode 100644 index 00000000..0af28637 --- /dev/null +++ b/src/tiny-string.hh @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +// Copyright 2024-Present Light Transport Entertainment Inc. + +#pragma once + +#include +#include + +namespace tinyusdz { + +namespace str { + +bool print_float_array(std::vector &v, + std::string &dst, const char delimiter); + +} // namespace str + +} // namespace tinyusdz + + From e36c9a8a18ef35a3b093a7c11c32cfcc7d216ef5 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 20 Sep 2024 05:30:34 +0900 Subject: [PATCH 003/387] Addding short stack container classes... --- src/tiny-string.hh | 179 +++++++++++++++++++++++++++++++++++++++++++++ src/tiny-vector.hh | 80 ++++++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100644 src/tiny-string.hh create mode 100644 src/tiny-vector.hh diff --git a/src/tiny-string.hh b/src/tiny-string.hh new file mode 100644 index 00000000..f6d4461f --- /dev/null +++ b/src/tiny-string.hh @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: MIT +// Copyright 2024-Present Syoyo Fujita. + +/// +/// Simple but fast string library. +/// + +#include +#include +#include +#include +#include + +#include "tiny-vector.hh" + +namespace tinyusdz { + +// default: Up to 1G char +static size_t strlen(const char *s, size_t max_len = 1024u*1024u*1024u) { + if (!s) { + return 0; + } + + size_t i = 0; + while(i < max_len) { + if (s[i] == '\0') { + return i; + } + i++; + } + + return i; +} + + +template +struct string { + static_assert(N >= 8, "N must be 8 or larger."); + + public: + + string() {} + ~string() { + _delete_string(); + } + + string(const char *s) { + _copy_string(s); + } + + string(const std::string &s) : string(s.c_str()) { + } + + string(const string &rhs) : string(rhs.c_str()) { + } + + string(string &&rhs) { + + _delete_string(); + + _buf = std::exchange(rhs._buf, nullptr); + _len = std::exchange(rhs._len, 0); + } + + string &operator=(const string &rhs) { + if (this == &rhs) { + return *this; + } + + _copy_string(rhs.c_str()); + + return *this; + } + + string &operator=(string &&rhs) noexcept { + if (this == &rhs) { + return *this; + } + + _delete_string(); + + _buf = std::exchange(rhs._buf, nullptr); + _len = std::exchange(rhs._len, 0); + + return *this; + } + + const char *c_str() const { + return reinterpret_cast(_buf); + } + + size_t size() const { + return _len; + } + + std::string to_std_string() { + char *p = reinterpret_cast(_buf); + std::string s(p, _len); + return s; + } + + private: + void _delete_string() { + if (_len >= N) { + char *p = reinterpret_cast(_buf); + delete[] p; + } + memset(_buf, 0, 8); + _len = 0; + } + + void _copy_string(const char *s) { + + if (_len > 0) { + if (_len >= N) { + char *p = reinterpret_cast(_buf); + delete[] p; + memset(_buf, 0, 8); + } + } + + char *dst = reinterpret_cast(_buf); + + _len = strlen(s); + if (_len >= N) { + dst = new char[_len+1]; + } else { + memcpy(dst, s, _len); + } + dst[_len] = '\0'; + + } + + // TODO: Ues custom vector class. + //char *_s{nullptr}; // end with '\0' + char _buf[N]{}; + size_t _len{0}; +}; + +// just a retain the pointer address. +struct string_view { + public: + + string_view() {} + ~string_view() { + _s = nullptr; + } + + string_view(const char *s) { + _len = strlen(s); + _s = s; + } + + string_view(const std::string &s) : string_view(s.c_str()) { + } + + const char *c_str() const { + return _s; + } + + size_t size() const { + return _len; + } + + private: + // TODO: Ues custom vector class. + const char *_s{nullptr}; // end with '\0' + size_t _len{0}; +}; + +namespace str { + +bool print_float_array(std::vector &v, + std::string &dst, const char delimiter = ','); + + +} + +} // namespace tinyusdz diff --git a/src/tiny-vector.hh b/src/tiny-vector.hh new file mode 100644 index 00000000..7bb7aecc --- /dev/null +++ b/src/tiny-vector.hh @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +// Copyright 2024 - Present : Syoyo Fujita +// + +// Simple short stack vector +// Inspired from +// - https://github.com/p-ranav/small_vector +// - https://chromium.googlesource.com/chromium/chromium/+/master/base/stack_container.h + +#pragma once + +#include +#include +#include + +namespace tinyusdz { + +template +class StackAllocator : public std::allocator { + + typedef typename std::allocator::pointer pointer; + typedef typename std::allocator::size_type size_type; + + struct StackBuf + { + T *data() { return reinterpret_cast(_buf); } + const T *data() const { + return reinterpret_cast(_buf); + } + + char _buf[sizeof(T[Size])]; + bool use_stack{false}; + }; + + pointer allocate(size_type n, void *hint = nullptr) { + if (_buf && !_buf->use_stack && (n <= Size)) { + _buf->use_stack = true; + return _buf->data(); + } else { + std::allocator::allocate(n, hint); + } + } + + void deallocate(pointer p, size_type sz) { + if (_buf && (p == _buf->data())) { + _buf->use_stack = false; + } else { + std::allocator::deallocate(p, sz); + } + } + + private: + StackBuf *_buf{nullptr}; +}; + +#if 0 +template > class short_vector { + private: + std::array _stack; + // TODO: Implement our own vector class. + std::vector _heap; + std::size_t _size{0}; + + public: + typedef T value_type; + typedef std::size_t size_type; + typedef value_type &reference; + typedef const value_type &const_reference; + typedef Allocator allocator_type; + typedef T *pointer; + typedef const T *const_pointer; + + small_vector() = default; +} +#endif + +} // namespace tinyusdz + + + From 56500d49719a8b45d5585bf6c77af751f6ff3494 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 23 Sep 2024 04:01:16 +0900 Subject: [PATCH 004/387] container w.i.p. --- src/tiny-container.hh | 114 ++++++++++++++++++++++++++++++++++++++++++ src/tiny-string.hh | 43 ++++++++++------ src/tiny-vector.hh | 80 ----------------------------- 3 files changed, 142 insertions(+), 95 deletions(-) create mode 100644 src/tiny-container.hh delete mode 100644 src/tiny-vector.hh diff --git a/src/tiny-container.hh b/src/tiny-container.hh new file mode 100644 index 00000000..b438d15d --- /dev/null +++ b/src/tiny-container.hh @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +// Copyright 2024 - Present : Syoyo Fujita +// + +// Simple stack container class for custom vector/string. +// Inspired from +// - https://github.com/p-ranav/small_vector +// - https://chromium.googlesource.com/chromium/chromium/+/master/base/stack_container.h + +#pragma once + +#include +#include +#include + +namespace tinyusdz { + +template +class StackAllocator : public std::allocator { + + typedef typename std::allocator::pointer pointer; + typedef typename std::allocator::size_type size_type; + + struct StackBuf + { + T *data() { return reinterpret_cast(_buf); } + const T *data() const { + return reinterpret_cast(_buf); + } + + char _buf[sizeof(T[Size])]; + bool use_stack{false}; + }; + + pointer allocate(size_type n, void *hint = nullptr) { + if (_buf && !_buf->use_stack && (n <= Size)) { + _buf->use_stack = true; + return _buf->data(); + } else { + std::allocator::allocate(n, hint); + } + } + + void deallocate(pointer p, size_type sz) { + if (_buf && (p == _buf->data())) { + _buf->use_stack = false; + } else { + std::allocator::deallocate(p, sz); + } + } + + private: + StackBuf *_buf{nullptr}; +}; + +// T : container type +// N : capacity +template +class StackContainer { + public: + + using Allocator = StackAllocator; + + StackContainer() : _allocator(&_stack), _container(_allocator) { + _container.reserve(Size); + } + + T &get() { return _container; } + const T &get() const { return _container; } + + T *operator->() { return &_container; } + const T *operator->() const { return &_container; } + + protected: + typename Allocator::StackBuf _stack; + + Allocator _allocator; + T _container; + + // disallow copy and assign. + StackContainer(const StackContainer &) = delete; + void operator=(const StackContainer &) = delete; + + +}; + +template +class StackVector : public StackContainer>, Size> { + public: + StackVector() : StackContainer>, Size>() {} + + + StackVector(const StackVector &rhs) : StackContainer>, Size>() { + this->get().assign(rhs->begin(), rhs->end()); + } + + StackVector &operator=(const StackVector &rhs) { + this->get().assign(rhs->begin(), rhs->end()); + return *this; + } + + T &operator[](size_t i) { return this->get().operator[](i); } + const T &operator[](size_t i) const { + return this->get().operator[](i); + } + + // TODO: lvalue ctor +}; + + +} // namespace tinyusdz + + + diff --git a/src/tiny-string.hh b/src/tiny-string.hh index f6d4461f..bb5edc31 100644 --- a/src/tiny-string.hh +++ b/src/tiny-string.hh @@ -11,7 +11,7 @@ #include #include -#include "tiny-vector.hh" +#include "tiny-container.hh" namespace tinyusdz { @@ -34,27 +34,27 @@ static size_t strlen(const char *s, size_t max_len = 1024u*1024u*1024u) { template -struct string { +struct tstring { static_assert(N >= 8, "N must be 8 or larger."); public: - string() {} - ~string() { + tstring() {} + ~tstring() { _delete_string(); } - string(const char *s) { + tstring(const char *s) { _copy_string(s); } - string(const std::string &s) : string(s.c_str()) { + tstring(const std::string &s) : tstring(s.c_str()) { } - string(const string &rhs) : string(rhs.c_str()) { + tstring(const tstring &rhs) : tstring(rhs.c_str()) { } - string(string &&rhs) { + tstring(tstring &&rhs) { _delete_string(); @@ -62,7 +62,7 @@ struct string { _len = std::exchange(rhs._len, 0); } - string &operator=(const string &rhs) { + tstring &operator=(const tstring &rhs) { if (this == &rhs) { return *this; } @@ -72,7 +72,7 @@ struct string { return *this; } - string &operator=(string &&rhs) noexcept { + tstring &operator=(tstring &&rhs) noexcept { if (this == &rhs) { return *this; } @@ -138,20 +138,20 @@ struct string { }; // just a retain the pointer address. -struct string_view { +struct tstring_view { public: - string_view() {} - ~string_view() { + constexpr tstring_view() {} + ~tstring_view() { _s = nullptr; } - string_view(const char *s) { + constexpr tstring_view(const char *s) { _len = strlen(s); _s = s; } - string_view(const std::string &s) : string_view(s.c_str()) { + constexpr tstring_view(const std::string &s) : tstring_view(s.c_str()) { } const char *c_str() const { @@ -170,6 +170,19 @@ struct string_view { namespace str { +bool parse_int(const tstring_view &sv, int32_t *ret); +bool parse_int64(const tstring_view &sv, int64_t *ret); + +bool parse_uint(const tstring_view &sv, uint32_t *ret); +bool parse_uint64(const tstring_view &sv, uint64_t *ret); + +bool parse_float(const tstring_view &sv, float *ret); +bool parse_double(const tstring_view &sv, double *ret); + +bool parse_int_arary(const tstring_view &sv, std::vector *result, const char delimiter = ','); +bool parse_float_arary(const tstring_view &sv, std::vector *result, const char delimiter = ','); +bool parse_double_arary(const tstring_view &sv, std::vector *result, const char delimiter = ','); + bool print_float_array(std::vector &v, std::string &dst, const char delimiter = ','); diff --git a/src/tiny-vector.hh b/src/tiny-vector.hh deleted file mode 100644 index 7bb7aecc..00000000 --- a/src/tiny-vector.hh +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright 2024 - Present : Syoyo Fujita -// - -// Simple short stack vector -// Inspired from -// - https://github.com/p-ranav/small_vector -// - https://chromium.googlesource.com/chromium/chromium/+/master/base/stack_container.h - -#pragma once - -#include -#include -#include - -namespace tinyusdz { - -template -class StackAllocator : public std::allocator { - - typedef typename std::allocator::pointer pointer; - typedef typename std::allocator::size_type size_type; - - struct StackBuf - { - T *data() { return reinterpret_cast(_buf); } - const T *data() const { - return reinterpret_cast(_buf); - } - - char _buf[sizeof(T[Size])]; - bool use_stack{false}; - }; - - pointer allocate(size_type n, void *hint = nullptr) { - if (_buf && !_buf->use_stack && (n <= Size)) { - _buf->use_stack = true; - return _buf->data(); - } else { - std::allocator::allocate(n, hint); - } - } - - void deallocate(pointer p, size_type sz) { - if (_buf && (p == _buf->data())) { - _buf->use_stack = false; - } else { - std::allocator::deallocate(p, sz); - } - } - - private: - StackBuf *_buf{nullptr}; -}; - -#if 0 -template > class short_vector { - private: - std::array _stack; - // TODO: Implement our own vector class. - std::vector _heap; - std::size_t _size{0}; - - public: - typedef T value_type; - typedef std::size_t size_type; - typedef value_type &reference; - typedef const value_type &const_reference; - typedef Allocator allocator_type; - typedef T *pointer; - typedef const T *const_pointer; - - small_vector() = default; -} -#endif - -} // namespace tinyusdz - - - From c84da86ea01b24dc67bcfcef78e91f9981a73a94 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 28 Mar 2025 08:47:21 +0900 Subject: [PATCH 005/387] lex fp experiment. --- sandbox/parse_fp/Makefile | 2 + sandbox/parse_fp/parse_fp.cc | 235 +++++++++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 sandbox/parse_fp/Makefile create mode 100644 sandbox/parse_fp/parse_fp.cc diff --git a/sandbox/parse_fp/Makefile b/sandbox/parse_fp/Makefile new file mode 100644 index 00000000..04eedc55 --- /dev/null +++ b/sandbox/parse_fp/Makefile @@ -0,0 +1,2 @@ +all: + clang++ -O2 -g parse_fp.cc diff --git a/sandbox/parse_fp/parse_fp.cc b/sandbox/parse_fp/parse_fp.cc new file mode 100644 index 00000000..f394f0af --- /dev/null +++ b/sandbox/parse_fp/parse_fp.cc @@ -0,0 +1,235 @@ +#include +#include + +struct Lexer { + + void skip_whitespaces() { + + while (eof()) { + + char s = *curr; + if ((s == ' ') || (s == '\t') || (s == '\f') || (s == '\n') || (s == '\r') || (s == '\v')) { + curr++; + } + break; + } + + } + + bool skip_until_delim_or_close_paren(const char delim, const char close_paren) { + + while (eof()) { + + char s = *curr; + if ((s == delim) || (s == close_paren)) { + return true; + } + + curr++; + } + + return false; + } + + bool char1(char *result) { + if (eof()) { + return false; + } + *result = *curr; + curr++; + + return true; + } + + bool look_char1(char *result) { + if (eof()) { + return false; + } + *result = *curr; + + return true; + } + + bool consume_char1() { + if (eof()) { + return false; + } + curr++; + + return true; + } + + inline bool eof() const { + return (curr >= p_end); + } + + inline bool unwind_char1() { + if (curr <= p_begin) { + return false; + } + + curr--; + return true; + } + + bool lex_float(uint16_t &len, bool &truncated) { + + // truncate too large fp string + // (e.g. "0.100000010000000100000010000..." + constexpr size_t n_trunc_chars = 256; // 65535 at max. + + size_t n = 0; + bool has_sign = false; + bool has_exponential = false; + bool has_dot = false; + + // oneOf [0-9, eE, -+] + while (eof() || (n >= n_trunc_chars)) { + char c; + look_char1(&c); + if ((c == '-') || (c == '+')) { + if (has_sign) { + return false; + } + has_sign = true; + } else if (c == '.') { + if (has_dot) { + return false; + } + has_dot = true; + } else if ((c == 'e') || (c == 'E')) { + if (has_exponential) { + return false; + } + has_exponential = true; + } else if ((c >= '0') && (c <= '9')) { + } else { + break; + } + + consume_char1(); + n++; + } + + if (n == 0) { + return false; + } + + truncated = (n >= n_trunc_chars); + + len = uint16_t(n); + return true; + } + + const char *p_begin{nullptr}; + const char *p_end{nullptr}; + + const char *curr{nullptr}; +}; + + +struct fp_lex_span +{ + const char *p_begin{nullptr}; + uint16_t length{0}; +}; + +// '[' + fp0 + "," + fp1 + ", " ... ']' +// allow_delim_at_last is true: '[' + fp0 + "," + fp1 + ", " ... "," + ']' +bool lex_float_array( + const char *p_begin, + const char *p_end, + std::vector &result, + bool allow_delim_at_last = true, char delim = ',', char open_paren = '[', char close_paren = ']') { + + if (p_begin <= p_end) { + return false; + } + + Lexer lexer; + lexer.p_begin = p_begin; + lexer.p_end = p_end; + lexer.curr = p_begin; + + + // '[' + { + char c; + if (!lexer.char1(&c)) { + return false; + } + + if (c != open_paren) { + return false; + } + } + + lexer.skip_whitespaces(); + + for (const char *curr = p_begin; curr < p_end; curr++) { + if (*curr == '\0') { + return false; + } + + fp_lex_span sp; + sp.p_begin = curr; + + uint16_t length{0}; + bool truncated{false}; + + if (!lexer.lex_float(length, truncated)) { + return false; + } + + sp.length = length; + + if (truncated) { + // skip until encountering delim or close_paren. + if (!lexer.skip_until_delim_or_close_paren(delim, close_paren)) { + return false; + } + } + + + result.emplace_back(std::move(sp)); + + lexer.skip_whitespaces(); + } + + lexer.skip_whitespaces(); + + if (allow_delim_at_last) { + char c; + if (!lexer.look_char1(&c)) { + return false; + } + + if (c == delim) { + lexer.consume_char1(); + } + + lexer.skip_whitespaces(); + } + + // ']' + { + char c; + if (!lexer.char1(&c)) { + return false; + } + + if (c != close_paren) { + return false; + } + } + + return false; +} + +int main(int argc, char **argv) +{ + std::vector result; + result.reserve(1024*1024); + + return 0; +} From b38b46b59c1fb89a7babe37722ca06f602ca3816 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 29 Mar 2025 06:09:20 +0900 Subject: [PATCH 006/387] lexing float array. --- sandbox/parse_fp/README.md | 9 ++ sandbox/parse_fp/parse_fp.cc | 168 +++++++++++++++++++++++++++-------- 2 files changed, 141 insertions(+), 36 deletions(-) create mode 100644 sandbox/parse_fp/README.md diff --git a/sandbox/parse_fp/README.md b/sandbox/parse_fp/README.md new file mode 100644 index 00000000..d2eed031 --- /dev/null +++ b/sandbox/parse_fp/README.md @@ -0,0 +1,9 @@ +Ryzen 3900X +-O2 -g + +1024*1024*32(32M floats) : roughly 870 msecs to lex. + +# TODO + +multithreading? + diff --git a/sandbox/parse_fp/parse_fp.cc b/sandbox/parse_fp/parse_fp.cc index f394f0af..af535799 100644 --- a/sandbox/parse_fp/parse_fp.cc +++ b/sandbox/parse_fp/parse_fp.cc @@ -1,11 +1,39 @@ #include #include +#include +#include + +#include + +std::string gen_floatarray(size_t n, bool delim_at_end) { + + std::stringstream ss; + + std::random_device rd; + + std::mt19937 engine(rd()); + std::uniform_real_distribution<> dist(-10000.0, 10000.0); + + ss << "["; + for (size_t i = 0; i < n; i++) { + double f = dist(engine); + ss << std::to_string(f); + if (delim_at_end) { + ss << ","; + } else if (i < (n-1)) { + ss << ","; + } + } + ss << "]"; + + return ss.str(); +} struct Lexer { void skip_whitespaces() { - while (eof()) { + while (!eof()) { char s = *curr; if ((s == ' ') || (s == '\t') || (s == '\f') || (s == '\n') || (s == '\r') || (s == '\v')) { @@ -18,7 +46,7 @@ struct Lexer { bool skip_until_delim_or_close_paren(const char delim, const char close_paren) { - while (eof()) { + while (!eof()) { char s = *curr; if ((s == delim) || (s == close_paren)) { @@ -84,7 +112,7 @@ struct Lexer { bool has_dot = false; // oneOf [0-9, eE, -+] - while (eof() || (n >= n_trunc_chars)) { + while (!eof() || (n < n_trunc_chars)) { char c; look_char1(&c); if ((c == '-') || (c == '+')) { @@ -112,6 +140,7 @@ struct Lexer { } if (n == 0) { + len = 0; return false; } @@ -121,10 +150,21 @@ struct Lexer { return true; } + void push_error(const std::string &msg) { + err_ += msg + "\n"; + } + + std::string get_error() const { + return err_; + } + const char *p_begin{nullptr}; const char *p_end{nullptr}; const char *curr{nullptr}; + + private: + std::string err_; }; @@ -140,9 +180,12 @@ bool lex_float_array( const char *p_begin, const char *p_end, std::vector &result, + std::string &err, bool allow_delim_at_last = true, char delim = ',', char open_paren = '[', char close_paren = ']') { - if (p_begin <= p_end) { + if (p_begin >= p_end) { + err = "Invalid input\n"; + return false; } @@ -156,28 +199,80 @@ bool lex_float_array( { char c; if (!lexer.char1(&c)) { + err = "Input too short.\n"; return false; } if (c != open_paren) { + err = "Input does not begin with open parenthesis character.\n"; return false; } } lexer.skip_whitespaces(); - for (const char *curr = p_begin; curr < p_end; curr++) { - if (*curr == '\0') { - return false; + while (!lexer.eof()) { + + bool prev_is_delim = false; + + // is ','? + { + char c; + if (!lexer.look_char1(&c)) { + lexer.push_error("Invalid character found."); + err = lexer.get_error(); + return false; + } + + if (c == delim) { + // Array element starts with delimiter, e.g. '[ ,' + if (result.empty()) { + lexer.push_error("Array element starts with the delimiter character."); + err = lexer.get_error(); + return false; + } + prev_is_delim = true; + lexer.consume_char1(); + } + + lexer.skip_whitespaces(); + } + + // is ']'? + { + char c; + if (!lexer.look_char1(&c)) { + lexer.push_error("Failed to read a character."); + err = lexer.get_error(); + return false; + } + + if (c == close_paren) { + if (prev_is_delim) { + if (allow_delim_at_last) { + // ok + return true; + } else { + lexer.push_error("Delimiter character is not allowed before the closing parenthesis character."); + err = lexer.get_error(); + return false; + } + } else { + // ok + return true; + } + } } fp_lex_span sp; - sp.p_begin = curr; + sp.p_begin = lexer.curr; uint16_t length{0}; bool truncated{false}; if (!lexer.lex_float(length, truncated)) { + lexer.push_error("Input is not a floating point literal."); + err = lexer.get_error(); return false; } @@ -186,6 +281,8 @@ bool lex_float_array( if (truncated) { // skip until encountering delim or close_paren. if (!lexer.skip_until_delim_or_close_paren(delim, close_paren)) { + lexer.push_error("Failed to seek to delimiter or closing parenthesis character."); + err = lexer.get_error(); return false; } } @@ -196,34 +293,7 @@ bool lex_float_array( lexer.skip_whitespaces(); } - lexer.skip_whitespaces(); - - if (allow_delim_at_last) { - char c; - if (!lexer.look_char1(&c)) { - return false; - } - - if (c == delim) { - lexer.consume_char1(); - } - - lexer.skip_whitespaces(); - } - - // ']' - { - char c; - if (!lexer.char1(&c)) { - return false; - } - - if (c != close_paren) { - return false; - } - } - - return false; + return true; } int main(int argc, char **argv) @@ -231,5 +301,31 @@ int main(int argc, char **argv) std::vector result; result.reserve(1024*1024); + bool delim_at_end = true; + size_t n = 1024*1024*32; + if (argc > 1) { + n = std::stoi(argv[1]); + } + if (argc > 2) { + delim_at_end = std::stoi(argv[2]) > 0; + } + + std::string input = gen_floatarray(n, delim_at_end); + //std::cout << input << "\n"; + + auto start = std::chrono::steady_clock::now(); + + std::string err; + if (!lex_float_array(input.c_str(), input.c_str() + input.size(), result, err)) { + std::cerr << "parse error\n"; + std::cerr << err << "\n"; + return -1; + } + auto end = std::chrono::steady_clock::now(); + + std::cout << "n elems " << result.size() << "\n"; + + std::cout << "lex time: " << std::chrono::duration_cast(end - start).count() << " [ms]\n"; + return 0; } From c9dcb3907ecdb0bf34e0a70917a10e3acc4d499e Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 29 Mar 2025 23:38:35 +0900 Subject: [PATCH 007/387] parse vec array w.i.p. --- sandbox/parse_fp/parse_fp.cc | 148 ++++++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 1 deletion(-) diff --git a/sandbox/parse_fp/parse_fp.cc b/sandbox/parse_fp/parse_fp.cc index af535799..b39b44fe 100644 --- a/sandbox/parse_fp/parse_fp.cc +++ b/sandbox/parse_fp/parse_fp.cc @@ -174,6 +174,12 @@ struct fp_lex_span uint16_t length{0}; }; +template +struct vec_lex_span +{ + fp_lex_span vspans[N]; +}; + // '[' + fp0 + "," + fp1 + ", " ... ']' // allow_delim_at_last is true: '[' + fp0 + "," + fp1 + ", " ... "," + ']' bool lex_float_array( @@ -181,7 +187,10 @@ bool lex_float_array( const char *p_end, std::vector &result, std::string &err, - bool allow_delim_at_last = true, char delim = ',', char open_paren = '[', char close_paren = ']') { + const bool allow_delim_at_last = true, + const char delim = ',', + const char open_paren = '[', + const char close_paren = ']') { if (p_begin >= p_end) { err = "Invalid input\n"; @@ -296,6 +305,143 @@ bool lex_float_array( return true; } +bool lex_vec2_array( + Lexer &lexer, + std::string &err, + vec_lex_span<2> &result, + const char vec_open_paren = '(', + const char vec_close_paren = ')') { + + return false; +} + + +bool lex_float2_array( + const char *p_begin, + const char *p_end, + std::vector> &result, + std::string &err, + bool allow_delim_at_last = true, + const char delim = ',', + const char arr_open_paren = '[', + const char arr_close_paren = ']', + const char vec_open_paren = '(', + const char vec_close_paren = ')') { + + if (p_begin >= p_end) { + err = "Invalid input\n"; + + return false; + } + + Lexer lexer; + lexer.p_begin = p_begin; + lexer.p_end = p_end; + lexer.curr = p_begin; + + + // '[' + { + char c; + if (!lexer.char1(&c)) { + err = "Input too short.\n"; + return false; + } + + if (c != arr_open_paren) { + err = "Input does not begin with open parenthesis character.\n"; + return false; + } + } + + lexer.skip_whitespaces(); + + while (!lexer.eof()) { + + bool prev_is_delim = false; + + // is ','? + { + char c; + if (!lexer.look_char1(&c)) { + lexer.push_error("Invalid character found."); + err = lexer.get_error(); + return false; + } + + if (c == delim) { + // Array element starts with delimiter, e.g. '[ ,' + if (result.empty()) { + lexer.push_error("Array element starts with the delimiter character."); + err = lexer.get_error(); + return false; + } + prev_is_delim = true; + lexer.consume_char1(); + } + + lexer.skip_whitespaces(); + } + + // is ']'? + { + char c; + if (!lexer.look_char1(&c)) { + lexer.push_error("Failed to read a character."); + err = lexer.get_error(); + return false; + } + + if (c == arr_close_paren) { + if (prev_is_delim) { + if (allow_delim_at_last) { + // ok + return true; + } else { + lexer.push_error("Delimiter character is not allowed before the closing parenthesis character."); + err = lexer.get_error(); + return false; + } + } else { + // ok + return true; + } + } + } + + // '(' + fp + ',' + fp + ')' + fp_lex_span sp; + sp.p_begin = lexer.curr; + + uint16_t length{0}; + bool truncated{false}; + + if (!lexer.lex_float(length, truncated)) { + lexer.push_error("Input is not a floating point literal."); + err = lexer.get_error(); + return false; + } + + sp.length = length; + + if (truncated) { + // skip until encountering delim or close_paren. + if (!lexer.skip_until_delim_or_close_paren(delim, arr_close_paren)) { + lexer.push_error("Failed to seek to delimiter or closing parenthesis character."); + err = lexer.get_error(); + return false; + } + } + + + result.emplace_back(std::move(sp)); + + lexer.skip_whitespaces(); + } + + return true; +} + int main(int argc, char **argv) { std::vector result; From 81dfc0a5a974e623a4abbee8fbd6c78f787f53af Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 31 Mar 2025 22:38:26 +0900 Subject: [PATCH 008/387] add dragonbox to print floating point. --- README.md | 1 + src/external/dragonbox/LICENSE-Apache2-LLVM | 218 + src/external/dragonbox/LICENSE-Boost | 23 + src/external/dragonbox/README.md | 263 ++ src/external/dragonbox/dragonbox.h | 4205 +++++++++++++++++ src/external/dragonbox/dragonbox_to_chars.cpp | 545 +++ src/external/dragonbox/dragonbox_to_chars.h | 388 ++ 7 files changed, 5643 insertions(+) create mode 100644 src/external/dragonbox/LICENSE-Apache2-LLVM create mode 100644 src/external/dragonbox/LICENSE-Boost create mode 100644 src/external/dragonbox/README.md create mode 100644 src/external/dragonbox/dragonbox.h create mode 100644 src/external/dragonbox/dragonbox_to_chars.cpp create mode 100644 src/external/dragonbox/dragonbox_to_chars.h diff --git a/README.md b/README.md index 7dedbf9e..ef2354cc 100644 --- a/README.md +++ b/README.md @@ -552,3 +552,4 @@ Some helper code is licensed under MIT license. * pugixml: MIT license. https://github.com/zeux/pugixml * nanoflann: 2-clause BSD license. https://github.com/jlblancoc/nanoflann * tinymeshutils: MIT license. https://github.com/syoyo/tinymeshutils +* dragonbox : Apache 2.0 or Boost 1.0 license(tinyusdz prefer Boost 1.0 license) https://github.com/jk-jeon/dragonbox diff --git a/src/external/dragonbox/LICENSE-Apache2-LLVM b/src/external/dragonbox/LICENSE-Apache2-LLVM new file mode 100644 index 00000000..bd8b243d --- /dev/null +++ b/src/external/dragonbox/LICENSE-Apache2-LLVM @@ -0,0 +1,218 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. diff --git a/src/external/dragonbox/LICENSE-Boost b/src/external/dragonbox/LICENSE-Boost new file mode 100644 index 00000000..36b7cd93 --- /dev/null +++ b/src/external/dragonbox/LICENSE-Boost @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/external/dragonbox/README.md b/src/external/dragonbox/README.md new file mode 100644 index 00000000..cf618f0c --- /dev/null +++ b/src/external/dragonbox/README.md @@ -0,0 +1,263 @@ +# Dragonbox +This library is a reference implementation of [Dragonbox](other_files/Dragonbox.pdf) in C++. + +Dragonbox is a float-to-string conversion algorithm based on a beautiful algorithm [Schubfach](https://drive.google.com/file/d/1IEeATSVnEE6TkrHlCYNY2GjaraBjOT4f/edit), developed by Raffaello Giulietti in 2017-2018. Dragonbox is further inspired by [Grisu](https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf) and [Grisu-Exact](https://github.com/jk-jeon/Grisu-Exact). + +# Introduction +Dragonbox generates a pair of integers from a floating-point number: the decimal significand and the decimal exponent of the input floating-point number. These integers can then be used for string generation of decimal representation of the input floating-point number, the procedure commonly called ````ftoa```` or ````dtoa````. + +The algorithm guarantees three things: + +1) It has the roundtrip guarantee; that is, a correct parser interprets the generated output string as the original input floating-point number. (See [here](https://github.com/jk-jeon/dragonbox/blob/master/README.md#precise-meaning-of-roundtrip-gurantee) for some explanation on this.) + +2) The output is of the shortest length; that is, no other output strings that are interpreted as the input number can contain less number of significand digits than the output of Dragonbox. + +3) The output is correctly rounded: the number generated by Dragonbox is the closest to the actual value of the input number among possible outputs of minimum number of digits. + +# About the Name "Dragonbox" +The core idea of Schubfach, which Dragonbox is based on, is a continuous analogue of discrete [pigeonhole principle](https://en.wikipedia.org/wiki/Pigeonhole_principle). The name *Schubfach* is coming from the German name of the pigeonhole principle, *Schubfachprinzip*, meaning "drawer principle". Since another name of the pigeonhole principle is *Dirichlet's box principle*, I decided to call my algorithm "Dragonbox" to honor its origins: Schubfach (box) and Grisu (dragon). + +# How to Use +Although Dragonbox is intended for float-to-string conversion routines, the actual string generation is not officially a part of the algorithm. Dragonbox just outputs two integers (the decimal significand/exponent) that can be consumed by a string generation procedure. The header file [`include/dragonbox/dragonbox.h`](include/dragonbox/dragonbox.h) includes everything needed for this (it is header-only). Nevertheless, a string generation procedure is included in the library. There are two additional files needed for that: [`include/dragonbox/dragonbox_to_chars.h`](include/dragonbox/dragonbox_to_chars.h) and [`source/dragonbox_to_chars.cpp`](source/dragonbox_to_chars.cpp). Since there are only three files, it should be not difficult to set up this library manually if you want, but you can also use it via CMake as explained below. If you are not familiar with CMake, I recommend you to have a look at [this](https://cliutils.gitlab.io/modern-cmake/) wonderful introduction. + +## Installing Dragonbox +The following will create platform-specific build files on your directory: +``` +git clone https://github.com/jk-jeon/dragonbox +cd dragonbox +mkdir build +cd build +cmake .. +``` +If you only want [`dragonbox.h`](include/dragonbox/dragonbox.h) but not [`dragonbox_to_chars.h`](include/dragonbox/dragonbox_to_chars.h)/[`.cpp`](source/dragonbox_to_chars.cpp), you can do the following to install [`dragonbox.h`](include/dragonbox/dragonbox.h) into your system: +``` +cmake .. -DDRAGONBOX_INSTALL_TO_CHARS=OFF +cmake --install . +``` +If you want the string generation part as well, build the generated files using platform-specific build tools (`make` or Visual Studio for example) and then perform +``` +cmake --install . +``` +on the `build` directory. + +## Including Dragonbox into CMake project +The easiest way to include Dragonbox in a CMake project is to do the following: +```cmake +include(FetchContent) +FetchContent_Declare( + dragonbox + GIT_REPOSITORY https://github.com/jk-jeon/dragonbox +) +FetchContent_MakeAvailable(dragonbox) +target_link_libraries(my_target dragonbox::dragonbox) # or dragonbox::dragonbox_to_chars +``` +Or, if you already have installed Dragonbox in your system, you can include it with: +```cmake +find_package(dragonbox) +target_link_libraries(my_target dragonbox::dragonbox) # or dragonbox::dragonbox_to_chars +``` + +# Language Standard +The library requires C++11 or higher. Since C++20, every function provided is `constexpr`. + +# Usage Examples +(Simple string generation from `float/double`) +```cpp +#include "dragonbox/dragonbox_to_chars.h" +constexpr int buffer_length = 1 + // for '\0' + jkj::dragonbox::max_output_string_length; +double x = 1.234; // Also works for float +char buffer[buffer_length]; + +// Null-terminate the buffer and return the pointer to the null character +// Hence, the length of the string is (end_ptr - buffer) +// buffer is now { '1', '.', '2', '3', '4', 'E', '0', '\0', (garbages) } +char* end_ptr = jkj::dragonbox::to_chars(x, buffer); + +// Does not null-terminate the buffer; returns the next-to-end pointer +// buffer is now { '1', '.', '2', '3', '4', 'E', '0', (garbages) } +// you can wrap the buffer with things like std::string_view +end_ptr = jkj::dragonbox::to_chars_n(x, buffer); +``` + +(Direct use of `jkj::dragonbox::to_decimal`) +```cpp +#include "dragonbox/dragonbox.h" +double x = 1.234; // Also works for float + +// Here, x should be a nonzero finite number! +// The return value v is a struct with three members: +// significand : decimal significand (1234 in this case); +// it is of type std::uint64_t for double, std::uint32_t for float +// exponent : decimal exponent (-3 in this case); it is of type int +// is_negative : as the name suggests; it is of type bool +auto v = jkj::dragonbox::to_decimal(x); +``` + +By default, `jkj::dragonbox::to_decimal` returns a struct with three members (`significand`, `exponent`, and `is_negative`). But the return type and the return value can change if you specify policy parameters. See [below](https://github.com/jk-jeon/dragonbox#policies). + +***Important.*** `jkj::dragonbox::to_decimal` is designed to ***work only with finite nonzero*** inputs. The behavior of it when given with infinities/NaN's/`+0`/`-0` is undefined. `jkj::dragonbox::to_chars` and `jkj::dragonbox::to_chars_n` work fine for any inputs. + +# To people wanting to port the algorithm +Those who want to port the algorithm into other languages or re-implement it from scratch are recommended to look at the [simpler implementation](https://github.com/jk-jeon/dragonbox/tree/master/subproject/simple) first rather than the main implementation, since the main implementation is riddled with template indirections obscuring the core logic of the algorithm. The simpler implementation offers less flexibility and somewhat slower performance, but is much more straightforward so it should be easier to understand. + +# Policies +Dragonbox provides several policies that the user can select. Most of the time the default policies will be sufficient, but for some situation this customizability might be useful. There are currently five different kinds of policies that you can specify: sign policy, trailing zero policy, decimal-to-binary (parsing) rounding policy, binary-to-decimal (formatting) rounding policy, and cache policy. Those policies live in the namespace `jkj::dragonbox::policy`. You can provide the policies as additional parameters to `jkj::dragonbox::to_decimal` or `jkj::dragonbox::to_chars` or `jkj::dragonbox::to_chars_n`. Here is an example usage: +```cpp +#include "dragonbox/dragonbox.h" +auto v = jkj::dragonbox::to_decimal(x, + jkj::dragonbox::policy::sign::ignore, + jkj::dragonbox::policy::cache::compact); +``` +In this example, the `ignore` sign policy and the `compact` cache policy are specified. The return value will not include the member `is_negative`, and `jkj::dragonbox::to_decimal` will internally use the compressed cache for the computation, rather than the full cache. There is no particular order for policy parameters; you can give them in any order. Default policies will be chosen if you do not explicitly specify any. In the above example, for instance, `nearest_to_even` decimal-to-binary rounding mode policy is chosen, which is the default decimal-to-binary rounding mode policy. If you provide two or more policies of the same kind, or if you provide an invalid policy parameter, then the compliation will fail. + +Policy parameters (e.g., `jkj::dragonbox::policy::sign::ignore` in the above example) are of different types, so different combinations of policies generally result in separate template instantiations, which might cause binary bloat. (However, it is only the combination that matters; giving the same parameter combination in a different order will usually not generate a separate binary.) + +## Sign policy +Determines whether or not `jkj::dragonbox::to_decimal` will extract and return the sign of the input parameter. + +- `jkj::dragonbox::policy::sign::ignore`: There is no `is_negative` member in the returned struct and the sign of the input is not returned. A string generation routine might anyway need to deal with the sign by itself, so often this member will not be needed. In that case, omitting `is_negative` member can reduce some overhead. `jkj::dragonbox::to_chars` and `jkj::dragonbox::to_chars_n` use this policy internally. In the implementation of `jkj::dragonbox::to_decimal`, the sign of the input is relevant only for deciding the rounding interval under certain rounding mode policies. Under the default rounding mode policies, the sign is completely ignored. +- `jkj::dragonbox::policy::sign::return_sign`: **This is the default policy.** The sign of the input will be written in the `is_negative` member of the returned struct. + +You cannot specify sign policy to `jkj::dragonbox::to_chars`/`jkj::dragonbox::to_chars_n`. + +## Trailing zero policy +Determines what `jkj::dragonbox::to_decimal` will do with possible trailing decimal zeros. + +- `jkj::dragonbox::policy::trailing_zero::ignore`: Do not care about trailing zeros; the output significand may contain trailing zeros. Since trailing zero removal is a relatively heavy operation involving lots of divisions, and a string generation routine will need to perform divisions anyway, it would be possible to get a better overall performance by omitting trailing zero removal from `jkj::dragonbox::to_decimal` and taking care of that in other places. +- `jkj::dragonbox::policy::trailing_zero::remove`: **This is the default policy.** Remove all trailing zeros in the output. `jkj::dragonbox::to_chars` and `jkj::dragonbox::to_chars_n` use this policy internally for IEEE-754 binary32 format (aka `float`). +- `jkj::dragonbox::policy::trailing_zero::report`: The output significand may contain trailing zeros, but such possibility will be reported in the additional member `may_have_trailing_zeros` of the returned struct. This member will be set to `true` if there might be trailing zeros, and it will be set to `false` if there should be no trailing zero. By how the algorithm works, it is guaranteed that whenever there might be trailing zeros, the maximum number of trailing zeros is 7 for binary32 and 15 for binary64. + +You cannot specify trailing zero policy to `jkj::dragonbox::to_chars`/`jkj::dragonbox::to_chars_n`. + +## Decimal-to-binary rounding policy +Dragonbox provides a roundtrip guarantee. This means that if we convert the output of Dragonbox back to IEEE-754 binary floating-point format, the result should be equal to the original input to Dragonbox. However, converting the decimal output of Dragonbox back into binary floating-point number requires a rounding, so in order to ensure the roundtrip guarantee, Dragonbox must assume which kind of rounding will be performed for *the inverse, decimal-to-binary conversion*. + +- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_to_even`: **This is the default policy.** Use *round-to-nearest, tie-to-even* rounding mode. +- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_to_odd`: Use *round-to-nearest, tie-to-odd* rounding mode. +- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_toward_plus_infinity`: Use *round-to-nearest, tie-toward-plus-infinity* rounding mode. +- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_toward_minus_infinity`: Use *round-to-nearest, tie-toward-minus-infinity* rounding mode. +- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_toward_zero`: Use *round-to-nearest, tie-toward-zero* rounding mode. This will produce the fastest code among all *round-to-nearest* rounding modes. +- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_away_from_zero`: Use *round-to-nearest, tie-away-from-zero* rounding mode. +- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_to_even_static_boundary`: Use *round-to-nearest, tie-to-even* rounding mode, but there will be completely independent code paths for even inputs and odd inputs. This will produce a bigger binary, but might run faster than `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_to_even` for some situation. +- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_to_odd_static_boundary`: Use *round-to-nearest, tie-to-odd* rounding mode, but there will be completely independent code paths for even inputs and odd inputs. This will produce a bigger binary, but might run faster than `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_to_odd` for some situation. +- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_toward_plus_infinity_static_boundary`: Use *round-to-nearest, tie-toward-plus-infinity* rounding mode, but there will be completely independent code paths for positive inputs and negative inputs. This will produce a bigger binary, but might run faster than `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_toward_plus_infinity` for some situation. +- `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_toward_minus_infinity_static_boundary`: Use *round-to-nearest, tie-toward-plus-infinity* rounding mode, but there will be completely independent code paths for positive inputs and negative inputs. This will produce a bigger binary, but might run faster than `jkj::dragonbox::policy::decimal_to_binary_rounding::nearest_toward_minus_infinity` for some situation. + +- `jkj::dragonbox::policy::decimal_to_binary_rounding::toward_plus_infinity`: Use *round-toward-plus-infinity* rounding mode. +- `jkj::dragonbox::policy::decimal_to_binary_rounding::toward_minus_infinity`: Use *round-toward-minus-infinity* rounding mode. +- `jkj::dragonbox::policy::decimal_to_binary_rounding::toward_zero`: Use *round-toward-zero* rounding mode. +- `jkj::dragonbox::policy::decimal_to_binary_rounding::away_from_zero`: Use *away-from-zero* rounding mode. + +All of these policies can be specified also to `jkj::dragonbox::to_chars`/`jkj::dragonbox::to_chars_n`. + +## Binary-to-decimal rounding policy +Determines what `jkj::dragonbox::to_decimal` will do when rounding tie occurs while obtaining the decimal significand. This policy will be completely ignored if the specified binary-to-decimal rounding policy is not one of the round-to-nearest policies (because for other policies rounding tie simply doesn't exist). + +- `jkj::dragonbox::policy::binary_to_decimal_rounding::do_not_care`: Do not care about correct rounding at all and just find any shortest output with the correct roundtrip. It will produce a faster code, but the performance difference will not be big. +- `jkj::dragonbox::policy::binary_to_decimal_rounding::to_even`: **This is the default policy.** Choose the even number when rounding tie occurs. +- `jkj::dragonbox::policy::binary_to_decimal_rounding::to_odd`: Choose the odd number when rounding tie occurs. +- `jkj::dragonbox::policy::binary_to_decimal_rounding::away_from_zero`: Choose the number with the bigger absolute value when rounding tie occurs. +- `jkj::dragonbox::policy::binary_to_decimal_rounding::toward_zero`: Choose the number with the smaller absolute value when rounding tie occurs. + +All of these policies can be specified also to `jkj::dragonbox::to_chars`/`jkj::dragonbox::to_chars_n`. + +## Cache policy +Choose between the full cache table and the compressed one. Using the compressed cache will result in about 20% slower code, but it can significantly reduce the amount of required static data. It currently has no effect for binary32 (`float`) inputs. For binary64 (`double`) inputs, `jkj::dragonbox::cache_policy::full` will cause `jkj::dragonbox::to_decimal` to use `619*16 = 9904` bytes of static data table, while the corresponding amount for `jkj::dragonbox::cache_policy::compact` is `23*16 + 27*8 = 584` bytes. + +- `jkj::dragonbox::policy::cache::full`: **This is the default policy.** Use the full table. +- `jkj::dragonbox::policy::cache::compact`: Use the compressed table. + +All of these policies can be specified also to `jkj::dragonbox::to_chars`/`jkj::dragonbox::to_chars_n`. + + +# Performance +In my machine (Intel Core i7-7700HQ 2.80GHz, Windows 10), it defeats or is on par with other contemporary algorithms including Grisu-Exact, Ryu, and Schubfach. + +The following benchmark result (performed on 03/30/2024) is obtained using Milo's dtoa benchmark framework ([https://github.com/miloyip/dtoa-benchmark](https://github.com/miloyip/dtoa-benchmark)). The complete source code for the benchmark below is available [here](https://github.com/jk-jeon/dtoa-benchmark). + +![corei7_7700hq@2.80_win64_vc2019_randomdigit_time](other_files/unknown_win64_vc2019_randomdigit_time.png) +![corei7_7700hq@2.80_win64_vc2019_randomdigit_time](other_files/unknown_win64_vc2019_randomdigit_timedigit.png) + +Note 1: `dragonbox` is the performance of Dragonbox with the full cache table, and `dragonbox_comp` is the performance of Dragonbox with the compact cache table. + +Note 2: [`fmt`](https://github.com/fmtlib/fmt) internally uses Dragonbox with an implementation almost identical to that in this repository. + +There is also a benchmark done by myself (also performed on 03/30/2024): + +(top: benchmark for ````float```` data, bottom: benchmark for ````double```` data; solid lines are the averages, dashed lines are the medians, and the shaded regions show 30%, 50%, and 70% percentiles): + +(Clang) +![digits_benchmark_binary32](subproject/benchmark/results/digits_benchmark_binary32_clang.png) +![digits_benchmark_binary64](subproject/benchmark/results/digits_benchmark_binary64_clang.png) + +(MSVC) +![digits_benchmark_binary32](subproject/benchmark/results/digits_benchmark_binary32_msvc.png) +![digits_benchmark_binary64](subproject/benchmark/results/digits_benchmark_binary64_msvc.png) + +Here is another performance plot with uniformly randomly generated ````float````(top) or ````double````(bottom) data: + +(Clang) +![uniform_benchmark_binary32](subproject/benchmark/results/uniform_benchmark_binary32_clang.png) +![uniform_benchmark_binary64](subproject/benchmark/results/uniform_benchmark_binary64_clang.png) + +(MSVC) +![uniform_benchmark_binary32](subproject/benchmark/results/uniform_benchmark_binary32_msvc.png) +![uniform_benchmark_binary64](subproject/benchmark/results/uniform_benchmark_binary64_msvc.png) + +(Note: the comparison with Schubfach is not completely fair, since the implementation I benchmarked against uses a digit generation procedure with a different set of constraints. More fair comparison is available in [this repository](https://github.com/abolz/Drachennest).) + +# Comprehensive Explanation of the Algorithm +Please see [this](other_files/Dragonbox.pdf) paper. + +# How to Run Tests, Benchmark, and Others +There are four subprojects contained in this repository: +1. [`common`](subproject/common): The subproject that other subprojects depend on. +2. [`benchmark`](subproject/benchmark): Runs benchmark. +3. [`test`](subproject/test): Runs tests. +4. [`meta`](subproject/meta): Generates static data that the main library uses. + +## Build each subproject independently +All subprojects including tests and benchmark are standalone, which means that you can build and run each of them independently. For example, you can do the following to run tests: +``` +git clone https://github.com/jk-jeon/dragonbox +cd dragonbox +mkdir -p build/subproject/test +cd build/subproject/test +cmake ../../../subproject/test +cmake --build . +ctest . +``` +(You might need to pass the configuration option to `cmake` and `ctest` if you use multi-configuration generators like Visual Studio.) + +## Build all subprojects from the root directory +It is also possible to build all subprojects from the root directory by passing the option `-DDRAGONBOX_ENABLE_SUBPROJECT=On` to `cmake`: +``` +git clone https://github.com/jk-jeon/dragonbox +cd dragonbox +mkdir build +cd build +cmake .. -DDRAGONBOX_ENABLE_SUBPROJECT=On +cmake --build . +``` + +## Notes on working directory +Some executable files require the correct working directory to be set. For example, the executable for [`benchmark`](subproject/benchmark) runs some MATLAB scripts provided in [`subproject/benchmark/matlab`](subproject/benchmark/matlab) directory, which will fail to execute if the working directory is not set to [`subproject/benchmark`](subproject/benchmark). If you use the provided `CMakeLists.txt` files to generate a Visual Studio solution, the debugger's working directory is automatically set to the corresponding source directory. For example, the working directory is set to [`subproject/benchmark`](subproject/benchmark) for the benchmark subproject. However, other generators of cmake are not able to set the debugger's working directory, so in that case you need to manually set the correct working directory when running the executables in order to make them work correctly. + + +# Notes + +## Correctness of the algorithm + +The [paper](other_files/Dragonbox.pdf) provides a mathematical proof of the correctness of the algorithm, with the aid of verification programs in [`test`](test) and [`meta`](meta) directories. In addition to that, I did a fair amount of uniformly random tests against Ryu (which is extremely heavily tested in its own), and I also ran a joint test of Dragonbox with a binary-to-decimal floating-point conversion routine I developed, and confirmed correct roundtrip for all possible IEEE-754 binary32-encoded floating-point numbers (aka `float`) with the round-to-nearest, tie-to-even rounding mode. Therefore, I am pretty confident about the correctness of both of the algorithms. + +## Precise meaning of roundtrip guarantee + +The precise meaning of roundtrip guarantee might be tricky, as it depends on the notion of "correct parsers". For example, given that `significand` and `exponent` are the outputs of Dragonbox with respect to an input floating-point number `x` of, say, type `double`, then things like `x == significand * pow(10.0, exponent)` might or might not be the case, because each of the floating-point operations in the expression `significand * pow(10.0, exponent)` can introduce rounding errors that can accumulate to a bigger error. What a correct parser should do is to precisely compute the floating-point number from the given expression according to the assumed rounding rule, and the result must be "correctly rounded" in the sense that only the minimum possible rounding error is allowed. Implementing a correct parser is indeed a very nontrivial job, so you may need additional libraries (like [Ryu](https://github.com/ulfjack/ryu) or [double-conversion](https://github.com/google/double-conversion)) if you want to check this roundtrip guarantee by yourself. + +# License +All code, except for those belong to third-party libraries (code in [`subproject/3rdparty`](subproject/3rdparty)), is licensed under either of + + * Apache License Version 2.0 with LLVM Exceptions ([LICENSE-Apache2-LLVM](LICENSE-Apache2-LLVM) or https://llvm.org/foundation/relicensing/LICENSE.txt) or + * Boost Software License Version 1.0 ([LICENSE-Boost](LICENSE-Boost) or https://www.boost.org/LICENSE_1_0.txt). + diff --git a/src/external/dragonbox/dragonbox.h b/src/external/dragonbox/dragonbox.h new file mode 100644 index 00000000..7cd8d194 --- /dev/null +++ b/src/external/dragonbox/dragonbox.h @@ -0,0 +1,4205 @@ +// Copyright 2020-2024 Junekey Jeon +// +// The contents of this file may be used under the terms of +// the Apache License v2.0 with LLVM Exceptions. +// +// (See accompanying file LICENSE-Apache or copy at +// https://llvm.org/foundation/relicensing/LICENSE.txt) +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE-Boost or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. + + +#ifndef JKJ_HEADER_DRAGONBOX +#define JKJ_HEADER_DRAGONBOX + +// Attribute for storing static data into a dedicated place, e.g. flash memory. Every ODR-used +// static data declaration will be decorated with this macro. The users may define this macro, +// before including the library headers, into whatever they want. +#ifndef JKJ_STATIC_DATA_SECTION + #define JKJ_STATIC_DATA_SECTION +#else + #define JKJ_STATIC_DATA_SECTION_DEFINED 1 +#endif + +// To use the library with toolchains without standard C++ headers, the users may define this macro +// into their custom namespace which contains the definitions of all the standard C++ library +// features used in this header. (The list can be found below.) +#ifndef JKJ_STD_REPLACEMENT_NAMESPACE + #define JKJ_STD_REPLACEMENT_NAMESPACE std + #include + #include + #include + #include + #include + + #ifdef __has_include + #if __has_include() + #include + #endif + #endif +#else + #define JKJ_STD_REPLACEMENT_NAMESPACE_DEFINED 1 +#endif + +//////////////////////////////////////////////////////////////////////////////////////// +// Language feature detections. +//////////////////////////////////////////////////////////////////////////////////////// + +// C++14 constexpr +#if defined(__cpp_constexpr) && __cpp_constexpr >= 201304L + #define JKJ_HAS_CONSTEXPR14 1 +#elif __cplusplus >= 201402L + #define JKJ_HAS_CONSTEXPR14 1 +#elif defined(_MSC_VER) && _MSC_VER >= 1910 && _MSVC_LANG >= 201402L + #define JKJ_HAS_CONSTEXPR14 1 +#else + #define JKJ_HAS_CONSTEXPR14 0 +#endif + +#if JKJ_HAS_CONSTEXPR14 + #define JKJ_CONSTEXPR14 constexpr +#else + #define JKJ_CONSTEXPR14 +#endif + +// C++17 constexpr lambdas +#if defined(__cpp_constexpr) && __cpp_constexpr >= 201603L + #define JKJ_HAS_CONSTEXPR17 1 +#elif __cplusplus >= 201703L + #define JKJ_HAS_CONSTEXPR17 1 +#elif defined(_MSC_VER) && _MSC_VER >= 1911 && _MSVC_LANG >= 201703L + #define JKJ_HAS_CONSTEXPR17 1 +#else + #define JKJ_HAS_CONSTEXPR17 0 +#endif + +// C++17 inline variables +#if defined(__cpp_inline_variables) && __cpp_inline_variables >= 201606L + #define JKJ_HAS_INLINE_VARIABLE 1 +#elif __cplusplus >= 201703L + #define JKJ_HAS_INLINE_VARIABLE 1 +#elif defined(_MSC_VER) && _MSC_VER >= 1912 && _MSVC_LANG >= 201703L + #define JKJ_HAS_INLINE_VARIABLE 1 +#else + #define JKJ_HAS_INLINE_VARIABLE 0 +#endif + +#if JKJ_HAS_INLINE_VARIABLE + #define JKJ_INLINE_VARIABLE inline constexpr +#else + #define JKJ_INLINE_VARIABLE static constexpr +#endif + +// C++17 if constexpr +#if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L + #define JKJ_HAS_IF_CONSTEXPR 1 +#elif __cplusplus >= 201703L + #define JKJ_HAS_IF_CONSTEXPR 1 +#elif defined(_MSC_VER) && _MSC_VER >= 1911 && _MSVC_LANG >= 201703L + #define JKJ_HAS_IF_CONSTEXPR 1 +#else + #define JKJ_HAS_IF_CONSTEXPR 0 +#endif + +#if JKJ_HAS_IF_CONSTEXPR + #define JKJ_IF_CONSTEXPR if constexpr +#else + #define JKJ_IF_CONSTEXPR if +#endif + +// C++20 std::bit_cast +#if JKJ_STD_REPLACEMENT_NAMESPACE_DEFINED + #if JKJ_STD_REPLACEMENT_HAS_BIT_CAST + #define JKJ_HAS_BIT_CAST 1 + #else + #define JKJ_HAS_BIT_CAST 0 + #endif +#elif defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L + #include + #define JKJ_HAS_BIT_CAST 1 +#else + #define JKJ_HAS_BIT_CAST 0 +#endif + +// C++23 if consteval or C++20 std::is_constant_evaluated +#if defined(__cpp_if_consteval) && __cpp_is_consteval >= 202106L + #define JKJ_IF_CONSTEVAL if consteval + #define JKJ_IF_NOT_CONSTEVAL if !consteval + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 1 + #define JKJ_USE_IS_CONSTANT_EVALUATED 0 +#elif JKJ_STD_REPLACEMENT_NAMESPACE_DEFINED + #if JKJ_STD_REPLACEMENT_HAS_IS_CONSTANT_EVALUATED + #define JKJ_IF_CONSTEVAL if (stdr::is_constant_evaluated()) + #define JKJ_IF_NOT_CONSTEVAL if (!stdr::is_constant_evaluated()) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 1 + #define JKJ_USE_IS_CONSTANT_EVALUATED 1 + #elif JKJ_HAS_IF_CONSTEXPR + #define JKJ_IF_CONSTEVAL if constexpr (false) + #define JKJ_IF_NOT_CONSTEVAL if constexpr (true) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 0 + #define JKJ_USE_IS_CONSTANT_EVALUATED 0 + #else + #define JKJ_IF_CONSTEVAL if (false) + #define JKJ_IF_NOT_CONSTEVAL if (true) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 0 + #define JKJ_USE_IS_CONSTANT_EVALUATED 0 + #endif +#else + #if defined(__cpp_lib_is_constant_evaluated) && __cpp_lib_is_constant_evaluated >= 201811L + #define JKJ_IF_CONSTEVAL if (stdr::is_constant_evaluated()) + #define JKJ_IF_NOT_CONSTEVAL if (!stdr::is_constant_evaluated()) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 1 + #define JKJ_USE_IS_CONSTANT_EVALUATED 1 + #elif JKJ_HAS_IF_CONSTEXPR + #define JKJ_IF_CONSTEVAL if constexpr (false) + #define JKJ_IF_NOT_CONSTEVAL if constexpr (true) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 0 + #define JKJ_USE_IS_CONSTANT_EVALUATED 0 + #else + #define JKJ_IF_CONSTEVAL if (false) + #define JKJ_IF_NOT_CONSTEVAL if (true) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 0 + #define JKJ_USE_IS_CONSTANT_EVALUATED 0 + #endif +#endif + +#if JKJ_CAN_BRANCH_ON_CONSTEVAL && JKJ_HAS_BIT_CAST + #define JKJ_CONSTEXPR20 constexpr +#else + #define JKJ_CONSTEXPR20 +#endif + +// Suppress additional buffer overrun check. +// I have no idea why MSVC thinks some functions here are vulnerable to the buffer overrun +// attacks. No, they aren't. +#if defined(__GNUC__) || defined(__clang__) + #define JKJ_SAFEBUFFERS + #define JKJ_FORCEINLINE inline __attribute__((always_inline)) +#elif defined(_MSC_VER) + #define JKJ_SAFEBUFFERS __declspec(safebuffers) + #define JKJ_FORCEINLINE __forceinline +#else + #define JKJ_SAFEBUFFERS + #define JKJ_FORCEINLINE inline +#endif + +#if defined(__has_builtin) + #define JKJ_HAS_BUILTIN(x) __has_builtin(x) +#else + #define JKJ_HAS_BUILTIN(x) false +#endif + +#if defined(_MSC_VER) + #include +#elif defined(__INTEL_COMPILER) + #include +#endif + +namespace jkj { + namespace dragonbox { + //////////////////////////////////////////////////////////////////////////////////////// + // The Compatibility layer for toolchains without standard C++ headers. + //////////////////////////////////////////////////////////////////////////////////////// + namespace detail { + namespace stdr { + // +#if JKJ_HAS_BIT_CAST + using JKJ_STD_REPLACEMENT_NAMESPACE::bit_cast; +#endif + + // + // We need assert() macro, but it is not namespaced anyway, so nothing to do here. + + // + using JKJ_STD_REPLACEMENT_NAMESPACE::int_least8_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::int_least16_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::int_least32_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::int_fast8_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::int_fast16_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::int_fast32_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_least8_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_least16_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_least32_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_least64_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_fast8_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_fast16_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_fast32_t; + // We need INT32_C, UINT32_C and UINT64_C macros too, but again there is nothing to do + // here. + + // + using JKJ_STD_REPLACEMENT_NAMESPACE::size_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::memcpy; + + // + template + using numeric_limits = JKJ_STD_REPLACEMENT_NAMESPACE::numeric_limits; + + // + template + using enable_if = JKJ_STD_REPLACEMENT_NAMESPACE::enable_if; + template + using add_rvalue_reference = JKJ_STD_REPLACEMENT_NAMESPACE::add_rvalue_reference; + template + using conditional = JKJ_STD_REPLACEMENT_NAMESPACE::conditional; +#if JKJ_USE_IS_CONSTANT_EVALUATED + using JKJ_STD_REPLACEMENT_NAMESPACE::is_constant_evaluated; +#endif + template + using is_same = JKJ_STD_REPLACEMENT_NAMESPACE::is_same; +#if !JKJ_HAS_BIT_CAST + template + using is_trivially_copyable = JKJ_STD_REPLACEMENT_NAMESPACE::is_trivially_copyable; +#endif + template + using is_integral = JKJ_STD_REPLACEMENT_NAMESPACE::is_integral; + template + using is_signed = JKJ_STD_REPLACEMENT_NAMESPACE::is_signed; + template + using is_unsigned = JKJ_STD_REPLACEMENT_NAMESPACE::is_unsigned; + } + } + + + //////////////////////////////////////////////////////////////////////////////////////// + // Some general utilities for C++11-compatibility. + //////////////////////////////////////////////////////////////////////////////////////// + namespace detail { +#if !JKJ_HAS_CONSTEXPR17 + template + struct index_sequence {}; + + template + struct make_index_sequence_impl { + using type = typename make_index_sequence_impl::type; + }; + + template + struct make_index_sequence_impl { + using type = index_sequence; + }; + + template + using make_index_sequence = typename make_index_sequence_impl<0, N, void>::type; +#endif + + // Available since C++11, but including just for this is an overkill. + template + typename stdr::add_rvalue_reference::type declval() noexcept; + + // Similarly, including is an overkill. + template + struct array { + T data_[N]; + constexpr T operator[](stdr::size_t idx) const noexcept { return data_[idx]; } + JKJ_CONSTEXPR14 T& operator[](stdr::size_t idx) noexcept { return data_[idx]; } + }; + } + + + //////////////////////////////////////////////////////////////////////////////////////// + // Some basic features for encoding/decoding IEEE-754 formats. + //////////////////////////////////////////////////////////////////////////////////////// + namespace detail { + template + struct physical_bits { + static constexpr stdr::size_t value = + sizeof(T) * stdr::numeric_limits::digits; + }; + template + struct value_bits { + static constexpr stdr::size_t value = stdr::numeric_limits< + typename stdr::enable_if::value, T>::type>::digits; + }; + + template + JKJ_CONSTEXPR20 To bit_cast(const From& from) { +#if JKJ_HAS_BIT_CAST + return stdr::bit_cast(from); +#else + static_assert(sizeof(From) == sizeof(To), ""); + static_assert(stdr::is_trivially_copyable::value, ""); + static_assert(stdr::is_trivially_copyable::value, ""); + To to; + stdr::memcpy(&to, &from, sizeof(To)); + return to; +#endif + } + } + + // These classes expose encoding specs of IEEE-754-like floating-point formats. + // Currently available formats are IEEE-754 binary32 & IEEE-754 binary64. + + struct ieee754_binary32 { + static constexpr int total_bits = 32; + static constexpr int significand_bits = 23; + static constexpr int exponent_bits = 8; + static constexpr int min_exponent = -126; + static constexpr int max_exponent = 127; + static constexpr int exponent_bias = -127; + static constexpr int decimal_significand_digits = 9; + static constexpr int decimal_exponent_digits = 2; + }; + struct ieee754_binary64 { + static constexpr int total_bits = 64; + static constexpr int significand_bits = 52; + static constexpr int exponent_bits = 11; + static constexpr int min_exponent = -1022; + static constexpr int max_exponent = 1023; + static constexpr int exponent_bias = -1023; + static constexpr int decimal_significand_digits = 17; + static constexpr int decimal_exponent_digits = 3; + }; + + // A floating-point format traits class defines ways to interpret a bit pattern of given size as + // an encoding of floating-point number. This is an implementation of such a traits class, + // supporting ways to interpret IEEE-754 binary floating-point numbers. + template + struct ieee754_binary_traits { + // CarrierUInt needs to have enough size to hold the entire contents of floating-point + // numbers. The actual bits are assumed to be aligned to the LSB, and every other bits are + // assumed to be zeroed. + static_assert(detail::value_bits::value >= Format::total_bits, + "jkj::dragonbox: insufficient number of bits"); + static_assert(detail::stdr::is_unsigned::value, ""); + + // ExponentUInt needs to be large enough to hold (unsigned) exponent bits as well as the + // (signed) actual exponent. + // TODO: static overflow guard against intermediate computations. + static_assert(detail::value_bits::value >= Format::exponent_bits + 1, + "jkj::dragonbox: insufficient number of bits"); + static_assert(detail::stdr::is_signed::value, ""); + + using format = Format; + using carrier_uint = CarrierUInt; + static constexpr int carrier_bits = int(detail::value_bits::value); + using exponent_int = ExponentInt; + + // Extract exponent bits from a bit pattern. + // The result must be aligned to the LSB so that there is no additional zero paddings + // on the right. This function does not do bias adjustment. + static constexpr exponent_int extract_exponent_bits(carrier_uint u) noexcept { + return exponent_int((u >> format::significand_bits) & + ((exponent_int(1) << format::exponent_bits) - 1)); + } + + // Extract significand bits from a bit pattern. + // The result must be aligned to the LSB so that there is no additional zero paddings + // on the right. The result does not contain the implicit bit. + static constexpr carrier_uint extract_significand_bits(carrier_uint u) noexcept { + return carrier_uint(u & ((carrier_uint(1) << format::significand_bits) - 1u)); + } + + // Remove the exponent bits and extract significand bits together with the sign bit. + static constexpr carrier_uint remove_exponent_bits(carrier_uint u) noexcept { + return carrier_uint(u & ~(((carrier_uint(1) << format::exponent_bits) - 1u) + << format::significand_bits)); + } + + // Shift the obtained signed significand bits to the left by 1 to remove the sign bit. + static constexpr carrier_uint remove_sign_bit_and_shift(carrier_uint u) noexcept { + return carrier_uint((carrier_uint(u) << 1) & + ((((carrier_uint(1) << (Format::total_bits - 1)) - 1u) << 1) | 1u)); + } + + // Obtain the actual value of the binary exponent from the extracted exponent bits. + static constexpr exponent_int binary_exponent(exponent_int exponent_bits) noexcept { + return exponent_int(exponent_bits == 0 ? format::min_exponent + : exponent_bits + format::exponent_bias); + } + + // Obtain the actual value of the binary significand from the extracted significand bits + // and exponent bits. + static constexpr carrier_uint binary_significand(carrier_uint significand_bits, + exponent_int exponent_bits) noexcept { + return carrier_uint( + exponent_bits == 0 + ? significand_bits + : (significand_bits | (carrier_uint(1) << format::significand_bits))); + } + + /* Various boolean observer functions */ + + static constexpr bool is_nonzero(carrier_uint u) noexcept { + return (u & ((carrier_uint(1) << (format::significand_bits + format::exponent_bits)) - + 1u)) != 0; + } + static constexpr bool is_positive(carrier_uint u) noexcept { + return u < (carrier_uint(1) << (format::significand_bits + format::exponent_bits)); + } + static constexpr bool is_negative(carrier_uint u) noexcept { return !is_positive(u); } + static constexpr bool is_finite(exponent_int exponent_bits) noexcept { + return exponent_bits != ((exponent_int(1) << format::exponent_bits) - 1); + } + static constexpr bool has_all_zero_significand_bits(carrier_uint u) noexcept { + return ((u << 1) & + ((((carrier_uint(1) << (Format::total_bits - 1)) - 1u) << 1) | 1u)) == 0; + } + static constexpr bool has_even_significand_bits(carrier_uint u) noexcept { + return u % 2 == 0; + } + }; + + // Convert between bit patterns stored in carrier_uint and instances of an actual + // floating-point type. Depending on format and carrier_uint, this operation might not + // be possible for some specific bit patterns. However, the contract is that u always + // denotes a valid bit pattern, so the functions here are assumed to be noexcept. + // Users might specialize this class to change the behavior for certain types. + // The default provided by the library is to treat the given floating-point type Float as either + // IEEE-754 binary32 or IEEE-754 binary64, depending on the bitwise size of Float. + template + struct default_float_bit_carrier_conversion_traits { + // Guards against types that have different internal representations than IEEE-754 + // binary32/64. I don't know if there is a truly reliable way of detecting IEEE-754 binary + // formats. I just did my best here. Note that in some cases + // numeric_limits::is_iec559 may report false even if the internal representation is + // IEEE-754 compatible. In such a case, the user can specialize this traits template and + // remove this static sanity check in order to make Dragonbox work for Float. + static_assert(detail::stdr::numeric_limits::is_iec559 && + detail::stdr::numeric_limits::radix == 2 && + (detail::physical_bits::value == 32 || + detail::physical_bits::value == 64), + "jkj::dragonbox: Float may not be of IEEE-754 binary32/binary64"); + + // Specifies the unsigned integer type to hold bitwise value of Float. + using carrier_uint = + typename detail::stdr::conditional::value == 32, + detail::stdr::uint_least32_t, + detail::stdr::uint_least64_t>::type; + + // Specifies the floating-point format. + using format = typename detail::stdr::conditional::value == 32, + ieee754_binary32, ieee754_binary64>::type; + + // Converts the floating-point type into the bit-carrier unsigned integer type. + static JKJ_CONSTEXPR20 carrier_uint float_to_carrier(Float x) noexcept { + return detail::bit_cast(x); + } + + // Converts the bit-carrier unsigned integer type into the floating-point type. + static JKJ_CONSTEXPR20 Float carrier_to_float(carrier_uint x) noexcept { + return detail::bit_cast(x); + } + }; + + // Convenient wrappers for floating-point traits classes. + // In order to reduce the argument passing overhead, these classes should be as simple as + // possible (e.g., no inheritance, no private non-static data member, etc.; this is an + // unfortunate fact about common ABI convention). + + template + struct signed_significand_bits { + using format_traits = FormatTraits; + using carrier_uint = typename format_traits::carrier_uint; + + carrier_uint u; + + signed_significand_bits() = default; + constexpr explicit signed_significand_bits(carrier_uint bit_pattern) noexcept + : u{bit_pattern} {} + + // Shift the obtained signed significand bits to the left by 1 to remove the sign bit. + constexpr carrier_uint remove_sign_bit_and_shift() const noexcept { + return format_traits::remove_sign_bit_and_shift(u); + } + + constexpr bool is_positive() const noexcept { return format_traits::is_positive(u); } + constexpr bool is_negative() const noexcept { return format_traits::is_negative(u); } + constexpr bool has_all_zero_significand_bits() const noexcept { + return format_traits::has_all_zero_significand_bits(u); + } + constexpr bool has_even_significand_bits() const noexcept { + return format_traits::has_even_significand_bits(u); + } + }; + + template + struct float_bits { + using format_traits = FormatTraits; + using carrier_uint = typename format_traits::carrier_uint; + using exponent_int = typename format_traits::exponent_int; + + carrier_uint u; + + float_bits() = default; + constexpr explicit float_bits(carrier_uint bit_pattern) noexcept : u{bit_pattern} {} + + // Extract exponent bits from a bit pattern. + // The result must be aligned to the LSB so that there is no additional zero paddings + // on the right. This function does not do bias adjustment. + constexpr exponent_int extract_exponent_bits() const noexcept { + return format_traits::extract_exponent_bits(u); + } + + // Extract significand bits from a bit pattern. + // The result must be aligned to the LSB so that there is no additional zero paddings + // on the right. The result does not contain the implicit bit. + constexpr carrier_uint extract_significand_bits() const noexcept { + return format_traits::extract_significand_bits(u); + } + + // Remove the exponent bits and extract significand bits together with the sign bit. + constexpr signed_significand_bits remove_exponent_bits() const noexcept { + return signed_significand_bits(format_traits::remove_exponent_bits(u)); + } + + // Obtain the actual value of the binary exponent from the extracted exponent bits. + static constexpr exponent_int binary_exponent(exponent_int exponent_bits) noexcept { + return format_traits::binary_exponent(exponent_bits); + } + constexpr exponent_int binary_exponent() const noexcept { + return binary_exponent(extract_exponent_bits()); + } + + // Obtain the actual value of the binary exponent from the extracted significand bits + // and exponent bits. + static constexpr carrier_uint binary_significand(carrier_uint significand_bits, + exponent_int exponent_bits) noexcept { + return format_traits::binary_significand(significand_bits, exponent_bits); + } + constexpr carrier_uint binary_significand() const noexcept { + return binary_significand(extract_significand_bits(), extract_exponent_bits()); + } + + constexpr bool is_nonzero() const noexcept { return format_traits::is_nonzero(u); } + constexpr bool is_positive() const noexcept { return format_traits::is_positive(u); } + constexpr bool is_negative() const noexcept { return format_traits::is_negative(u); } + constexpr bool is_finite(exponent_int exponent_bits) const noexcept { + return format_traits::is_finite(exponent_bits); + } + constexpr bool is_finite() const noexcept { + return format_traits::is_finite(extract_exponent_bits()); + } + constexpr bool has_even_significand_bits() const noexcept { + return format_traits::has_even_significand_bits(u); + } + }; + + template , + class FormatTraits = ieee754_binary_traits> + JKJ_CONSTEXPR20 float_bits make_float_bits(Float x) noexcept { + return float_bits(ConversionTraits::float_to_carrier(x)); + } + + namespace detail { + //////////////////////////////////////////////////////////////////////////////////////// + // Bit operation intrinsics. + //////////////////////////////////////////////////////////////////////////////////////// + + namespace bits { + // Most compilers should be able to optimize this into the ROR instruction. + // n is assumed to be at most of bit_width bits. + template + JKJ_CONSTEXPR14 UInt rotr(UInt n, unsigned int r) noexcept { + static_assert(bit_width > 0, "jkj::dragonbox: rotation bit-width must be positive"); + static_assert(bit_width <= value_bits::value, + "jkj::dragonbox: rotation bit-width is too large"); + r &= (bit_width - 1); + return (n >> r) | (n << ((bit_width - r) & (bit_width - 1))); + } + } + + //////////////////////////////////////////////////////////////////////////////////////// + // Utilities for wide unsigned integer arithmetic. + //////////////////////////////////////////////////////////////////////////////////////// + + namespace wuint { + // Compilers might support built-in 128-bit integer types. However, it seems that + // emulating them with a pair of 64-bit integers actually produces a better code, + // so we avoid using those built-ins. That said, they are still useful for + // implementing 64-bit x 64-bit -> 128-bit multiplication. + + // clang-format off +#if defined(__SIZEOF_INT128__) + // To silence "error: ISO C++ does not support '__int128' for 'type name' + // [-Wpedantic]" +#if defined(__GNUC__) + __extension__ +#endif + using builtin_uint128_t = unsigned __int128; +#endif + // clang-format on + + struct uint128 { + uint128() = default; + + stdr::uint_least64_t high_; + stdr::uint_least64_t low_; + + constexpr uint128(stdr::uint_least64_t high, stdr::uint_least64_t low) noexcept + : high_{high}, low_{low} {} + + constexpr stdr::uint_least64_t high() const noexcept { return high_; } + constexpr stdr::uint_least64_t low() const noexcept { return low_; } + + JKJ_CONSTEXPR20 uint128& operator+=(stdr::uint_least64_t n) & noexcept { + auto const generic_impl = [&] { + auto const sum = (low_ + n) & UINT64_C(0xffffffffffffffff); + high_ += (sum < low_ ? 1 : 0); + low_ = sum; + }; + // To suppress warning. + static_cast(generic_impl); + + JKJ_IF_CONSTEXPR(value_bits::value > 64) { + generic_impl(); + return *this; + } + + JKJ_IF_CONSTEVAL { + generic_impl(); + return *this; + } + + // See https://github.com/fmtlib/fmt/pull/2985. +#if JKJ_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__) + JKJ_IF_CONSTEXPR( + stdr::is_same::value) { + unsigned long long carry{}; + low_ = stdr::uint_least64_t(__builtin_addcll(low_, n, 0, &carry)); + high_ = stdr::uint_least64_t(__builtin_addcll(high_, 0, carry, &carry)); + return *this; + } +#endif +#if JKJ_HAS_BUILTIN(__builtin_addcl) && !defined(__ibmxl__) + JKJ_IF_CONSTEXPR(stdr::is_same::value) { + unsigned long carry{}; + low_ = stdr::uint_least64_t( + __builtin_addcl(static_cast(low_), + static_cast(n), 0, &carry)); + high_ = stdr::uint_least64_t( + __builtin_addcl(static_cast(high_), 0, carry, &carry)); + return *this; + } +#endif +#if JKJ_HAS_BUILTIN(__builtin_addc) && !defined(__ibmxl__) + JKJ_IF_CONSTEXPR(stdr::is_same::value) { + unsigned int carry{}; + low_ = stdr::uint_least64_t(__builtin_addc(static_cast(low_), + static_cast(n), 0, + &carry)); + high_ = stdr::uint_least64_t( + __builtin_addc(static_cast(high_), 0, carry, &carry)); + return *this; + } +#endif + +#if JKJ_HAS_BUILTIN(__builtin_ia32_addcarry_u64) + // __builtin_ia32_addcarry_u64 is not documented, but it seems it takes unsigned + // long long arguments. + unsigned long long result{}; + auto const carry = __builtin_ia32_addcarry_u64(0, low_, n, &result); + low_ = stdr::uint_least64_t(result); + __builtin_ia32_addcarry_u64(carry, high_, 0, &result); + high_ = stdr::uint_least64_t(result); +#elif defined(_MSC_VER) && defined(_M_X64) + // On MSVC, uint_least64_t and __int64 must be unsigned long long; see + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/standard-types + // and https://learn.microsoft.com/en-us/cpp/cpp/int8-int16-int32-int64. + static_assert(stdr::is_same::value, + ""); + auto const carry = _addcarry_u64(0, low_, n, &low_); + _addcarry_u64(carry, high_, 0, &high_); +#elif defined(__INTEL_COMPILER) && (defined(_M_X64) || defined(__x86_64)) + // Cannot find any documentation on how things are defined, but hopefully this + // is always true... + static_assert(stdr::is_same::value, ""); + auto const carry = _addcarry_u64(0, low_, n, &low_); + _addcarry_u64(carry, high_, 0, &high_); +#else + generic_impl(); +#endif + return *this; + } + }; + + inline JKJ_CONSTEXPR20 stdr::uint_least64_t umul64(stdr::uint_least32_t x, + stdr::uint_least32_t y) noexcept { +#if defined(_MSC_VER) && defined(_M_IX86) + JKJ_IF_NOT_CONSTEVAL { return __emulu(x, y); } +#endif + return x * stdr::uint_least64_t(y); + } + + // Get 128-bit result of multiplication of two 64-bit unsigned integers. + JKJ_SAFEBUFFERS inline JKJ_CONSTEXPR20 uint128 + umul128(stdr::uint_least64_t x, stdr::uint_least64_t y) noexcept { + auto const generic_impl = [=]() -> uint128 { + auto const a = stdr::uint_least32_t(x >> 32); + auto const b = stdr::uint_least32_t(x); + auto const c = stdr::uint_least32_t(y >> 32); + auto const d = stdr::uint_least32_t(y); + + auto const ac = umul64(a, c); + auto const bc = umul64(b, c); + auto const ad = umul64(a, d); + auto const bd = umul64(b, d); + + auto const intermediate = + (bd >> 32) + stdr::uint_least32_t(ad) + stdr::uint_least32_t(bc); + + return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), + (intermediate << 32) + stdr::uint_least32_t(bd)}; + }; + // To silence warning. + static_cast(generic_impl); + +#if defined(__SIZEOF_INT128__) + auto const result = builtin_uint128_t(x) * builtin_uint128_t(y); + return {stdr::uint_least64_t(result >> 64), stdr::uint_least64_t(result)}; +#elif defined(_MSC_VER) && defined(_M_X64) + JKJ_IF_CONSTEVAL { + // This redundant variable is to workaround MSVC's codegen bug caused by the + // interaction of NRVO and intrinsics. + auto const result = generic_impl(); + return result; + } + uint128 result; + #if defined(__AVX2__) + result.low_ = _mulx_u64(x, y, &result.high_); + #else + result.low_ = _umul128(x, y, &result.high_); + #endif + return result; +#else + return generic_impl(); +#endif + } + + // Get high half of the 128-bit result of multiplication of two 64-bit unsigned + // integers. + JKJ_SAFEBUFFERS inline JKJ_CONSTEXPR20 stdr::uint_least64_t + umul128_upper64(stdr::uint_least64_t x, stdr::uint_least64_t y) noexcept { + auto const generic_impl = [=]() -> stdr::uint_least64_t { + auto const a = stdr::uint_least32_t(x >> 32); + auto const b = stdr::uint_least32_t(x); + auto const c = stdr::uint_least32_t(y >> 32); + auto const d = stdr::uint_least32_t(y); + + auto const ac = umul64(a, c); + auto const bc = umul64(b, c); + auto const ad = umul64(a, d); + auto const bd = umul64(b, d); + + auto const intermediate = + (bd >> 32) + stdr::uint_least32_t(ad) + stdr::uint_least32_t(bc); + + return ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32); + }; + // To silence warning. + static_cast(generic_impl); + +#if defined(__SIZEOF_INT128__) + auto const result = builtin_uint128_t(x) * builtin_uint128_t(y); + return stdr::uint_least64_t(result >> 64); +#elif defined(_MSC_VER) && defined(_M_X64) + JKJ_IF_CONSTEVAL { + // This redundant variable is to workaround MSVC's codegen bug caused by the + // interaction of NRVO and intrinsics. + auto const result = generic_impl(); + return result; + } + stdr::uint_least64_t result; + #if defined(__AVX2__) + _mulx_u64(x, y, &result); + #else + result = __umulh(x, y); + #endif + return result; +#else + return generic_impl(); +#endif + } + + // Get upper 128-bits of multiplication of a 64-bit unsigned integer and a 128-bit + // unsigned integer. + JKJ_SAFEBUFFERS inline JKJ_CONSTEXPR20 uint128 umul192_upper128(stdr::uint_least64_t x, + uint128 y) noexcept { + auto r = umul128(x, y.high()); + r += umul128_upper64(x, y.low()); + return r; + } + + // Get upper 64-bits of multiplication of a 32-bit unsigned integer and a 64-bit + // unsigned integer. + inline JKJ_CONSTEXPR20 stdr::uint_least64_t + umul96_upper64(stdr::uint_least32_t x, stdr::uint_least64_t y) noexcept { +#if defined(__SIZEOF_INT128__) || (defined(_MSC_VER) && defined(_M_X64)) + return umul128_upper64(stdr::uint_least64_t(x) << 32, y); +#else + auto const yh = stdr::uint_least32_t(y >> 32); + auto const yl = stdr::uint_least32_t(y); + + auto const xyh = umul64(x, yh); + auto const xyl = umul64(x, yl); + + return xyh + (xyl >> 32); +#endif + } + + // Get lower 128-bits of multiplication of a 64-bit unsigned integer and a 128-bit + // unsigned integer. + JKJ_SAFEBUFFERS inline JKJ_CONSTEXPR20 uint128 umul192_lower128(stdr::uint_least64_t x, + uint128 y) noexcept { + auto const high = x * y.high(); + auto const high_low = umul128(x, y.low()); + return {(high + high_low.high()) & UINT64_C(0xffffffffffffffff), high_low.low()}; + } + + // Get lower 64-bits of multiplication of a 32-bit unsigned integer and a 64-bit + // unsigned integer. + constexpr stdr::uint_least64_t umul96_lower64(stdr::uint_least32_t x, + stdr::uint_least64_t y) noexcept { + return (x * y) & UINT64_C(0xffffffffffffffff); + } + } + + //////////////////////////////////////////////////////////////////////////////////////// + // Some simple utilities for constexpr computation. + //////////////////////////////////////////////////////////////////////////////////////// + + template + constexpr Int compute_power(Int a) noexcept { + static_assert(k >= 0, ""); +#if JKJ_HAS_CONSTEXPR14 + Int p = 1; + for (int i = 0; i < k; ++i) { + p *= a; + } + return p; +#else + return k == 0 ? 1 + : k % 2 == 0 ? compute_power(a * a) + : a * compute_power(a * a); +#endif + } + + template + constexpr int count_factors(UInt n) noexcept { + static_assert(a > 1, ""); +#if JKJ_HAS_CONSTEXPR14 + int c = 0; + while (n % a == 0) { + n /= a; + ++c; + } + return c; +#else + return n % a == 0 ? count_factors(n / a) + 1 : 0; +#endif + } + + //////////////////////////////////////////////////////////////////////////////////////// + // Utilities for fast/constexpr log computation. + //////////////////////////////////////////////////////////////////////////////////////// + + namespace log { + static_assert((stdr::int_fast32_t(-1) >> 1) == stdr::int_fast32_t(-1) && + (stdr::int_fast16_t(-1) >> 1) == stdr::int_fast16_t(-1), + "jkj::dragonbox: right-shift for signed integers must be arithmetic"); + + // For constexpr computation. + // Returns -1 when n = 0. + template + constexpr int floor_log2(UInt n) noexcept { +#if JKJ_HAS_CONSTEXPR14 + int count = -1; + while (n != 0) { + ++count; + n >>= 1; + } + return count; +#else + return n == 0 ? -1 : floor_log2(n / 2) + 1; +#endif + } + + template