Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
FloatConversion.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include <charconv>
5#include <sstream>
6
9#include "Stroika/Foundation/Containers/Common.h"
10#include "Stroika/Foundation/Math/Common.h"
11#include "Stroika/Foundation/Memory/Common.h"
14
16
17 /*
18 ********************************************************************************
19 ************************ FloatConversion::Precision ****************************
20 ********************************************************************************
21 */
22 constexpr Precision::Precision (unsigned int p)
23 : fPrecision_{p}
24 {
25 }
26 constexpr Precision::Precision (FullFlag)
27 : fPrecision_{}
28 {
29 }
30 template <floating_point T>
31 constexpr unsigned int Precision::GetEffectivePrecision () const
32 {
33 // note missing implies eFull
34 // https://stackoverflow.com/questions/22458355/what-is-the-purpose-of-max-digits10-and-how-is-it-different-from-digits10
35 return fPrecision_.value_or (numeric_limits<T>::max_digits10);
36 }
37 /**
38 * \brief Full precision here means enough digits so that when written out (serialized) - and read back in (deserialized)
39 * you get the exact same answer.
40 *
41 * \see // https://stackoverflow.com/questions/22458355/what-is-the-purpose-of-max-digits10-and-how-is-it-different-from-digits10
42 * numeric_limits<T>::max_digits10
43 */
44 constexpr inline Precision Precision::kFull{Precision::FullFlag::eFull};
45
46 /*
47 ********************************************************************************
48 ********************* FloatConversion::ToStringOptions *************************
49 ********************************************************************************
50 */
51 inline ToStringOptions::ToStringOptions (const locale& l)
52 : fUseLocale_{l}
53 {
54 }
56 : fUseCurrentLocale_{p == PredefinedLocale::eUseCurrentLocale}
57 {
58 }
59 constexpr ToStringOptions::ToStringOptions (ios_base::fmtflags fmtFlags)
60 : fFmtFlags_{fmtFlags}
61 {
62 }
63 constexpr ToStringOptions::ToStringOptions (Precision precision)
64 : fPrecision_{precision}
65 {
66 }
67 constexpr ToStringOptions::ToStringOptions (FloatFormatType scientificNotation)
68 : fFloatFormat_{scientificNotation}
69 {
70 }
72 : fTrimTrailingZeros_{trimTrailingZeros == TrimTrailingZerosType::eTrimZeros}
73 {
74 }
75 inline ToStringOptions::ToStringOptions (const ToStringOptions& b1, const ToStringOptions& b2)
76 : ToStringOptions{b1}
77 {
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_);
84 }
85 template <typename... ARGS>
86 inline ToStringOptions::ToStringOptions (const ToStringOptions& b1, const ToStringOptions& b2, ARGS&&... args)
87 : ToStringOptions{ToStringOptions{b1, b2}, forward<ARGS> (args)...}
88 {
89 }
90 inline optional<Precision> ToStringOptions::GetPrecision () const
91 {
92 return fPrecision_;
93 }
94 inline optional<bool> ToStringOptions::GetTrimTrailingZeros () const
95 {
96 return fTrimTrailingZeros_;
97 }
98 inline locale ToStringOptions::GetUseLocale () const
99 {
100 if (fUseCurrentLocale_) {
101 return locale{};
102 }
103 if (fUseLocale_) {
104 return *fUseLocale_;
105 }
106 static const locale kCLocale_ = locale::classic ();
107 return kCLocale_;
108 }
110 {
111 if (not fUseLocale_ and not fUseCurrentLocale_) {
112 return true; // very common case
113 }
114 return GetUseLocale () == locale::classic ();
115 }
116 inline optional<FloatFormatType> ToStringOptions::GetFloatFormat () const
117 {
118 return fFloatFormat_;
119 }
120 inline optional<ios_base::fmtflags> ToStringOptions::GetIOSFmtFlags () const
121 {
122 return fFmtFlags_;
123 }
124
125 namespace Private_ {
126 inline void TrimTrailingZeros_ (String* strResult)
127 {
128 // @todo THIS could be more efficient. We should KNOW case of the 'e' and maybe able to tell/avoid looking based on args to String2Float
129 RequireNotNull (strResult);
130 // strip trailing zeros - except for the last first one after the decimal point.
131 // And don't do if ends with exponential notation e+40 shouldnt get shortned to e+4!
132 bool hasE = strResult->Find ('e', eCaseInsensitive).has_value ();
133 //Assert (hasE == (strResult->find ('e') != String::npos or strResult->find ('E') != String::npos));
134 if (not hasE) {
135 size_t pastDot = strResult->find ('.');
136 if (pastDot != String::npos) {
137 ++pastDot;
138 size_t len = strResult->length ();
139 size_t pPastLastZero = len;
140 for (; (pPastLastZero - 1) > pastDot; --pPastLastZero) {
141 if ((*strResult)[pPastLastZero - 1] != '0') {
142 break;
143 }
144 }
145 if (len != pPastLastZero) [[unlikely]] { // check common case of no change, but this substring and assign already pretty optimized (not sure helps performance)
146 *strResult = strResult->SubString (0, pPastLastZero);
147 }
148 }
149 }
150 }
151 }
152
153 namespace Private_ {
154 // TEMPLATE version of wcstof(etc) - to make easier to call from generic algorithm
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);
159 template <>
160 inline float CStr2FloatType_ (const wchar_t* s, wchar_t** e)
161 {
162 return ::wcstof (s, e);
163 }
164 template <>
165 inline double CStr2FloatType_ (const wchar_t* s, wchar_t** e)
166 {
167 return wcstod (s, e);
168 }
169 template <>
170 inline long double CStr2FloatType_ (const wchar_t* s, wchar_t** e)
171 {
172 return wcstold (s, e);
173 }
174 template <>
175 inline float CStr2FloatType_ (const char* s, char** e)
176 {
177 return ::strtof (s, e);
178 }
179 template <>
180 inline double CStr2FloatType_ (const char* s, char** e)
181 {
182 return strtod (s, e);
183 }
184 template <>
185 inline long double CStr2FloatType_ (const char* s, char** e)
186 {
187 return strtold (s, e);
188 }
189 }
190
191 namespace Private_ {
192 template <typename RETURN_TYPE>
193 RETURN_TYPE ToFloat_ViaStrToD_ (const char* start, const char* end, const char** remainder)
194 {
195 Require (start <= end);
196 if (start == end) {
197 if (remainder != nullptr) {
198 *remainder = start;
199 }
200 return Math::nan<RETURN_TYPE> ();
201 }
202 else {
203 char* e = nullptr;
204 const char* cst = start;
205
206 if (remainder == nullptr) {
207 // REJECT strings with leading space in this case - must match exactly
208 if (::isspace (*cst)) {
209 return Math::nan<RETURN_TYPE> ();
210 }
211 }
212
213 Memory::StackBuffer<char> tmp;
214 if (*end != '\0') {
215 // remap addresses - copying to a temporary buffer, so we can nul-terminate string passed to strtod (etc)
216 size_t len = end - start;
217 tmp.GrowToSize (len + 1);
218 (void)::memcpy (tmp.begin (), start, len);
219 cst = tmp.begin ();
220 tmp[len] = '\0';
221 }
222 RETURN_TYPE d = CStr2FloatType_<RETURN_TYPE> (cst, &e);
223 // If asked to return remainder do so.
224 // If NOT asked to return remainder, treat not using the entire string as forcing result to be a Nan (invalid parse of number of not the whole thing is a number)
225 if (remainder == nullptr) {
226 if (e != end) {
227 d = Math::nan<RETURN_TYPE> ();
228 }
229 }
230 else {
231 *remainder = e - cst + start; // adjust in case we remapped data
232 }
233 return d;
234 }
235 }
236 template <typename RETURN_TYPE>
237 RETURN_TYPE ToFloat_ViaStrToD_ (const wchar_t* start, const wchar_t* end, const wchar_t** remainder)
238 {
239 Require (start <= end);
240 if (start == end) {
241 if (remainder != nullptr) {
242 *remainder = start;
243 }
244 return Math::nan<RETURN_TYPE> ();
245 }
246 else {
247 wchar_t* e = nullptr;
248 const wchar_t* cst = start;
249
250 if (remainder == nullptr) {
251 // REJECT strings with leading space in this case - must match exactly
252 if (::iswspace (*cst)) {
253 return Math::nan<RETURN_TYPE> ();
254 }
255 }
256
257 Memory::StackBuffer<wchar_t> tmp;
258 if (*end != '\0') {
259 // remap addresses - copying to a temporary buffer, so we can nul-terminate string passed to strtod (etc)
260 size_t len = end - start;
261 tmp.GrowToSize (len + 1);
262 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wrestrict\""); // /usr/include/x86_64-linux-gnu/bits/string_fortified.h:29:33: warning: 'void* __builtin_memcpy(void*, const void*, long unsigned int)' accessing 18446744073709551612 bytes at offsets 8 and 0 overlaps 9223372036854775801 bytes at offset -9223372036854775805 [-Wrestrict]
263 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wstringop-overflow=\"");
264 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wstringop-overflow\""); // desperation try to silience
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\"");
269 cst = tmp.begin ();
270 tmp[len] = '\0';
271 }
272 RETURN_TYPE d = CStr2FloatType_<RETURN_TYPE> (cst, &e);
273 // If asked to return remainder do so.
274 // If NOT asked to return remainder, treat not using the entire string as forcing result to be a Nan (invalid parse of number of not the whole thing is a number)
275 if (remainder == nullptr) {
276 if (e != end) {
277 d = Math::nan<RETURN_TYPE> ();
278 }
279 }
280 else {
281 *remainder = e - cst + start; // adjust in case we remapped data
282 }
283 return d;
284 }
285 }
286 }
287
288 namespace Private_ {
289#if qStroika_Foundation_Debug_AssertionsChecked || !(__cpp_lib_to_chars >= 201611)
290 inline size_t CalcPrecision_ (const String& numStr, bool countZerosAtEndAfterDecPoint = false)
291 {
292 bool leading = true;
293 bool ignoreRest = false;
294 bool seenDot = false;
295 size_t n{};
296 size_t nTrailingZeros{};
297 numStr.Apply ([&] (Character c) {
298 if (ignoreRest) {
299 return;
300 }
301 if (c == '.') {
302 seenDot = true;
303 }
304 if (c == '+' or c == '-' or c == '.') {
305 return;
306 }
307 if (leading and c == '0') {
308 return;
309 }
310 leading = false;
311 if (c == 'e') {
312 ignoreRest = true;
313 return;
314 }
315 if (countZerosAtEndAfterDecPoint and seenDot) {
316 if (c == '0') {
317 ++nTrailingZeros;
318 }
319 else {
320 nTrailingZeros = 0;
321 }
322 }
323 ++n;
324 });
325 return countZerosAtEndAfterDecPoint ? n : n - nTrailingZeros;
326 }
327#endif
328 template <typename FLOAT_TYPE>
329 String ToString_OptimizedForCLocaleAndNoStreamFlags_ (FLOAT_TYPE f, Precision precision)
330 {
331 using namespace Memory;
332 size_t sz = numeric_limits<FLOAT_TYPE>::max_digits10 + numeric_limits<FLOAT_TYPE>::max_exponent10 + 5; // source? "-1.##e+##\0"
333 StackBuffer<char> buf{Memory::eUninitialized, sz};
334 ptrdiff_t resultStrLen;
335 unsigned int effectivePrecision = precision.GetEffectivePrecision<FLOAT_TYPE> ();
336
337 // XCode 15 still doesn't define __cpp_lib_to_chars, as well as _LIBCPP_VERSION < 190000, I believe --LGP 2024-07-13
338#if __cpp_lib_to_chars >= 201611
339 // empirically, on MSVC, to_chars() is much faster than snprintf (appears 3x apx faster) -- LGP 2021-11-04
340 if (precision == Precision::kFull) {
341 resultStrLen = to_chars (buf.begin (), buf.end (), f, chars_format::general).ptr - buf.begin ();
342 }
343 else {
344 resultStrLen = to_chars (buf.begin (), buf.end (), f, chars_format::general, effectivePrecision).ptr - buf.begin ();
345 }
346#else
347 auto mkFmtWithPrecisionArg_ = [] (char* formatBufferStart, [[maybe_unused]] char* formatBufferEnd, char _Spec, bool forceScientific) -> char* {
348 char* fmtPtr = formatBufferStart;
349 *fmtPtr++ = '%';
350 // include precision arg
351 *fmtPtr++ = '.';
352 *fmtPtr++ = '*';
353 if (_Spec != '\0') {
354 *fmtPtr++ = _Spec; // e.g. 'L' qualifier
355 }
356 *fmtPtr++ = forceScientific ? 'e' : 'g'; // format specifier
357 *fmtPtr = '\0';
358 Require (fmtPtr < formatBufferEnd);
359 return formatBufferStart;
360 };
361
362 FLOAT_TYPE useRoundedFloat = Math::Round<FLOAT_TYPE> (f, effectivePrecision);
363 if (precision != Precision::kFull) {
364 f = useRoundedFloat;
365 }
366
367 bool forceScientific = fabs (f) >= std::pow (10, effectivePrecision);
368 char format[100]; // intentionally uninitialized, cuz filled in with mkFmtWithPrecisionArg_
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)); // slide 'e+22' back over the lost precision bytes of number
381 }
382 resultStrLen -= nBytes;
383 }
384#endif
385
386 // [[maybe_unused]] auto oo1 = String{span{buf.data (), static_cast<size_t> (resultStrLen)}}.As<wstring> ();
387 // [[maybe_unused]] auto ooo = CalcPrecision_ (String{span{buf.data (), static_cast<size_t> (resultStrLen)}});
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);
391#endif
392 return String{span{buf.data (), static_cast<size_t> (resultStrLen)}};
393 }
394 }
395
396 namespace Private_ {
397 template <typename FLOAT_TYPE>
398 String ToString_GeneralCase_ (FLOAT_TYPE f, const ToStringOptions& options)
399 {
400 // expensive to construct, and slightly cheaper to just use thread_local version of
401 // the same stringstream each time (only one per thread can be in use)
402 static thread_local stringstream s;
403 static const ios_base::fmtflags kDefaultIOSFmtFlags_ = s.flags (); // Just copy out of the first constructed stringstream
404
405 s.str (string{});
406 s.clear ();
407
408 s.imbue (options.GetUseLocale ());
409
410 // must set explicitly (even if defaulted) because of the thread_local thing
411 s.flags (options.GetIOSFmtFlags ().value_or (kDefaultIOSFmtFlags_));
412
413 // todo must set default precision because of the thread_local thing
414 unsigned int usePrecision = options.GetPrecision ().value_or (Precision{}).GetEffectivePrecision<FLOAT_TYPE> ();
415 s.precision (usePrecision);
416
417 {
418 optional<ios_base::fmtflags> useFloatField;
419 switch (options.GetFloatFormat ().value_or (FloatFormatType::eDEFAULT)) {
420 case FloatFormatType::eScientific:
421 useFloatField = ios_base::scientific;
422 break;
423 case FloatFormatType::eDefaultFloat:
424 break;
425 case FloatFormatType::eFixedPoint:
426 useFloatField = ios_base::fixed;
427 break;
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)); // scientific preserves more precision - but non-scientific looks better
431 if (useScientificNotation) {
432 useFloatField = ios_base::scientific;
433 }
434 } break;
435 default:
437 break;
438 }
439 if (useFloatField) {
440 s.setf (*useFloatField, ios_base::floatfield);
441 }
442 else {
443 s.unsetf (ios_base::floatfield); // see std::defaultfloat - not same as ios_base::fixed
444 }
445 }
446
447 s << f;
448
449 return options.GetUsingLocaleClassic () ? String{s.str ()} : String::FromNarrowString (s.str (), options.GetUseLocale ());
450 }
451 }
452
453 namespace Private_ {
454 template <typename FLOAT_TYPE>
455 String ToString_String_Implementation_ (FLOAT_TYPE f, const ToStringOptions& options)
456 {
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);
462 }
463 return result;
464 }
465 }
466
467 namespace Private_ {
468 //
469 // This RESPECTS THE CURRENT LOCALE
470 // this private function allows nullptr remainder, and behaves somewhat differently if null or not (unlike PUBLIC API)
471 // Roughly (ignoring precision and character set)
472 // Calls strtod
473 // Strtod Skips leading whitespace, but this routine does NOT allow that
474 // if remainder==null, rejects if not all characters eaten (else returns how many eaten in remainder)
475 //
476 // Note - inlined though long, because most of the logic gets compiled out depending on (template or actual)
477 // parameters, and want to assure those 'seen' by optimizer so most of the code eliminated.
478 //
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)
481 {
482 if (srcSpan.empty ()) {
483 if (remainder != nullptr) {
484 *remainder = srcSpan.begin ();
485 }
486 return Math::nan<T> ();
487 }
488
489 // because strtod, etc, require a NUL-terminated string, and span doesn't generally provide one,
490 // we must copy to a temporary buffer (not super costly, especially since this isn't the main
491 // approach tried typically)
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; // buf nul-terminated, but dont treat the NUL as part of our length
497 const CHAR_T* ri = ei;
498
499 // since strtod skips leading whitespace, prevent that
500 if (remainder == nullptr) {
501 bool isSpace;
502 if constexpr (sizeof (CHAR_T) == 1) {
503 isSpace = std::isspace (*si);
504 }
505 else if constexpr (sizeof (CHAR_T) == sizeof (wchar_t)) {
506 isSpace = std::iswspace (*si);
507 }
508 else {
509 isSpace = std::iswspace (static_cast<wint_t> (*si)); // not sure how to check without complex conversion logic
510 }
511 if (isSpace) {
512 if (remainder != nullptr) {
513 *remainder = srcSpan.begin ();
514 }
515 return Math::nan<T> ();
516 }
517 }
518 T d; // intentionally uninitialized - set below
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)));
523 }
524 else if constexpr (same_as<T, double>) {
525 d = ::strtod (reinterpret_cast<const char*> (si), const_cast<char**> (reinterpret_cast<const char**> (&ri)));
526 }
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)));
529 }
530 else {
532 }
533 }
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)));
537 }
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)));
540 }
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)));
543 }
544 else {
546 }
547 }
548 else {
549 // must utf convert to wchar_t which we support
550 Memory::StackBuffer<wchar_t> wideBuf{Memory::eUninitialized, UTFConvert::ComputeTargetBufferSize<wchar_t> (srcSpan)};
551 span<const wchar_t> wideSpan = UTFConvert::kThe.ConvertSpan (srcSpan, span{wideBuf});
552 if (remainder == nullptr) {
553 d = ToFloat_RespectingLocale_<T, wchar_t> (wideSpan, nullptr);
554 }
555 else {
556 // do the conversion using wchar_t, and then map back the resulting remainder offset
557 span<const wchar_t>::iterator wideRemainder;
558 d = ToFloat_RespectingLocale_<T> (wideSpan, &wideRemainder);
559 ri = UTFConvert::kThe.ConvertOffset<CHAR_T> (wideSpan, wideRemainder - wideSpan.begin ()) + si;
560 }
561 }
562 if (remainder == nullptr) {
563 // if no remainder argument, we require all the text is 'eaten'
564 if (ri != ei) {
565 d = Math::nan<T> ();
566 }
567 }
568 else {
569 *remainder = srcSpan.begin () + (ri - si);
570 }
571 return d;
572 }
573
574 }
575
576 /*
577 ********************************************************************************
578 ************************** FloatConversion::ToString ***************************
579 ********************************************************************************
580 */
581 template <>
582 inline String ToString (float f, const ToStringOptions& options)
583 {
584 return Private_::ToString_String_Implementation_ (f, options);
585 }
586 template <>
587 inline String ToString (double f, const ToStringOptions& options)
588 {
589 return Private_::ToString_String_Implementation_ (f, options);
590 }
591 template <>
592 inline String ToString (long double f, const ToStringOptions& options)
593 {
594 return Private_::ToString_String_Implementation_ (f, options);
595 }
596 template <>
597 inline string ToString (float f, const ToStringOptions& options)
598 {
599 // @todo improve performance for this case
600 Require (options.GetUsingLocaleClassic ());
601 return Private_::ToString_String_Implementation_ (f, options).AsASCII ();
602 }
603 template <>
604 inline string ToString (double f, const ToStringOptions& options)
605 {
606 // @todo improve performance for this case
607 Require (options.GetUsingLocaleClassic ());
608 return Private_::ToString_String_Implementation_ (f, options).AsASCII ();
609 }
610 template <>
611 inline string ToString (long double f, const ToStringOptions& options)
612 {
613 // @todo improve performance for this case
614 Require (options.GetUsingLocaleClassic ());
615 return Private_::ToString_String_Implementation_ (f, options).AsASCII ();
616 }
617 template <>
618 inline wstring ToString (float f, const ToStringOptions& options)
619 {
620 // @todo improve performance for this case
621 Require (options.GetUsingLocaleClassic ());
622 return Private_::ToString_String_Implementation_ (f, options).As<wstring> ();
623 }
624 template <>
625 inline wstring ToString (double f, const ToStringOptions& options)
626 {
627 // @todo improve performance for this case
628 Require (options.GetUsingLocaleClassic ());
629 return Private_::ToString_String_Implementation_ (f, options).As<wstring> ();
630 }
631 template <>
632 inline wstring ToString (long double f, const ToStringOptions& options)
633 {
634 // @todo improve performance for this case
635 Require (options.GetUsingLocaleClassic ());
636 return Private_::ToString_String_Implementation_ (f, options).As<wstring> ();
637 }
638
639 /*
640 ********************************************************************************
641 *************************** FloatConversion::ToFloat ***************************
642 ********************************************************************************
643 */
644 template <floating_point T, IUNICODECanUnambiguouslyConvertFrom CHAR_T>
645 T ToFloat (span<const CHAR_T> s)
646 {
647 if constexpr (same_as<remove_cv_t<CHAR_T>, char>) {
648 Require (Character::IsASCII (s));
649 }
650 /*
651 * Most of the time we can do this very efficiently, because there are just ascii characters.
652 * Else, fallback on algorithm that understands full unicode character set, and locales
653 */
655 if (Character::AsASCIIQuietly (s, &asciiS)) {
656 T result; // intentionally uninitialized
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
659 asciiS.push_back (0);
660#endif
661 auto b = asciiS.begin ();
662 auto e = asciiS.end ();
663#if qCompilerAndStdLib_to_chars_assmes_str_nul_terminated_Buggy
664 e--;
665#endif
666 if (b != e and *b == '+') [[unlikely]] {
667 ++b; // "the plus sign is not recognized outside of the exponent (only the minus sign is permitted at the beginning)" from https://en.cppreference.com/w/cpp/utility/from_chars
668 }
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 ();
672 }
673 else if (ec != std::errc{} or ptr != e) [[unlikely]] {
674 //result = Math::nan<T> (); "if # is 100,1 - in funny locale - may need to use legacy algorithm
675 // @todo ADD OPTION arg to ToFloat so we know if C-Locale so can just return NAN here!...
676 result = Private_::ToFloat_RespectingLocale_<T> (s, nullptr);
677 }
678#else
679 result = Private_::ToFloat_RespectingLocale_<T> (span<const char>{asciiS}, nullptr);
680#endif
681 return result;
682 }
683 return Private_::ToFloat_RespectingLocale_<T> (s, nullptr); // fallback for non-ascii strings to old code
684 }
685 template <floating_point T, IUNICODECanUnambiguouslyConvertFrom CHAR_T>
686 T ToFloat (span<const CHAR_T> s, typename span<const CHAR_T>::iterator* remainder)
687 {
688 RequireNotNull (remainder); // use other overload if 'null'
689 if constexpr (same_as<remove_cv_t<CHAR_T>, char>) {
690 Require (Character::IsASCII (s));
691 }
692 /*
693 * Most of the time we can do this very efficiently, because there are just ascii characters.
694 * Else, fallback on algorithm that understands full unicode character set.
695 */
696#if __cpp_lib_to_chars >= 201611 and not qCompilerAndStdLib_from_chars_and_tochars_FP_Precision_Buggy
698 if (Character::AsASCIIQuietly (s, &asciiS)) {
699 T result; // intentionally uninitialized
700 char* b = asciiS.begin ();
701 char* e = asciiS.end ();
702 if (b != e and *b == '+') [[unlikely]] {
703 ++b; // "the plus sign is not recognized outside of the exponent (only the minus sign is permitted at the beginning)" from https://en.cppreference.com/w/cpp/utility/from_chars
704 }
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 ();
708 }
709 else if (ec != std::errc{} or ptr != e) [[unlikely]] {
710 //result = Math::nan<T> (); "if # is 100,1 - in funny locale - may need to use legacy algorithm
711 // @todo ADD OPTION arg to ToFloat so we know if C-Locale so can just return NAN here!...
712 typename span<const CHAR_T>::iterator tmpI;
713 result = Private_::ToFloat_RespectingLocale_<T> (s, &tmpI);
714 ptr = tmpI - s.begin () + b;
715 }
716 *remainder = s.begin () + UTFConvert::kThe.ConvertOffset<CHAR_T> (
717 span{reinterpret_cast<const char8_t*> (b), reinterpret_cast<const char8_t*> (e)}, ptr - b);
718 Assert (*remainder <= s.end ());
719 return result;
720 }
721#endif
722 return Private_::ToFloat_RespectingLocale_<T> (s, remainder); // fallback for non-ascii strings to strtod etc code
723 }
724 template <floating_point T, typename STRINGISH_ARG>
725 inline T ToFloat (STRINGISH_ARG&& s)
726 requires (IConvertibleToString<STRINGISH_ARG> or is_convertible_v<STRINGISH_ARG, std::string>)
727 {
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)});
733 }
734 else if constexpr (same_as<DecayedStringishArg, String>) {
735 Memory::StackBuffer<wchar_t> ignored; // todo optimize for keep ascii case
736 auto sp = s.template GetData<wchar_t> (&ignored);
737 return ToFloat<T> (sp);
738 }
739 else if constexpr (is_convertible_v<DecayedStringishArg, std::string>) {
740 string ss = s;
741 return ToFloat<T> (span{ss.data (), ss.size ()});
742 }
743 else {
744 return ToFloat<T> (String{forward<STRINGISH_ARG> (s)});
745 }
746 }
747 template <floating_point T>
748 inline T ToFloat (const String& s, String* remainder)
749 {
750 RequireNotNull (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 ())};
756 return result;
757 }
758
759 //////////////// DEPRECATED STUFF BELOW
760
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)
763 {
764 Require (start <= end);
765 RequireNotNull (remainder);
766 T result; // intentionally uninitialized
767#if __cpp_lib_to_chars >= 201611 and not qCompilerAndStdLib_from_chars_and_tochars_FP_Precision_Buggy
768 /*
769 * Most of the time we can do this very efficiently, because there are just ascii characters.
770 * Else, fallback on older algorithm that understands full unicode character set.
771 */
772 Memory::StackBuffer<char> asciiS;
773 if (Character::AsASCIIQuietly (span{start, end}, &asciiS)) {
774 auto b = asciiS.begin ();
775 auto originalB = b; // needed to properly compute remainder
776 auto e = asciiS.end ();
777 if (remainder != nullptr) [[unlikely]] {
778 // in remainder mode we skip leading whitespace
779 while (b != e and iswspace (*b)) [[unlikely]] {
780 ++b;
781 }
782 }
783 if (b != e and *b == '+') [[unlikely]] {
784 ++b; // "the plus sign is not recognized outside of the exponent (only the minus sign is permitted at the beginning)" from https://en.cppreference.com/w/cpp/utility/from_chars
785 }
786
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 ();
790 }
791 // if error - return nan
792 if (ec != std::errc{}) [[unlikely]] {
793 result = Math::nan<T> ();
794 }
795 // If asked to return remainder do so.
796 // If NOT asked to return remainder, treat not using the entire string as forcing result to be a Nan (invalid parse of number of not the whole thing is a number)
797 if (remainder == nullptr) [[likely]] {
798 if (ptr != e) [[unlikely]] {
799 result = Math::nan<T> ();
800 }
801 }
802 else {
803 *remainder = ptr - originalB + start; // adjust in case we remapped data
804 }
805 }
806 else {
807 result = Private_::ToFloat_ViaStrToD_<T> (start, end, remainder);
808 }
809#else
810 result = Private_::ToFloat_ViaStrToD_<T> (start, end, remainder);
811#endif
812 return result;
813 }
814 template <typename T = double>
815 [[deprecated ("Since Stroika v3.0d1 - use span overload")]] inline T ToFloat (const char* start, const char* end)
816 {
817 return ToFloat<T> (span{start, end});
818 }
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)
821 {
822 return ToFloat<T> (span{start, end});
823 }
824
825}
#define RequireNotReached()
Definition Assertions.h:385
#define RequireNotNull(p)
Definition Assertions.h:347
#define AssertNotReached()
Definition Assertions.h:355
#define Verify(c)
Definition Assertions.h:419
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)
Definition String.inl:340
static constexpr size_t npos
Definition String.h:1390
static const UTFConvert kThe
Nearly always use this default UTFConvert.
Definition UTFConvert.h:369
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.
STRING_TYPE ToString(FLOAT_TYPE f, const ToStringOptions &options={})
static const Precision kFull
Full precision here means enough digits so that when written out (serialized) - and read back in (des...
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...