9#include "Stroika/Foundation/Containers/Common.h"
10#include "Stroika/Foundation/Math/Common.h"
11#include "Stroika/Foundation/Memory/Common.h"
26 constexpr Precision::Precision (FullFlag)
30 template <
floating_po
int T>
31 constexpr unsigned int Precision::GetEffectivePrecision ()
const
35 return fPrecision_.value_or (numeric_limits<T>::max_digits10);
44 constexpr inline Precision Precision::kFull{Precision::FullFlag::eFull};
51 inline ToStringOptions::ToStringOptions (
const locale& l)
60 : fFmtFlags_{fmtFlags}
64 : fPrecision_{precision}
68 : fFloatFormat_{scientificNotation}
78 Memory::CopyToIf (&fPrecision_, b2.fPrecision_);
79 Memory::CopyToIf (&fFmtFlags_, b2.fFmtFlags_);
80 fUseCurrentLocale_ = b2.fUseCurrentLocale_;
81 Memory::CopyToIf (&fUseLocale_, b2.fUseLocale_);
82 Memory::CopyToIf (&fTrimTrailingZeros_, b2.fTrimTrailingZeros_);
83 Memory::CopyToIf (&fFloatFormat_, b2.fFloatFormat_);
85 template <
typename... ARGS>
87 : ToStringOptions{ToStringOptions{b1, b2}, forward<ARGS> (args)...}
90 inline optional<Precision> ToStringOptions::GetPrecision ()
const
94 inline optional<bool> ToStringOptions::GetTrimTrailingZeros ()
const
96 return fTrimTrailingZeros_;
100 if (fUseCurrentLocale_) {
106 static const locale kCLocale_ = locale::classic ();
111 if (not fUseLocale_ and not fUseCurrentLocale_) {
116 inline optional<FloatFormatType> ToStringOptions::GetFloatFormat ()
const
118 return fFloatFormat_;
120 inline optional<ios_base::fmtflags> ToStringOptions::GetIOSFmtFlags ()
const
126 inline void TrimTrailingZeros_ (String* strResult)
132 bool hasE = strResult->Find (
'e', eCaseInsensitive).has_value ();
135 size_t pastDot = strResult->find (
'.');
138 size_t len = strResult->length ();
139 size_t pPastLastZero = len;
140 for (; (pPastLastZero - 1) > pastDot; --pPastLastZero) {
141 if ((*strResult)[pPastLastZero - 1] !=
'0') {
145 if (len != pPastLastZero) [[unlikely]] {
146 *strResult = strResult->SubString (0, pPastLastZero);
155 template <
typename T>
156 T CStr2FloatType_ (
const wchar_t* s,
wchar_t** e);
157 template <
typename T>
158 T CStr2FloatType_ (
const char* s,
char** e);
160 inline float CStr2FloatType_ (
const wchar_t* s,
wchar_t** e)
162 return ::wcstof (s, e);
165 inline double CStr2FloatType_ (
const wchar_t* s,
wchar_t** e)
167 return wcstod (s, e);
170 inline long double CStr2FloatType_ (
const wchar_t* s,
wchar_t** e)
172 return wcstold (s, e);
175 inline float CStr2FloatType_ (
const char* s,
char** e)
177 return ::strtof (s, e);
180 inline double CStr2FloatType_ (
const char* s,
char** e)
182 return strtod (s, e);
185 inline long double CStr2FloatType_ (
const char* s,
char** e)
187 return strtold (s, e);
192 template <
typename RETURN_TYPE>
193 RETURN_TYPE ToFloat_ViaStrToD_ (
const char* start,
const char* end,
const char** remainder)
195 Require (start <= end);
197 if (remainder !=
nullptr) {
200 return Math::nan<RETURN_TYPE> ();
204 const char* cst = start;
206 if (remainder ==
nullptr) {
208 if (::isspace (*cst)) {
209 return Math::nan<RETURN_TYPE> ();
213 Memory::StackBuffer<char> tmp;
216 size_t len = end - start;
217 tmp.GrowToSize (len + 1);
218 (void)::memcpy (tmp.begin (), start, len);
222 RETURN_TYPE d = CStr2FloatType_<RETURN_TYPE> (cst, &e);
225 if (remainder ==
nullptr) {
227 d = Math::nan<RETURN_TYPE> ();
231 *remainder = e - cst + start;
236 template <
typename RETURN_TYPE>
237 RETURN_TYPE ToFloat_ViaStrToD_ (
const wchar_t* start,
const wchar_t* end,
const wchar_t** remainder)
239 Require (start <= end);
241 if (remainder !=
nullptr) {
244 return Math::nan<RETURN_TYPE> ();
247 wchar_t* e =
nullptr;
248 const wchar_t* cst = start;
250 if (remainder ==
nullptr) {
252 if (::iswspace (*cst)) {
253 return Math::nan<RETURN_TYPE> ();
257 Memory::StackBuffer<wchar_t> tmp;
260 size_t len = end - start;
261 tmp.GrowToSize (len + 1);
262 DISABLE_COMPILER_GCC_WARNING_START (
"GCC diagnostic ignored \"-Wrestrict\"");
263 DISABLE_COMPILER_GCC_WARNING_START (
"GCC diagnostic ignored \"-Wstringop-overflow=\"");
264 DISABLE_COMPILER_GCC_WARNING_START (
"GCC diagnostic ignored \"-Wstringop-overflow\"");
265 (void)::memcpy (tmp.begin (), start, len *
sizeof (wchar_t));
266 DISABLE_COMPILER_GCC_WARNING_END (
"GCC diagnostic ignored \"-Wstringop-overflow\"");
267 DISABLE_COMPILER_GCC_WARNING_END (
"GCC diagnostic ignored \"-Wstringop-overflow=\"");
268 DISABLE_COMPILER_GCC_WARNING_END (
"GCC diagnostic ignored \"-Wrestrict\"");
272 RETURN_TYPE d = CStr2FloatType_<RETURN_TYPE> (cst, &e);
275 if (remainder ==
nullptr) {
277 d = Math::nan<RETURN_TYPE> ();
281 *remainder = e - cst + start;
289#if qStroika_Foundation_Debug_AssertionsChecked || !(__cpp_lib_to_chars >= 201611)
290 inline size_t CalcPrecision_ (
const String& numStr,
bool countZerosAtEndAfterDecPoint =
false)
293 bool ignoreRest =
false;
294 bool seenDot =
false;
296 size_t nTrailingZeros{};
297 numStr.Apply ([&] (Character c) {
304 if (c ==
'+' or c ==
'-' or c ==
'.') {
307 if (leading and c ==
'0') {
315 if (countZerosAtEndAfterDecPoint and seenDot) {
325 return countZerosAtEndAfterDecPoint ? n : n - nTrailingZeros;
328 template <
typename FLOAT_TYPE>
329 String ToString_OptimizedForCLocaleAndNoStreamFlags_ (FLOAT_TYPE f, Precision precision)
331 using namespace Memory;
332 size_t sz = numeric_limits<FLOAT_TYPE>::max_digits10 + numeric_limits<FLOAT_TYPE>::max_exponent10 + 5;
333 StackBuffer<char> buf{Memory::eUninitialized, sz};
334 ptrdiff_t resultStrLen;
335 unsigned int effectivePrecision = precision.GetEffectivePrecision<FLOAT_TYPE> ();
338#if __cpp_lib_to_chars >= 201611
341 resultStrLen = to_chars (buf.begin (), buf.end (), f, chars_format::general).ptr - buf.
begin ();
344 resultStrLen = to_chars (buf.begin (), buf.end (), f, chars_format::general, effectivePrecision).ptr - buf.begin ();
347 auto mkFmtWithPrecisionArg_ = [] (
char* formatBufferStart, [[maybe_unused]]
char* formatBufferEnd,
char _Spec,
bool forceScientific) ->
char* {
348 char* fmtPtr = formatBufferStart;
356 *fmtPtr++ = forceScientific ?
'e' :
'g';
358 Require (fmtPtr < formatBufferEnd);
359 return formatBufferStart;
362 FLOAT_TYPE useRoundedFloat = Math::Round<FLOAT_TYPE> (f, effectivePrecision);
367 bool forceScientific = fabs (f) >= std::pow (10, effectivePrecision);
369 resultStrLen = ::snprintf (buf.data (), buf.size (),
370 mkFmtWithPrecisionArg_ (std::begin (format), std::end (format),
371 same_as<FLOAT_TYPE, long double> ?
'L' :
'\0', forceScientific),
372 (int)effectivePrecision + 1, f);
373 auto actualPrecIncZeros = CalcPrecision_ (String{span{buf.data (),
static_cast<size_t> (resultStrLen)}},
true);
374 if (actualPrecIncZeros > effectivePrecision) {
375 ptrdiff_t nBytes =
static_cast<ptrdiff_t
> (actualPrecIncZeros) -
static_cast<ptrdiff_t
> (effectivePrecision);
376 auto numberEnd = buf.data () + resultStrLen;
377 auto ePtr = std::find (buf.data (), numberEnd,
'e');
378 if (ePtr != numberEnd) {
379 Assert (buf.data () <= ePtr - nBytes);
380 memmove (ePtr - nBytes, ePtr, (buf.data () + resultStrLen - ePtr));
382 resultStrLen -= nBytes;
388 Verify (resultStrLen > 0 and resultStrLen <
static_cast<int> (sz));
389#if qStroika_Foundation_Debug_AssertionsChecked
390 Assert (precision ==
Precision::kFull or CalcPrecision_ (String{span{buf.data (),
static_cast<size_t> (resultStrLen)}}) <= effectivePrecision);
392 return String{span{buf.data (),
static_cast<size_t> (resultStrLen)}};
397 template <
typename FLOAT_TYPE>
398 String ToString_GeneralCase_ (FLOAT_TYPE f,
const ToStringOptions& options)
402 static thread_local stringstream s;
403 static const ios_base::fmtflags kDefaultIOSFmtFlags_ = s.flags ();
408 s.imbue (options.GetUseLocale ());
411 s.flags (options.GetIOSFmtFlags ().value_or (kDefaultIOSFmtFlags_));
414 unsigned int usePrecision = options.GetPrecision ().value_or (Precision{}).GetEffectivePrecision<FLOAT_TYPE> ();
415 s.precision (usePrecision);
418 optional<ios_base::fmtflags> useFloatField;
419 switch (options.GetFloatFormat ().value_or (FloatFormatType::eDEFAULT)) {
420 case FloatFormatType::eScientific:
421 useFloatField = ios_base::scientific;
423 case FloatFormatType::eDefaultFloat:
425 case FloatFormatType::eFixedPoint:
426 useFloatField = ios_base::fixed;
428 case FloatFormatType::eAutomaticScientific: {
429 bool useScientificNotation = abs (f) >= pow (10, usePrecision / 2) or
430 (f != 0 and abs (f) < pow (10, -static_cast<
int> (usePrecision) / 2));
431 if (useScientificNotation) {
432 useFloatField = ios_base::scientific;
440 s.setf (*useFloatField, ios_base::floatfield);
443 s.unsetf (ios_base::floatfield);
449 return options.GetUsingLocaleClassic () ? String{s.str ()} :
String::FromNarrowString (s.str (), options.GetUseLocale ());
454 template <
typename FLOAT_TYPE>
455 String ToString_String_Implementation_ (FLOAT_TYPE f,
const ToStringOptions& options)
457 auto result = (options.GetUsingLocaleClassic () and not options.GetIOSFmtFlags () and not options.GetFloatFormat ())
458 ? Private_::ToString_OptimizedForCLocaleAndNoStreamFlags_ (f, options.GetPrecision ().value_or (Precision{}))
459 : Private_::ToString_GeneralCase_ (f, options);
460 if (options.GetTrimTrailingZeros ().value_or (ToStringOptions::kDefaultTrimTrailingZeros)) {
461 TrimTrailingZeros_ (&result);
479 template <
typename T, IUNICODECanUnambiguouslyConvertFrom CHAR_T>
480 inline T ToFloat_RespectingLocale_ (
const span<const CHAR_T> srcSpan,
typename span<const CHAR_T>::iterator* remainder)
482 if (srcSpan.empty ()) {
483 if (remainder !=
nullptr) {
484 *remainder = srcSpan.begin ();
486 return Math::nan<T> ();
492 Memory::StackBuffer<CHAR_T> srcBufWithNul{Memory::eUninitialized, srcSpan.size () + 1};
493 Memory::CopyBytes (srcSpan, span{srcBufWithNul});
494 srcBufWithNul[srcSpan.size ()] =
'\0';
495 const CHAR_T* si = srcBufWithNul.begin ();
496 const CHAR_T* ei = srcBufWithNul.end () - 1;
497 const CHAR_T* ri = ei;
500 if (remainder ==
nullptr) {
502 if constexpr (
sizeof (CHAR_T) == 1) {
503 isSpace = std::isspace (*si);
505 else if constexpr (
sizeof (CHAR_T) ==
sizeof (
wchar_t)) {
506 isSpace = std::iswspace (*si);
509 isSpace = std::iswspace (
static_cast<wint_t
> (*si));
512 if (remainder !=
nullptr) {
513 *remainder = srcSpan.begin ();
515 return Math::nan<T> ();
519 static_assert (same_as<T, float> or same_as<T, double> or same_as<T, long double>);
520 if constexpr (
sizeof (CHAR_T) == 1) {
521 if constexpr (same_as<T, float>) {
522 d = ::strtof (
reinterpret_cast<const char*
> (si),
const_cast<char**
> (
reinterpret_cast<const char**
> (&ri)));
524 else if constexpr (same_as<T, double>) {
525 d = ::strtod (
reinterpret_cast<const char*
> (si),
const_cast<char**
> (
reinterpret_cast<const char**
> (&ri)));
527 else if constexpr (same_as<T, long double>) {
528 d = ::strtold (
reinterpret_cast<const char*
> (si),
const_cast<char**
> (
reinterpret_cast<const char**
> (&ri)));
534 else if constexpr (
sizeof (CHAR_T) ==
sizeof (
wchar_t)) {
535 if constexpr (same_as<T, float>) {
536 d = ::wcstof (
reinterpret_cast<const wchar_t*
> (si),
const_cast<wchar_t**
> (
reinterpret_cast<const wchar_t**
> (&ri)));
538 else if constexpr (same_as<T, double>) {
539 d = ::wcstod (
reinterpret_cast<const wchar_t*
> (si),
const_cast<wchar_t**
> (
reinterpret_cast<const wchar_t**
> (&ri)));
541 else if constexpr (same_as<T, long double>) {
542 d = ::wcstold (
reinterpret_cast<const wchar_t*
> (si),
const_cast<wchar_t**
> (
reinterpret_cast<const wchar_t**
> (&ri)));
550 Memory::StackBuffer<wchar_t> wideBuf{Memory::eUninitialized, UTFConvert::ComputeTargetBufferSize<wchar_t> (srcSpan)};
552 if (remainder ==
nullptr) {
553 d = ToFloat_RespectingLocale_<T, wchar_t> (wideSpan,
nullptr);
557 span<const wchar_t>::iterator wideRemainder;
558 d = ToFloat_RespectingLocale_<T> (wideSpan, &wideRemainder);
562 if (remainder ==
nullptr) {
569 *remainder = srcSpan.begin () + (ri - si);
582 inline String
ToString (
float f,
const ToStringOptions& options)
584 return Private_::ToString_String_Implementation_ (f, options);
587 inline String
ToString (
double f,
const ToStringOptions& options)
589 return Private_::ToString_String_Implementation_ (f, options);
592 inline String
ToString (
long double f,
const ToStringOptions& options)
594 return Private_::ToString_String_Implementation_ (f, options);
597 inline string ToString (
float f,
const ToStringOptions& options)
600 Require (options.GetUsingLocaleClassic ());
601 return Private_::ToString_String_Implementation_ (f, options).AsASCII ();
604 inline string ToString (
double f,
const ToStringOptions& options)
607 Require (options.GetUsingLocaleClassic ());
608 return Private_::ToString_String_Implementation_ (f, options).AsASCII ();
611 inline string ToString (
long double f,
const ToStringOptions& options)
614 Require (options.GetUsingLocaleClassic ());
615 return Private_::ToString_String_Implementation_ (f, options).AsASCII ();
618 inline wstring
ToString (
float f,
const ToStringOptions& options)
621 Require (options.GetUsingLocaleClassic ());
622 return Private_::ToString_String_Implementation_ (f, options).As<wstring> ();
625 inline wstring
ToString (
double f,
const ToStringOptions& options)
628 Require (options.GetUsingLocaleClassic ());
629 return Private_::ToString_String_Implementation_ (f, options).As<wstring> ();
632 inline wstring
ToString (
long double f,
const ToStringOptions& options)
635 Require (options.GetUsingLocaleClassic ());
636 return Private_::ToString_String_Implementation_ (f, options).As<wstring> ();
644 template <
floating_po
int T, IUNICODECanUnambiguouslyConvertFrom CHAR_T>
647 if constexpr (same_as<remove_cv_t<CHAR_T>,
char>) {
657#if __cpp_lib_to_chars >= 201611 and not qCompilerAndStdLib_from_chars_and_tochars_FP_Precision_Buggy
658#if qCompilerAndStdLib_to_chars_assmes_str_nul_terminated_Buggy
661 auto b = asciiS.begin ();
662 auto e = asciiS.end ();
663#if qCompilerAndStdLib_to_chars_assmes_str_nul_terminated_Buggy
666 if (b != e and *b ==
'+') [[unlikely]] {
669 auto [ptr, ec] = from_chars (b, e, result);
670 if (ec == errc::result_out_of_range) [[unlikely]] {
671 result = *b ==
'-' ? -numeric_limits<T>::infinity () : numeric_limits<T>::infinity ();
673 else if (ec != std::errc{} or ptr != e) [[unlikely]] {
676 result = Private_::ToFloat_RespectingLocale_<T> (s,
nullptr);
679 result = Private_::ToFloat_RespectingLocale_<T> (span<const char>{asciiS},
nullptr);
683 return Private_::ToFloat_RespectingLocale_<T> (s,
nullptr);
685 template <
floating_po
int T, IUNICODECanUnambiguouslyConvertFrom CHAR_T>
686 T ToFloat (span<const CHAR_T> s,
typename span<const CHAR_T>::iterator* remainder)
689 if constexpr (same_as<remove_cv_t<CHAR_T>,
char>) {
696#if __cpp_lib_to_chars >= 201611 and not qCompilerAndStdLib_from_chars_and_tochars_FP_Precision_Buggy
700 char* b = asciiS.begin ();
701 char* e = asciiS.end ();
702 if (b != e and *b ==
'+') [[unlikely]] {
705 auto [ptr, ec] = from_chars (b, e, result);
706 if (ec == errc::result_out_of_range) [[unlikely]] {
707 result = *b ==
'-' ? -numeric_limits<T>::infinity () : numeric_limits<T>::infinity ();
709 else if (ec != std::errc{} or ptr != e) [[unlikely]] {
712 typename span<const CHAR_T>::iterator tmpI;
713 result = Private_::ToFloat_RespectingLocale_<T> (s, &tmpI);
714 ptr = tmpI - s.begin () + b;
717 span{
reinterpret_cast<const char8_t*
> (b),
reinterpret_cast<const char8_t*
> (e)}, ptr - b);
718 Assert (*remainder <= s.end ());
722 return Private_::ToFloat_RespectingLocale_<T> (s, remainder);
724 template <
floating_po
int T,
typename STRINGISH_ARG>
725 inline T
ToFloat (STRINGISH_ARG&& s)
726 requires (IConvertibleToString<STRINGISH_ARG> or is_convertible_v<STRINGISH_ARG, std::string>)
728 using DecayedStringishArg = remove_cvref_t<STRINGISH_ARG>;
729 if constexpr (same_as<DecayedStringishArg, const char*> or same_as<DecayedStringishArg, const char8_t*> or
730 same_as<DecayedStringishArg, const char16_t*> or same_as<DecayedStringishArg, const char32_t*> or
731 same_as<DecayedStringishArg, const wchar_t*>) {
732 return ToFloat<T> (span{s, CString::Length (s)});
734 else if constexpr (same_as<DecayedStringishArg, String>) {
735 Memory::StackBuffer<wchar_t> ignored;
736 auto sp = s.template GetData<wchar_t> (&ignored);
737 return ToFloat<T> (sp);
739 else if constexpr (is_convertible_v<DecayedStringishArg, std::string>) {
741 return ToFloat<T> (span{ss.data (), ss.size ()});
744 return ToFloat<T> (String{forward<STRINGISH_ARG> (s)});
747 template <
floating_po
int T>
748 inline T
ToFloat (
const String& s, String* remainder)
751 Memory::StackBuffer<wchar_t> ignored;
752 span<const wchar_t> srcSpan = s.GetData (&ignored);
753 span<const wchar_t>::iterator tmpRemainder;
754 auto result = ToFloat<T> (srcSpan, &tmpRemainder);
755 *remainder = String{srcSpan.subspan (tmpRemainder - srcSpan.begin ())};
761 template <
typename T>
762 [[deprecated (
"Since Stroika v3.0d1 - use span overload")]]
inline T
ToFloat (
const wchar_t* start,
const wchar_t* end,
const wchar_t** remainder)
764 Require (start <= end);
767#if __cpp_lib_to_chars >= 201611 and not qCompilerAndStdLib_from_chars_and_tochars_FP_Precision_Buggy
772 Memory::StackBuffer<char> asciiS;
774 auto b = asciiS.begin ();
776 auto e = asciiS.end ();
777 if (remainder !=
nullptr) [[unlikely]] {
779 while (b != e and iswspace (*b)) [[unlikely]] {
783 if (b != e and *b ==
'+') [[unlikely]] {
787 auto [ptr, ec] = from_chars (b, e, result);
788 if (ec == errc::result_out_of_range) [[unlikely]] {
789 return *b ==
'-' ? -numeric_limits<T>::infinity () : numeric_limits<T>::infinity ();
792 if (ec != std::errc{}) [[unlikely]] {
793 result = Math::nan<T> ();
797 if (remainder ==
nullptr) [[likely]] {
798 if (ptr != e) [[unlikely]] {
799 result = Math::nan<T> ();
803 *remainder = ptr - originalB + start;
807 result = Private_::ToFloat_ViaStrToD_<T> (start, end, remainder);
810 result = Private_::ToFloat_ViaStrToD_<T> (start, end, remainder);
814 template <
typename T =
double>
815 [[deprecated (
"Since Stroika v3.0d1 - use span overload")]]
inline T
ToFloat (
const char* start,
const char* end)
817 return ToFloat<T> (span{start, end});
819 template <
typename T =
double>
820 [[deprecated (
"Since Stroika v3.0d1 - use span overload")]]
inline T
ToFloat (
const wchar_t* start,
const wchar_t* end)
822 return ToFloat<T> (span{start, end});
#define RequireNotReached()
#define RequireNotNull(p)
#define AssertNotReached()
constexpr bool IsASCII() const noexcept
Return true iff the given character (or all in span) is (are) in the ascii range [0....
static bool AsASCIIQuietly(span< const CHAR_T > fromS, RESULT_T *into)
static String FromNarrowString(const char *from, const locale &l)
static constexpr size_t npos
static const UTFConvert kThe
Nearly always use this default UTFConvert.
nonvirtual span< TRG_T > ConvertSpan(span< const SRC_T > source, span< TRG_T > target) const
Convert between UTF-N encoded (including the special case of ASCII, and Latin1) character spans (e....
nonvirtual size_t ConvertOffset(span< const SRC_T > source, size_t srcIndex) const
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
nonvirtual void push_back(Common::ArgByValueType< T > e)
nonvirtual Iterator< T > begin() const
Support for ranged for, and STL syntax in general.
T ToFloat(span< const CHAR_T > s)
STRING_TYPE ToString(FLOAT_TYPE f, const ToStringOptions &options={})
constexpr Precision()=default
static const Precision kFull
Full precision here means enough digits so that when written out (serialized) - and read back in (des...
constexpr ToStringOptions()=default
nonvirtual locale GetUseLocale() const
return the selected locale object
nonvirtual bool GetUsingLocaleClassic() const
return true if locale used is locale::classic() - the 'C' locale; mostly used as optimization/special...